├── packages
├── css-render
│ ├── .browserslistrc
│ ├── src
│ │ ├── hash.ts
│ │ ├── index.ts
│ │ ├── exists.ts
│ │ ├── CssRender.ts
│ │ ├── utils.ts
│ │ ├── c.ts
│ │ ├── parse.ts
│ │ ├── mount.ts
│ │ ├── types.ts
│ │ └── render.ts
│ ├── .eslintignore
│ ├── .eslintrc.js
│ ├── tsconfig.json
│ ├── tsconfig.cjs.json
│ ├── tsconfig.esm.json
│ ├── index.html
│ ├── playground
│ │ └── index.ts
│ ├── __tests__
│ │ ├── parse
│ │ │ ├── index.spec.ts
│ │ │ └── pathTestCases.spec.ts
│ │ ├── find
│ │ │ └── index.spec.ts
│ │ ├── render
│ │ │ ├── sassSpec.spec.ts
│ │ │ └── index.spec.ts
│ │ └── mount
│ │ │ └── index.spec.ts
│ ├── karma.conf.js
│ ├── rollup.config.js
│ ├── package.json
│ ├── CHANGELOG.md
│ ├── README.md
│ └── CHANGELOG.json
├── plugin-bem
│ ├── .browserslistrc
│ ├── .eslintignore
│ ├── tsconfig.json
│ ├── .eslintrc.js
│ ├── README.md
│ ├── babel.config.js
│ ├── jest.config.js
│ ├── tsconfig.esm.json
│ ├── tsconfig.cjs.json
│ ├── package.json
│ ├── CHANGELOG.md
│ ├── index.ts
│ ├── CHANGELOG.json
│ └── __tests__
│ │ └── index.spec.ts
├── test-shared
│ ├── README.md
│ ├── tsconfig.json
│ ├── tsconfig.esm.json
│ ├── tsconfig.cjs.json
│ ├── utils.ts
│ └── package.json
├── vue3-ssr
│ ├── .browserslistrc
│ ├── .eslintignore
│ ├── .eslintrc.js
│ ├── babel.config.js
│ ├── tsconfig.cjs.json
│ ├── tsconfig.esm.json
│ ├── tsconfig.json
│ ├── jest.config.js
│ ├── __tests__
│ │ ├── __snapshots__
│ │ │ └── index.spec.ts.snap
│ │ └── index.spec.ts
│ ├── package.json
│ ├── README.md
│ ├── src
│ │ └── index.ts
│ ├── CHANGELOG.md
│ └── CHANGELOG.json
├── docs
│ ├── package.json
│ └── docs
│ │ ├── installation.md
│ │ ├── .vitepress
│ │ └── config.js
│ │ ├── css-render-instance.md
│ │ ├── qa.md
│ │ ├── mount.md
│ │ ├── get-started.md
│ │ ├── plugin-development.md
│ │ ├── cnode-and-render.md
│ │ └── index.md
└── eslint-config
│ ├── package.json
│ └── base.js
├── common
├── git-hooks
│ ├── precommit
│ └── commit-msg.sample
├── config
│ └── rush
│ │ ├── command-line.json
│ │ ├── repo-state.json
│ │ ├── version-policies.json
│ │ ├── common-versions.json
│ │ ├── .npmrc
│ │ └── .npmrc-publish
└── scripts
│ ├── install-run-rushx.js
│ └── install-run-rush.js
├── playground
├── prototype.ts
├── testHint.ts
├── testHint.js
├── perf-log.js
├── perf.log
├── button.js
└── button.perf.js
├── CONTRIBUTING.md
├── package.json
├── tsconfig.json
├── MEMO.md
├── .gitattributes
├── .github
├── dependabot.yml
└── workflows
│ └── nodejs.yml
├── LICENSE
├── .gitignore
├── TODO.md
├── rush.json
└── README.md
/packages/css-render/.browserslistrc:
--------------------------------------------------------------------------------
1 | last 1 version
2 | > 1%
--------------------------------------------------------------------------------
/packages/plugin-bem/.browserslistrc:
--------------------------------------------------------------------------------
1 | last 1 version
2 | > 1%
--------------------------------------------------------------------------------
/packages/test-shared/README.md:
--------------------------------------------------------------------------------
1 | # @css-render/test-shared
--------------------------------------------------------------------------------
/packages/vue3-ssr/.browserslistrc:
--------------------------------------------------------------------------------
1 | last 1 version
2 | > 1%
--------------------------------------------------------------------------------
/packages/css-render/src/hash.ts:
--------------------------------------------------------------------------------
1 | export { default } from '@emotion/hash'
2 |
--------------------------------------------------------------------------------
/packages/test-shared/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.esm.json"
3 | }
--------------------------------------------------------------------------------
/packages/css-render/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | **/*/dist
4 | **/*/esm
5 | **/*/lib
6 |
--------------------------------------------------------------------------------
/packages/plugin-bem/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | **/*/dist
4 | **/*/esm
5 | **/*/lib
6 |
--------------------------------------------------------------------------------
/packages/vue3-ssr/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | **/*/dist
4 | **/*/esm
5 | **/*/lib
6 |
--------------------------------------------------------------------------------
/common/git-hooks/precommit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cp README.md packages/css-render/README.md
4 | cp README.md packages/docs/docs/index.md
5 | add .
6 |
--------------------------------------------------------------------------------
/packages/plugin-bem/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.esm.json",
3 | "include": [
4 | "./index.ts",
5 | "./__tests__/**/*"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/css-render/.eslintrc.js:
--------------------------------------------------------------------------------
1 | require('@rushstack/eslint-config/patch/modern-module-resolution')
2 |
3 | module.exports = {
4 | extends: ['@css-render/eslint-config/base']
5 | }
6 |
--------------------------------------------------------------------------------
/packages/plugin-bem/.eslintrc.js:
--------------------------------------------------------------------------------
1 | require('@rushstack/eslint-config/patch/modern-module-resolution')
2 |
3 | module.exports = {
4 | extends: ['@css-render/eslint-config/base']
5 | }
6 |
--------------------------------------------------------------------------------
/packages/plugin-bem/README.md:
--------------------------------------------------------------------------------
1 | # @css-render/plugin-bem
2 | A plugin of css-render that helping generate BEM standard CSS
3 |
4 | See [css-render](https://github.com/07akioni/css-render)
--------------------------------------------------------------------------------
/packages/vue3-ssr/.eslintrc.js:
--------------------------------------------------------------------------------
1 | require('@rushstack/eslint-config/patch/modern-module-resolution')
2 |
3 | module.exports = {
4 | extends: ['@css-render/eslint-config/base']
5 | }
6 |
--------------------------------------------------------------------------------
/packages/vue3-ssr/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', { targets: { node: 'current' } }],
4 | '@babel/preset-typescript'
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/packages/plugin-bem/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', { targets: { node: 'current' } }],
4 | '@babel/preset-typescript'
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/common/config/rush/command-line.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json",
3 | "commands": [],
4 | "parameters": []
5 | }
6 |
--------------------------------------------------------------------------------
/packages/plugin-bem/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | collectCoverage: true,
3 | collectCoverageFrom: [
4 | './index.ts'
5 | ],
6 | reporters: ['jest-standard-reporter']
7 | }
8 |
--------------------------------------------------------------------------------
/playground/prototype.ts:
--------------------------------------------------------------------------------
1 | interface I {
2 | x: number
3 | y: string
4 | }
5 |
6 | const i: I = Object.create({
7 | x: 0
8 | })
9 |
10 | function a (): I {
11 | return i
12 | }
13 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # CONTRIBUTING
2 |
3 | css-render use `rush` as monorepo manager.
4 |
5 | `npm i -g @mircosoft/rush`
6 |
7 | Before you start to make some change, run `rush update` then `rush install`.
8 |
--------------------------------------------------------------------------------
/common/config/rush/repo-state.json:
--------------------------------------------------------------------------------
1 | // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
2 | {
3 | "preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
4 | }
5 |
--------------------------------------------------------------------------------
/common/config/rush/version-policies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "definitionName": "lockStepVersion",
4 | "policyName": "css-render",
5 | "version": "0.15.14",
6 | "nextBump": "patch"
7 | }
8 | ]
9 |
--------------------------------------------------------------------------------
/packages/css-render/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.esm.json",
3 | "include": [
4 | "src/**/*",
5 | "__tests__/**/*"
6 | ],
7 | "compilerOptions": {
8 | "rootDir": "."
9 | }
10 | }
--------------------------------------------------------------------------------
/common/config/rush/common-versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json",
3 | "preferredVersions": {
4 | },
5 | "allowedAlternativeVersions": {}
6 | }
7 |
--------------------------------------------------------------------------------
/packages/plugin-bem/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./esm",
5 | "rootDir": ".",
6 | "module": "ESNext"
7 | },
8 | "include": [
9 | "./index.ts"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/test-shared/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./esm",
5 | "rootDir": ".",
6 | "module": "ESNext"
7 | },
8 | "include": [
9 | "./utils.ts"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/css-render/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./lib",
5 | "rootDir": "./src",
6 | "module": "CommonJS"
7 | },
8 | "include": [
9 | "./src/**/*"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/css-render/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./esm",
5 | "rootDir": "./src",
6 | "module": "ESNext"
7 | },
8 | "include": [
9 | "./src/**/*"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/plugin-bem/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./lib",
5 | "rootDir": ".",
6 | "module": "CommonJS"
7 | },
8 | "include": [
9 | "./index.ts"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/test-shared/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./lib",
5 | "rootDir": ".",
6 | "module": "CommonJS"
7 | },
8 | "include": [
9 | "./utils.ts"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/vue3-ssr/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./lib",
5 | "rootDir": "./src",
6 | "module": "CommonJS"
7 | },
8 | "include": [
9 | "./src/**/*"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/vue3-ssr/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./esm",
5 | "rootDir": "./src",
6 | "module": "ESNext"
7 | },
8 | "include": [
9 | "./src/**/*"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/vue3-ssr/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.esm.json",
3 | "include": [
4 | "src/**/*",
5 | "__tests__/**/*"
6 | ],
7 | "compilerOptions": {
8 | "rootDir": ".",
9 | "types": [
10 | "jest"
11 | ]
12 | }
13 | }
--------------------------------------------------------------------------------
/packages/css-render/src/index.ts:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 | import { CssRender } from './CssRender'
3 |
4 | export * from './types'
5 | export { default as hash } from './hash'
6 | export { exists } from './exists'
7 | export { CssRender }
8 | export default CssRender
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "lint": "eslint \"packages/**/*.ts\" \"packages/**/*.js\" --fix",
5 | "perf": "npm run build && node playground/perf-log.js >> playground/perf.log"
6 | },
7 | "author": "07akioni",
8 | "license": "MIT"
9 | }
10 |
--------------------------------------------------------------------------------
/packages/css-render/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CssRender Test
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/packages/css-render/playground/index.ts:
--------------------------------------------------------------------------------
1 | import { CssRender } from '../src/index'
2 |
3 | const { c } = CssRender()
4 |
5 | const style = c([
6 | c('.a', {
7 | color: 'red'
8 | }, [
9 | c('.b', {
10 | color: 'green'
11 | })
12 | ])
13 | ])
14 |
15 | style.mount({
16 | id: 'ab'
17 | })
18 |
--------------------------------------------------------------------------------
/packages/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@css-render/docs",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build:vercel": "vitepress build docs",
7 | "build": "",
8 | "dev": "vitepress dev docs"
9 | },
10 | "devDependencies": {
11 | "vitepress": "~0.21.3"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "module": "ESNext",
5 | "declaration": true,
6 | "declarationMap": false,
7 | "sourceMap": false,
8 | "lib": [
9 | "ESNext",
10 | "DOM"
11 | ],
12 | "strict": true,
13 | "target": "ES6"
14 | }
15 | }
--------------------------------------------------------------------------------
/packages/vue3-ssr/jest.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * For a detailed explanation regarding each configuration property, visit:
3 | * https://jestjs.io/docs/en/configuration.html
4 | */
5 | module.exports = {
6 | collectCoverage: true,
7 | collectCoverageFrom: [
8 | 'src/**/*'
9 | ],
10 | reporters: ['jest-standard-reporter']
11 | }
12 |
--------------------------------------------------------------------------------
/playground/testHint.ts:
--------------------------------------------------------------------------------
1 | import CssRender from '@css-render/core/src'
2 |
3 | const cssr = CssRender()
4 | const { c } = cssr
5 |
6 | const target = c('selector', {
7 | background: 'black',
8 | from: {
9 | background: 'black'
10 | }
11 | }, [
12 | c('selector2', {
13 | background: 'black',
14 | from: {
15 | background: 'black'
16 | }
17 | })
18 | ])
19 |
--------------------------------------------------------------------------------
/packages/docs/docs/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## Core
4 | The core of css-render
5 | ```bash
6 | npm i -D css-render
7 | ```
8 |
9 | ## Plugin BEM
10 | css-render plugin for writing bem codes.
11 | ```bash
12 | npm i -D css-render @css-render/plugin-bem
13 | ```
14 |
15 | ## Vue3 SSR
16 | Vue3 SSR suite for css-render.
17 | ```bash
18 | npm i -D css-render @css-render/vue3-ssr
19 | ```
--------------------------------------------------------------------------------
/packages/test-shared/utils.ts:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai'
2 | const expect = chai.expect
3 |
4 | export function assertEqual (pattern: string, targetPattern: string): Chai.Assertion {
5 | return expect(
6 | pattern.replace(/\s+/g, ' ').replace(/\{\s+\}/g, '{}').trim()
7 | )
8 | .to.equal(
9 | targetPattern.replace(/\s+/g, ' ').replace(/\{\s+\}/g, '{}').trim()
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/packages/vue3-ssr/__tests__/__snapshots__/index.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ssr render to string should work 1`] = `
4 | "Child
"
9 | `;
10 |
11 | exports[`ssr useSsrAdapter should work 1`] = `
12 | "Child
"
17 | `;
18 |
--------------------------------------------------------------------------------
/packages/css-render/src/exists.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/strict-boolean-expressions */
2 | import { MountId, SsrAdapter } from './types'
3 | import { queryElement } from './utils'
4 |
5 | export function exists (id: MountId, ssr?: SsrAdapter): boolean {
6 | if (id === undefined) return false
7 | if (ssr) {
8 | const {
9 | context: { ids }
10 | } = ssr
11 | return ids.has(id)
12 | }
13 | return queryElement(id) !== null
14 | }
15 |
--------------------------------------------------------------------------------
/packages/test-shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@css-render/test-shared",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json"
6 | },
7 | "module": "esm/utils.js",
8 | "main": "lib/utils.js",
9 | "devDependencies": {
10 | "typescript": "~4.4.4",
11 | "chai": "^4.2.0",
12 | "@types/chai": "^4.2.11",
13 | "eslint": "~8.5.0",
14 | "@rushstack/eslint-config": "~2.5.1"
15 | },
16 | "sideEffects": false,
17 | "license": "MIT"
18 | }
19 |
--------------------------------------------------------------------------------
/MEMO.md:
--------------------------------------------------------------------------------
1 | ## 为全部的包更新依赖
2 |
3 | rush add -p "typescript@latest" --dev -m
4 | -m 所有包一起更新
5 | 需要目录正确
6 |
7 | ## 清除一切
8 |
9 | rush unlink && git clean -dfx
10 |
11 | ## 重安一遍
12 |
13 | rush update
14 |
15 | ## 构建
16 |
17 | rush rebuild -f from -t to -v -o only
18 | -v verbose
19 |
20 | ## 发包
21 | 先
22 | rush change
23 | rush change --bulk --message xxx --bump-type
24 | 再
25 | rush version --bump // 这玩意有的时候会生成一个 version update only,不知道原因,即使真的有 change,或许和时间有关
26 |
27 | 这个时候就可以提交了
28 |
29 | rush build
30 | rush publish --include-all --publish
--------------------------------------------------------------------------------
/packages/eslint-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@css-render/eslint-config",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "build": ""
6 | },
7 | "dependencies": {
8 | "@typescript-eslint/eslint-plugin": "^5.8.1",
9 | "@typescript-eslint/parser": "^5.8.1",
10 | "eslint-config-standard": "^16.0.3",
11 | "eslint-config-standard-with-typescript": "^21.0.1",
12 | "eslint-plugin-import": "^2.25.3",
13 | "eslint-plugin-node": "^11.1.0",
14 | "eslint-plugin-promise": "^6.0.0"
15 | },
16 | "devDependencies": {
17 | "@rushstack/eslint-config": "~2.5.1"
18 | },
19 | "license": "MIT"
20 | }
21 |
--------------------------------------------------------------------------------
/packages/css-render/src/CssRender.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CssRenderConfig,
3 | CssRenderInstance,
4 | createCNode,
5 | CssRenderPlugin
6 | } from './types'
7 | import {
8 | c
9 | } from './c'
10 | import {
11 | queryElement
12 | } from './utils'
13 |
14 | export function CssRender (config: CssRenderConfig = {}): CssRenderInstance {
15 | const cssr: CssRenderInstance = {
16 | c: (
17 | (...args: any[]) => c(cssr, ...args as [any, any, any])
18 | ) as createCNode,
19 | use: (plugin: CssRenderPlugin, ...args: any[]) => plugin.install(cssr, ...args),
20 | find: queryElement,
21 | context: {},
22 | config
23 | }
24 | return cssr
25 | }
26 |
--------------------------------------------------------------------------------
/packages/eslint-config/base.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | overrides: [
3 | {
4 | files: ['*.ts'],
5 | parser: '@typescript-eslint/parser',
6 | plugins: [
7 | '@typescript-eslint'
8 | ],
9 | extends: [
10 | 'standard-with-typescript'
11 | ],
12 | parserOptions: {
13 | project: './tsconfig.json'
14 | },
15 | rules: {
16 | '@typescript-eslint/no-floating-promises': 0
17 | }
18 | },
19 | {
20 | files: ['*.js'],
21 | extends: 'standard',
22 | parserOptions: {
23 | ecmaVersion: 2020,
24 | sourceType: 'module'
25 | }
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Don't allow people to merge changes to these generated files, because the result
2 | # may be invalid. You need to run "rush update" again.
3 | pnpm-lock.yaml merge=binary
4 | shrinkwrap.yaml merge=binary
5 | npm-shrinkwrap.json merge=binary
6 | yarn.lock merge=binary
7 |
8 | # Rush's JSON config files use JavaScript-style code comments. The rule below prevents pedantic
9 | # syntax highlighters such as GitHub's from highlighting these comments as errors. Your text editor
10 | # may also require a special configuration to allow comments in JSON.
11 | #
12 | # For more information, see this issue: https://github.com/microsoft/rushstack/issues/1088
13 | #
14 | *.json linguist-language=JSON-with-Comments
15 |
--------------------------------------------------------------------------------
/playground/testHint.js:
--------------------------------------------------------------------------------
1 | const { CssRender } = require('css-render')
2 | const { plugin } = require('@css-render/plugin-bem')
3 |
4 | const cssr = CssRender()
5 | const bemPlugin = plugin({
6 | blockPrefix: '.n-'
7 | })
8 | const { c } = cssr
9 | cssr.use(bemPlugin)
10 | const {
11 | cB, cE, cM, cNotM
12 | } = bemPlugin
13 |
14 | const style = c('selector', {
15 | background: 'black',
16 | from: {
17 | background: 'black'
18 | }
19 | }, [
20 | c('selector2', {
21 | background: 'black',
22 | from: {
23 | background: 'black'
24 | }
25 | })
26 | ])
27 |
28 | const style2 = c('selector', ({
29 | context,
30 | props
31 | }) => {
32 | return {
33 | x: ''
34 | }
35 | })
36 |
37 | const target = style.mount({
38 | target: '123'
39 | })
40 |
41 | style.render({
42 | cool: 'good'
43 | })
44 |
--------------------------------------------------------------------------------
/packages/css-render/__tests__/parse/index.spec.ts:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai'
2 | import { parseSelectorPath } from '../../src/parse'
3 | import pathTestCases from './pathTestCases.spec'
4 |
5 | const expect = chai.expect
6 |
7 | const _sr = /,(?![^(]*\))/
8 |
9 | function normalizeSelector (selector: string): string {
10 | // eslint-disable-next-line @typescript-eslint/require-array-sort-compare
11 | return selector
12 | .split(_sr)
13 | .map(part => part.trim())
14 | .sort()
15 | .join(', ')
16 | }
17 |
18 | describe('# parse selector path', () => {
19 | pathTestCases.forEach(testCase => {
20 | it(`parse result of ${testCase.input.toString()} should be ${testCase.output}`, () => {
21 | expect(normalizeSelector(parseSelectorPath(testCase.input))).to.equal(normalizeSelector(testCase.output))
22 | })
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/packages/css-render"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | - package-ecosystem: npm
9 | directory: "/packages/vue3-ssr"
10 | schedule:
11 | interval: daily
12 | open-pull-requests-limit: 10
13 | - package-ecosystem: npm
14 | directory: "/packages/plugin-bem"
15 | schedule:
16 | interval: daily
17 | open-pull-requests-limit: 10
18 | - package-ecosystem: npm
19 | directory: "/packages/docs"
20 | schedule:
21 | interval: daily
22 | open-pull-requests-limit: 10
23 | - package-ecosystem: npm
24 | directory: "/packages/eslint-config"
25 | schedule:
26 | interval: daily
27 | open-pull-requests-limit: 10
28 | - package-ecosystem: npm
29 | directory: "/packages/test-shared"
30 | schedule:
31 | interval: daily
32 | open-pull-requests-limit: 10
33 |
--------------------------------------------------------------------------------
/packages/css-render/__tests__/find/index.spec.ts:
--------------------------------------------------------------------------------
1 | import * as chai from 'chai'
2 | import CssRender from '../../src'
3 |
4 | const expect = chai.expect
5 | const cssr = CssRender()
6 |
7 | const {
8 | c, find
9 | } = cssr
10 |
11 | describe('# find', () => {
12 | const style = c('.red-block', {
13 | backgroundColor: 'red'
14 | })
15 | before(() => {
16 | style.mount({
17 | id: 'test-id-1'
18 | })
19 | style.mount({
20 | id: 'test-id-2'
21 | })
22 | style.mount({
23 | id: '14138'
24 | })
25 | style.mount({
26 | id: '14139'
27 | })
28 | })
29 | after(() => style.unmount())
30 | it('works', () => {
31 | expect(find('14138')).not.to.equal(null)
32 | expect(find('14139')).not.to.equal(null)
33 | expect(find('test-id-1')).not.to.equal(null)
34 | expect(find('test-id-2')).not.to.equal(null)
35 | expect(find('gogogo')).to.equal(null)
36 | expect(find('kirby')).to.equal(null)
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/packages/docs/docs/.vitepress/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | title: 'css-render',
3 | themeConfig: {
4 | sidebar: [
5 | {
6 | text: 'Introduction',
7 | link: '/'
8 | },
9 | {
10 | text: 'Installation',
11 | link: '/installation'
12 | },
13 | {
14 | text: 'Getting Started',
15 | link: '/get-started'
16 | },
17 | {
18 | text: 'Render Style',
19 | link: '/cnode-and-render'
20 | },
21 | {
22 | text: 'Mount Style',
23 | link: '/mount'
24 | },
25 | {
26 | text: 'Plugin Development',
27 | link: '/plugin-development'
28 | },
29 | {
30 | text: 'CssRender Instance',
31 | link: '/css-render-instance'
32 | },
33 | {
34 | text: 'Q & A',
35 | link: '/qa'
36 | }
37 | ],
38 | nav: [
39 | {
40 | text: 'Github',
41 | link: 'https://github.com/07akioni/css-render'
42 | }
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/packages/vue3-ssr/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@css-render/vue3-ssr",
3 | "version": "0.15.14",
4 | "scripts": {
5 | "lint": "eslint --fix *.js src/**/*.ts __tests__/**/*.ts",
6 | "test": "jest",
7 | "build": "npm run lint && rm -rf es lib && npm run test && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json"
8 | },
9 | "devDependencies": {
10 | "@babel/preset-env": "^7.12.17",
11 | "@babel/preset-typescript": "^7.12.17",
12 | "@css-render/eslint-config": "workspace:*",
13 | "@rushstack/eslint-config": "~2.5.1",
14 | "@types/jest": "^27.0.3",
15 | "@vue/server-renderer": "^3.0.11",
16 | "babel-jest": "^27.4.5",
17 | "css-render": "workspace:~0.15.14",
18 | "eslint": "~8.5.0",
19 | "jest": "^27.4.5",
20 | "jest-standard-reporter": "~2.0.0",
21 | "typescript": "~4.4.4",
22 | "vue": "^3.2.45"
23 | },
24 | "peerDependencies": {
25 | "vue": "^3.0.11"
26 | },
27 | "main": "lib/index.js",
28 | "module": "esm/index.js",
29 | "sideEffects": false,
30 | "license": "MIT"
31 | }
32 |
--------------------------------------------------------------------------------
/common/git-hooks/commit-msg.sample:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # This is an example Git hook for use with Rush. To enable this hook, rename this file
4 | # to "commit-msg" and then run "rush install", which will copy it from common/git-hooks
5 | # to the .git/hooks folder.
6 | #
7 | # TO LEARN MORE ABOUT GIT HOOKS
8 | #
9 | # The Git documentation is here: https://git-scm.com/docs/githooks
10 | # Some helpful resources: https://githooks.com
11 | #
12 | # ABOUT THIS EXAMPLE
13 | #
14 | # The commit-msg hook is called by "git commit" with one argument, the name of the file
15 | # that has the commit message. The hook should exit with non-zero status after issuing
16 | # an appropriate message if it wants to stop the commit. The hook is allowed to edit
17 | # the commit message file.
18 |
19 | # This example enforces that commit message should contain a minimum amount of
20 | # description text.
21 | if [ `cat $1 | wc -w` -lt 3 ]; then
22 | echo ""
23 | echo "Invalid commit message: The message must contain at least 3 words."
24 | exit 1
25 | fi
26 |
--------------------------------------------------------------------------------
/packages/css-render/src/utils.ts:
--------------------------------------------------------------------------------
1 | export function removeElement (el: HTMLStyleElement | null): void {
2 | /* istanbul ignore if */
3 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
4 | if (!el) return
5 | const parentElement = el.parentElement
6 | /* istanbul ignore else */
7 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
8 | if (parentElement) parentElement.removeChild(el)
9 | }
10 |
11 | export function queryElement (id: string, parent?: ParentNode): HTMLStyleElement | null {
12 | return (parent ?? document.head).querySelector(`style[cssr-id="${id}"]`)
13 | }
14 |
15 | export function createElement (id: string): HTMLStyleElement {
16 | const el = document.createElement('style')
17 | el.setAttribute('cssr-id', id)
18 | return el
19 | }
20 |
21 | export function isMediaOrSupports (selector: string | undefined | null): selector is string {
22 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
23 | if (!selector) return false
24 | return /^\s*@(s|m)/.test(selector)
25 | }
26 |
--------------------------------------------------------------------------------
/playground/perf-log.js:
--------------------------------------------------------------------------------
1 | const { execSync } = require('child_process')
2 |
3 | const loopTimes = 100
4 | const trimmedLength = 20
5 |
6 | let log = '-----log-start-----\n'
7 |
8 | const gitHash = execSync('git rev-parse HEAD').toString().trim()
9 | log += ('Commit hash : ' + gitHash + '\n')
10 | log += ('Raw logs : ')
11 |
12 | const timeList = []
13 |
14 | for (let i = 0; i < loopTimes; ++i) {
15 | const output = execSync('node playground/button.perf.js')
16 | const time = Number(Number(output.toString()).toFixed(2))
17 | timeList.push(time)
18 | log += (time + ',')
19 | }
20 |
21 | const trimmedTimeList = timeList.sort().slice(
22 | trimmedLength / 2 - 1,
23 | loopTimes - trimmedLength / 2
24 | )
25 |
26 | const sum = trimmedTimeList.reduce((prevValue, currentValue) => prevValue + currentValue, 0)
27 |
28 | log += '\n'
29 | log += ('Max time : ' + Math.max(...timeList) + '\n')
30 | log += ('Min time : ' + Math.min(...timeList) + '\n')
31 | log += ('Average time : ' + (sum / (loopTimes - trimmedLength)).toFixed(4) + '\n')
32 | log += '------log-end------\n'
33 |
34 | console.log(log)
35 |
--------------------------------------------------------------------------------
/packages/vue3-ssr/README.md:
--------------------------------------------------------------------------------
1 | # @css-render/vue3-ssr
2 |
3 | ## Example
4 |
5 | ### Server
6 |
7 | ```js
8 | import { createSSRApp } from 'vue'
9 | import { renderToString } from '@vue/server-renderer'
10 | import { setup } = from '@css-render/vue3-ssr'
11 |
12 | // For each request, you need to create a new app
13 | const ssrApp = createSSRApp(App)
14 | const { collect } = setup(ssrApp)
15 |
16 | renderToString(ssrApp).then(appHtml => {
17 | const css = collect()
18 | const page = `
19 |
20 | ${css}
21 | ${appHtml}
22 | `
23 | })
24 | ```
25 |
26 | ### Component
27 |
28 | ```js
29 | import { defineComponent } from 'vue'
30 | import { useSsrAdapter } from '@css-render/vue3-ssr'
31 |
32 | const Child = defineComponent({
33 | setup() {
34 | c("div", {
35 | color: "red",
36 | }).mount({
37 | id: "mount-id",
38 | // It always returns undefined in browser (document === undefined)
39 | ssr: useSsrAdapter(),
40 | });
41 | },
42 | render() {
43 | return h("div", null, "Child");
44 | },
45 | });
46 | ```
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 07akioni
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/common/config/rush/.npmrc:
--------------------------------------------------------------------------------
1 | # Rush uses this file to configure the NPM package registry during installation. It is applicable
2 | # to PNPM, NPM, and Yarn package managers. It is used by operations such as "rush install",
3 | # "rush update", and the "install-run.js" scripts.
4 | #
5 | # NOTE: The "rush publish" command uses .npmrc-publish instead.
6 | #
7 | # Before invoking the package manager, Rush will copy this file to the folder where installation
8 | # is performed. The copied file will omit any config lines that reference environment variables
9 | # that are undefined in that session; this avoids problems that would otherwise result due to
10 | # a missing variable being replaced by an empty string.
11 | #
12 | # * * * SECURITY WARNING * * *
13 | #
14 | # It is NOT recommended to store authentication tokens in a text file on a lab machine, because
15 | # other unrelated processes may be able to read the file. Also, the file may persist indefinitely,
16 | # for example if the machine loses power. A safer practice is to pass the token via an
17 | # environment variable, which can be referenced from .npmrc using ${} expansion. For example:
18 | #
19 | # //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}
20 | #
21 | registry=https://registry.npmjs.org/
22 | always-auth=false
23 |
--------------------------------------------------------------------------------
/packages/docs/docs/css-render-instance.md:
--------------------------------------------------------------------------------
1 | # `CssRender` Instance
2 | You may wonder why css-render doesn't provide a `c` method from its entry point like `React.createElement` or `Vue.h`. The reason is the `c` method shared a context can be modified by plugins. I can't make `c` a singleton since it may be influenced accidentally by other packages.
3 |
4 | `CssRender` instance can be created by the following codes:
5 | ```js
6 | import CssRender from 'css-render'
7 |
8 | const config = {
9 | // ...
10 | }
11 | const cssr = CssRender(config) // cssr is a CssRender instance
12 | ```
13 | ## Config
14 | ```ts
15 | interface Config {
16 | // whether to render the CSS of a CNode with empty properties (eg. {})
17 | // default is false
18 | keepEmptyBlock: boolean
19 | }
20 | ```
21 | ## Properties on the Instance
22 | the instance(`cssr`) has some public properties:
23 | - `c(...)`: the method to create a `CNode`, see [Create a CNode & Render a CNode Tree](cnode-and-render.md).
24 | - `context`: the context of the instance, default is `{}`. It may be used by a plugin or in the rendering phase of a `CNode` tree.
25 | - `use(plugin)`: bind the instance with a plugin.
26 |
27 | ### Example
28 | ```ts
29 | import CssRender from 'css-render'
30 |
31 | const {
32 | c, context, use
33 | } = CssRender()
34 | ```
35 |
--------------------------------------------------------------------------------
/packages/docs/docs/qa.md:
--------------------------------------------------------------------------------
1 | # Q & A
2 | #### This sounds a bit like styled-components to me. How does this package differ?
3 |
4 | 1. css-render is mainly built for **library builders**. It helps the maintainer ship a library wihout css at a small cost. (gzip < 2kb)
5 |
6 | 2. css-render doesn't do the bindings between components and styles. It is more like a style generator with low level mount and unmount API. So it's not recommend to build style for web-apps.
7 |
8 | 3. As result of `2`, it just generate CSS and mount it to html so doesn't bind with any specific framework.
9 |
10 | 4. css-render is easier to write like a sass mixin or less mixin.
11 | ```ts
12 | // For example
13 | cB('block', [
14 | cE('element', [
15 | cM('modifier', {})
16 | ]
17 | ])
18 | // generates css:
19 | // block__element--modifier {}
20 | ```
21 | If you build library with sass or less, you may have a try with css-render.
22 |
23 | 5. Style (CNode) can be reused at any granularity.
24 |
25 | 6. Style can be generated in node side simplelly.
26 |
27 | However, I want to stress that it can be just a supplementary method. If you find everything you are using, for example pure CSS, preprocessors, or other libraries work fine, just keep going, they are doing great jobs.
28 |
29 | When you got some bottleneck and find the mentioned features are useful for you, why not have a try?
--------------------------------------------------------------------------------
/common/config/rush/.npmrc-publish:
--------------------------------------------------------------------------------
1 | # This config file is very similar to common/config/rush/.npmrc, except that .npmrc-publish
2 | # is used by the "rush publish" command, as publishing often involves different credentials
3 | # and registries than other operations.
4 | #
5 | # Before invoking the package manager, Rush will copy this file to "common/temp/publish-home/.npmrc"
6 | # and then temporarily map that folder as the "home directory" for the current user account.
7 | # This enables the same settings to apply for each project folder that gets published. The copied file
8 | # will omit any config lines that reference environment variables that are undefined in that session;
9 | # this avoids problems that would otherwise result due to a missing variable being replaced by
10 | # an empty string.
11 | #
12 | # * * * SECURITY WARNING * * *
13 | #
14 | # It is NOT recommended to store authentication tokens in a text file on a lab machine, because
15 | # other unrelated processes may be able to read the file. Also, the file may persist indefinitely,
16 | # for example if the machine loses power. A safer practice is to pass the token via an
17 | # environment variable, which can be referenced from .npmrc using ${} expansion. For example:
18 | #
19 | # //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}
20 | #
21 | //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}
22 |
--------------------------------------------------------------------------------
/common/scripts/install-run-rushx.js:
--------------------------------------------------------------------------------
1 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
2 | //
3 | // This script is intended for usage in an automated build environment where the Rush command may not have
4 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
5 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to the
6 | // rushx command.
7 | //
8 | // An example usage would be:
9 | //
10 | // node common/scripts/install-run-rushx.js custom-command
11 | //
12 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
13 |
14 | /******/ (() => { // webpackBootstrap
15 | /******/ "use strict";
16 | var __webpack_exports__ = {};
17 | /*!*************************************************!*\
18 | !*** ./lib-esnext/scripts/install-run-rushx.js ***!
19 | \*************************************************/
20 |
21 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22 | // See the @microsoft/rush package's LICENSE file for license information.
23 | require('./install-run-rush');
24 | //# sourceMappingURL=install-run-rushx.js.map
25 | module.exports = __webpack_exports__;
26 | /******/ })()
27 | ;
28 | //# sourceMappingURL=install-run-rushx.js.map
--------------------------------------------------------------------------------
/packages/css-render/karma.conf.js:
--------------------------------------------------------------------------------
1 | process.env.CHROME_BIN = require('puppeteer').executablePath()
2 | const pluginKarmaMocha = require('karma-mocha')
3 | const pluginKarmaTypescript = require('karma-typescript')
4 | const pluginKarmaChromeLauncher = require('karma-chrome-launcher')
5 | const pluginKarmaSpecReporter = require('karma-spec-reporter')
6 |
7 | module.exports = function (config) {
8 | config.set({
9 | singleRun: true,
10 | browsers: ['ChromeHeadless'],
11 | plugins: [
12 | pluginKarmaMocha,
13 | pluginKarmaTypescript,
14 | pluginKarmaChromeLauncher,
15 | pluginKarmaSpecReporter
16 | ],
17 | frameworks: ['mocha', 'karma-typescript'],
18 | files: [
19 | 'src/**/*.ts',
20 | '__tests__/**/*.ts'
21 | ],
22 | preprocessors: {
23 | 'src/**/*.ts': 'karma-typescript',
24 | '__tests__/**/*.ts': 'karma-typescript'
25 | },
26 | reporters: ['spec', 'karma-typescript'],
27 | karmaTypescriptConfig: {
28 | compilerOptions: {
29 | module: 'CommonJS',
30 | inlineSourceMap: true
31 | },
32 | reports: {
33 | text: '',
34 | html: {
35 | directory: 'coverage',
36 | subdirectory: 'html'
37 | },
38 | lcovonly: {
39 | directory: 'coverage',
40 | subdirectory: 'lcov'
41 | }
42 | },
43 | tsconfig: './tsconfig.json'
44 | }
45 | })
46 | }
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | *.log
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 |
7 | # Runtime data
8 | *.pid
9 | *.seed
10 | *.pid.lock
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # nyc test coverage
19 | .nyc_output
20 |
21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
22 | .grunt
23 |
24 | # Bower dependency directory (https://bower.io/)
25 | bower_components
26 |
27 | # node-waf configuration
28 | .lock-wscript
29 |
30 | # Compiled binary addons (https://nodejs.org/api/addons.html)
31 | build/Release
32 |
33 | # Dependency directories
34 | node_modules/
35 | jspm_packages/
36 |
37 | # Optional npm cache directory
38 | .npm
39 |
40 | # Optional eslint cache
41 | .eslintcache
42 |
43 | # Optional REPL history
44 | .node_repl_history
45 |
46 | # Output of 'npm pack'
47 | *.tgz
48 |
49 | # Yarn Integrity file
50 | .yarn-integrity
51 |
52 | # dotenv environment variables file
53 | .env
54 |
55 | # next.js build output
56 | .next
57 |
58 | # OS X temporary files
59 | .DS_Store
60 |
61 | # Rush temporary files
62 | common/deploy/
63 | common/temp/
64 | common/autoinstallers/*/.npmrc
65 | **/.rush/temp/
66 |
67 | package-lock.json
68 | *.swp
69 | yarn.lock
70 | .yarn
71 | dist
72 | **/*/dist
73 | **/*/esm
74 | **/*/lib
75 | playground/*.css
76 | .vscode
77 | .DS_Store
78 | .pnp.js
79 | *.tsbuildinfo
80 |
--------------------------------------------------------------------------------
/packages/css-render/rollup.config.js:
--------------------------------------------------------------------------------
1 | const { terser } = require('rollup-plugin-terser')
2 | const path = require('path')
3 |
4 | module.exports = {
5 | input: {
6 | input: path.resolve(__dirname, 'esm/index.js')
7 | },
8 | output: [
9 | {
10 | file: path.resolve(__dirname, 'dist/css-render.iife.js'),
11 | format: 'iife',
12 | name: 'CssRender'
13 | },
14 | {
15 | file: path.resolve(__dirname, 'dist/css-render.cjs.js'),
16 | format: 'cjs',
17 | exports: 'named'
18 | },
19 | {
20 | file: path.resolve(__dirname, 'dist/css-render.esm.js'),
21 | format: 'es',
22 | exports: 'named'
23 | },
24 | {
25 | file: path.resolve(__dirname, 'dist/css-render.esm.min.js'),
26 | format: 'es',
27 | exports: 'named',
28 | plugins: [
29 | terser({
30 | compress: true,
31 | mangle: true
32 | })
33 | ]
34 | },
35 | {
36 | file: path.resolve(__dirname, 'dist/css-render.cjs.min.js'),
37 | format: 'cjs',
38 | exports: 'named',
39 | plugins: [
40 | terser({
41 | compress: true,
42 | mangle: true
43 | })
44 | ]
45 | },
46 | {
47 | file: path.resolve(__dirname, 'dist/css-render.iife.min.js'),
48 | format: 'iife',
49 | name: 'CssRender',
50 | plugins: [
51 | terser({
52 | compress: true,
53 | mangle: true
54 | })
55 | ]
56 | }
57 | ]
58 | }
59 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | 1. 允许创建多个 CSS Render 的实例,通过 create 方法或者构造函数,现在所有的渲染会共用一个上下文,这显然是有一些问题的✅
2 | 2. 当多个 CSS Render 存在的时候,Mount 节点需要能被手动控制,否则有的时候可能会产生冲突,主要取决于挂载的时候看不看命名空间让用户去管理 id 吧,我不管了✅
3 | 3. declarationMap, tsbuildinfo 都是什么玩意?Project Reference 又是啥?
4 | 4. 可能把 render 方法放在 CNode 上比较好✅
5 | 5. 插件 API 是不是友好?把 selector 换成 $ 可以带来 1% 左右的打包尺寸提升,emmmm,十分困扰,是要维持还说换呢换,API 可以理解,短一点是一点✅
6 | 6. 对属性的类型得写的严谨一点 通过 `csstype` 包已经解决了这个问题,可是关键是为啥本地 typescript 就不能帮我 resolve 正确的类型呢?我没法在这个文件夹内部测试啊!!!搞的只能 xjb 涨 npm 版本号(这必然也是个错误的行为) 我通过软连接解决这个问题了,似乎是一个说的过去的解决方案... 但是这个软连接会把测试覆盖率搞坏,总是得清掉= =✅
7 | 7. `karma-typescript` 似乎有个 bug,没法正确的 resolve 一个只包含 index.d.ts 的 npm 包!麻烦
8 | 8. mount 函数的测试✅
9 | 9. Minify 的 build,使用 rollup 和 terser
10 | 10. 在 render 时候类型的传递我似乎应该用泛型,现在全都是 any,not good虽然对外没啥区别✅
11 | 11. 感觉我应该允许 mount 和 unmount `null`, `undefined`,因为初始化的时候真的很难避免这两个值 我琢磨了一下,`undefined` 和现有的 API 冲突,里面还要传 props 呢,所以就只让 `null` 什么都不干好了✅
12 | 12. 然后我还感觉发布的包类型提醒不老对的,是不是重启 vscode 就能好了?
13 | 13. 处理 `:is(a, b, c)` 这种 selector✅
14 | 14. mount 的返回值类型不能改改吗,某些情况下不可能返回 null 的通过泛型解决✅
15 | 15. 为 unmount 增加 delay 选项,因为框架的生命周期不能精确的体现 dom 何时被真正的卸载✅
16 | 16. 使用 webworker 帮助渲染 // 真的能管用么?
17 | 17. 使用 serviceworker 缓存渲染结果这个没有 HTTPS 做不了,要做也应该是拓展
18 | 18. 为函数类型的 selector 和 children 增加文档
19 | 19. 为静态的选择器进行提前解析,稍微提升一丢丢性能 这个目前想来其实没啥用,因为实际使用中经常用动态样式
20 | 20. 经过我的仔细观察,测试覆盖率的问题应该是 source map 出了问题... See https://github.com/istanbuljs/nyc/issues/618#issuecomment-396818724
--------------------------------------------------------------------------------
/packages/plugin-bem/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@css-render/plugin-bem",
3 | "main": "lib/index.js",
4 | "module": "esm/index.js",
5 | "license": "MIT",
6 | "description": "A plugin of css-render that helping generate BEM standard CSS",
7 | "keywords": [
8 | "css-render",
9 | "css",
10 | "style",
11 | "css in js",
12 | "css-in-js",
13 | "bem"
14 | ],
15 | "files": [
16 | "lib",
17 | "esm"
18 | ],
19 | "author": "07akioni",
20 | "homepage": "https://github.com/07akioni/css-render",
21 | "repository": {
22 | "type": "git",
23 | "url": "https://github.com/07akioni/css-render"
24 | },
25 | "scripts": {
26 | "lint": "eslint --fix index.ts __tests__/**/*.ts",
27 | "test": "jest",
28 | "build": "npm run lint && npm run test && rm -rf es lib && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json"
29 | },
30 | "version": "0.15.14",
31 | "sideEffects": false,
32 | "peerDependencies": {
33 | "css-render": "~0.15.14"
34 | },
35 | "devDependencies": {
36 | "@babel/preset-env": "^7.12.17",
37 | "@babel/preset-typescript": "^7.12.17",
38 | "@css-render/eslint-config": "workspace:*",
39 | "@css-render/test-shared": "workspace:*",
40 | "@rushstack/eslint-config": "~2.5.1",
41 | "@types/jest": "^27.0.3",
42 | "@types/node": "~17.0.5",
43 | "babel-jest": "^27.4.5",
44 | "css-render": "workspace:~0.15.14",
45 | "eslint": "~8.5.0",
46 | "jest": "^27.4.5",
47 | "jest-standard-reporter": "~2.0.0",
48 | "typescript": "~4.4.4"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/rush.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json",
3 | "rushVersion": "5.88.0",
4 | "pnpmVersion": "7.19.0",
5 | "pnpmOptions": {
6 | "useWorkspaces": true
7 | },
8 | "nodeSupportedVersionRange": ">=14.15.0 <17.3.0 || >= 18.0.0 < 19.0.0",
9 | "ensureConsistentVersions": true,
10 | "projectFolderMinDepth": 2,
11 | "projectFolderMaxDepth": 2,
12 | "gitPolicy": {},
13 | "repository": {
14 | "url": "git@github.com:07akioni/css-render.git",
15 | "defaultBranch": "master"
16 | },
17 | "projects": [
18 | {
19 | "packageName": "css-render",
20 | "projectFolder": "packages/css-render",
21 | "shouldPublish": true,
22 | "versionPolicyName": "css-render"
23 | },
24 | {
25 | "packageName": "@css-render/vue3-ssr",
26 | "projectFolder": "packages/vue3-ssr",
27 | "shouldPublish": true,
28 | "versionPolicyName": "css-render"
29 | },
30 | {
31 | "packageName": "@css-render/plugin-bem",
32 | "projectFolder": "packages/plugin-bem",
33 | "shouldPublish": true,
34 | "versionPolicyName": "css-render"
35 | },
36 | {
37 | "packageName": "@css-render/test-shared",
38 | "projectFolder": "packages/test-shared",
39 | "shouldPublish": false
40 | },
41 | {
42 | "packageName": "@css-render/eslint-config",
43 | "projectFolder": "packages/eslint-config",
44 | "shouldPublish": false
45 | },
46 | {
47 | "packageName": "@css-render/docs",
48 | "projectFolder": "packages/docs",
49 | "shouldPublish": false
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [16.x]
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | with:
24 | fetch-depth: 2
25 | - name: Use Node.js ${{ matrix.node-version }}
26 | uses: actions/setup-node@v1
27 | with:
28 | node-version: ${{ matrix.node-version }}
29 | - run: node common/scripts/install-run-rush.js update
30 | - run: node common/scripts/install-run-rush.js install
31 | - run: node common/scripts/install-run-rush.js build --verbose
32 | - run: export CODECOV_TOKEN="1b50e2b4-d843-48eb-bec7-6665d283b783"
33 | - run: bash <(curl -s https://raw.githubusercontent.com/codecov/codecov-bash/7877012ffd85dfb7968f91b5992edd627483dfe0/codecov) -f packages/vue3-ssr/coverage/lcov.info -F vue3-ssr -i vue3-ssr
34 | - run: bash <(curl -s https://raw.githubusercontent.com/codecov/codecov-bash/7877012ffd85dfb7968f91b5992edd627483dfe0/codecov) -f packages/plugin-bem/coverage/lcov.info -F plugin-bem -i packages/plugin-bem
35 | - run: bash <(curl -s https://raw.githubusercontent.com/codecov/codecov-bash/7877012ffd85dfb7968f91b5992edd627483dfe0/codecov) -f packages/css-render/coverage/lcov/lcov.info -F css-render -i packages/css-render
36 |
--------------------------------------------------------------------------------
/packages/vue3-ssr/src/index.ts:
--------------------------------------------------------------------------------
1 | import { inject, InjectionKey, App } from 'vue'
2 |
3 | interface CssrSsrContext {
4 | styles: string[]
5 | ids: Set
6 | }
7 |
8 | const ssrContextKey =
9 | '@css-render/vue3-ssr' as unknown as InjectionKey
10 |
11 | function createStyleString (id: string, style: string): string {
12 | return ``
13 | }
14 |
15 | function ssrAdapter (
16 | id: string,
17 | style: string,
18 | ssrContext: CssrSsrContext
19 | ): void {
20 | const { styles, ids } = ssrContext
21 | // we need to impl other options to make it behaves the same as the client side
22 | if (ids.has(id)) return
23 | if (styles !== null) {
24 | ids.add(id)
25 | styles.push(createStyleString(id, style))
26 | }
27 | }
28 |
29 | const isBrowser = typeof document !== 'undefined'
30 |
31 | export function useSsrAdapter ():
32 | | {
33 | adapter: (id: string, style: string) => void
34 | context: CssrSsrContext
35 | }
36 | | undefined {
37 | if (isBrowser) return undefined
38 | const context = inject(ssrContextKey, null)
39 | if (context === null) return undefined
40 | return {
41 | adapter: (id, style) => ssrAdapter(id, style, context),
42 | context
43 | }
44 | }
45 |
46 | interface SsrHandle {
47 | collect: () => string
48 | }
49 |
50 | export function setup (app: App): SsrHandle {
51 | const styles: string[] = []
52 | const ssrContext: CssrSsrContext = {
53 | styles,
54 | ids: new Set()
55 | }
56 | app.provide(ssrContextKey, ssrContext)
57 | return {
58 | collect () {
59 | const res = styles.join('\n')
60 | styles.length = 0
61 | return res
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/packages/css-render/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "css-render",
3 | "main": "lib/index.js",
4 | "module": "esm/index.js",
5 | "license": "MIT",
6 | "description": "Generating CSS using JS with considerable flexibility and extensibility, at both server side and client side.",
7 | "keywords": [
8 | "css-render",
9 | "css",
10 | "style",
11 | "css in js",
12 | "css-in-js"
13 | ],
14 | "files": [
15 | "lib",
16 | "esm"
17 | ],
18 | "author": "07akioni",
19 | "homepage": "https://github.com/07akioni/css-render",
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/07akioni/css-render"
23 | },
24 | "version": "0.15.14",
25 | "sideEffects": false,
26 | "scripts": {
27 | "lint": "eslint --fix *.js src/*.ts",
28 | "lint:type": "tsc -p tsconfig.esm.json",
29 | "build": "npm run lint && npm run test && rm -rf lib esm && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json",
30 | "test": "rm -rf ./coverage && karma start karma.conf.js",
31 | "test:cov": "npm run test && http-server ./coverage/html"
32 | },
33 | "dependencies": {
34 | "@emotion/hash": "~0.8.0",
35 | "csstype": "~3.0.5"
36 | },
37 | "devDependencies": {
38 | "@css-render/eslint-config": "workspace:*",
39 | "@css-render/test-shared": "workspace:*",
40 | "@rushstack/eslint-config": "~2.5.1",
41 | "@types/chai": "^4.2.11",
42 | "@types/mocha": "^9.0.0",
43 | "@types/sinon": "^10.0.6",
44 | "chai": "^4.2.0",
45 | "eslint": "~8.5.0",
46 | "karma": "~6.3.2",
47 | "karma-chrome-launcher": "^3.1.0",
48 | "karma-mocha": "^2.0.1",
49 | "karma-spec-reporter": "0.0.33",
50 | "karma-typescript": "~5.5.1",
51 | "mocha": "^9.1.3",
52 | "puppeteer": "^13.0.1",
53 | "rollup": "^2.19.0",
54 | "rollup-plugin-terser": "^7.0.2",
55 | "sinon": "^12.0.1",
56 | "terser": "^5.3.3",
57 | "typescript": "~4.4.4",
58 | "vite": "~2.7.9"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/packages/css-render/__tests__/render/sassSpec.spec.ts:
--------------------------------------------------------------------------------
1 | import CssRender from '../../src'
2 | import { assertEqual } from '@css-render/test-shared'
3 |
4 | const {
5 | c
6 | } = CssRender()
7 |
8 | describe('#render (some compatible cases from sass-spec)', () => {
9 | it('should render as expected(1)', () => {
10 | assertEqual(
11 | c('div', [
12 | c('span, p, span', {
13 | color: 'red'
14 | }),
15 | c('a.foo.bar.foo', {
16 | color: 'green'
17 | }),
18 | c('&:nth(-3)', {
19 | color: 'blue'
20 | })
21 | ]).render(),
22 | `
23 | div span, div p, div span {
24 | color: red;
25 | }
26 | div a.foo.bar.foo {
27 | color: green;
28 | }
29 | div:nth(-3) {
30 | color: blue;
31 | }
32 | `
33 | )
34 | })
35 | it('should render as expected(2)', () => {
36 | /**
37 | * this case is modified, in the original case keyframe block is nested,
38 | * which doesn't follow CSS spec.
39 | */
40 | assertEqual(
41 | c('@-webkit-keyframes', {
42 | from: {
43 | left: '0px',
44 | whatever: 'hoo'
45 | },
46 | to: {
47 | left: '200px'
48 | }
49 | }).render(),
50 | `
51 | @-webkit-keyframes {
52 | from {
53 | left: 0px;
54 | whatever: hoo;
55 | }
56 | to {
57 | left: 200px;
58 | }
59 | }
60 | `
61 | )
62 | })
63 | it('should render as expected(3)', () => {
64 | assertEqual(
65 | c('a, b', {
66 | color: 'red'
67 | }, [
68 | c('c, d', {
69 | height: '10px'
70 | }, [
71 | c('e, f', {
72 | width: '12px'
73 | })
74 | ])
75 | ]).render(),
76 | `
77 | a, b {
78 | color: red;
79 | }
80 | a c, b c, a d, b d {
81 | height: 10px;
82 | }
83 | a c e, b c e, a d e, b d e, a c f, b c f, a d f, b d f {
84 | width: 12px;
85 | }
86 | `
87 | )
88 | })
89 | })
90 |
--------------------------------------------------------------------------------
/packages/docs/docs/mount.md:
--------------------------------------------------------------------------------
1 | # Advanced Mount & Unmount Options
2 | Every [CNode](https://github.com/07akioni/css-render/blob/master/docs/overview.md) has `mount` & `unmount` methods.
3 |
4 | They can help you mount style to the HTML document.
5 |
6 | > Since `CNode` support lazy selector, properties and children evaluation, and some call of the `mount` will render the `CNode` again, the mounted style can be different in different mount.
7 |
8 | ## Mount
9 | Render the `CNode`'s style and mount it to document.
10 |
11 | ```typescript
12 | type mount = (
13 | options?: {
14 | id?: string,
15 | props?: any,
16 | ssr?: SsrAdapter
17 | anchorMetaName?: string
18 | parent?: ParentNode
19 | }
20 | ) => HTMLStyleElement
21 | ```
22 |
23 | ### `id`
24 | - If `id` or `options` is `undefined`, every call of mount method will create a new `style` element with rendered style and mount it to `parent`. For example: `style.mount()` or `style.mount({ props: {} })`.
25 | - If `id` is a `string`. It will mount the style on a `style[cssr-id="${id}"]` element to `parent`. For example: ``. If the element already exists, the `mount` method will **not** refresh the content of the element.
26 | ### `props`
27 | The `props` will be used as the render function's `props` during this mount.
28 | ### `ssr`
29 | When mount the style in SSR environment, you should put correct ssr adapter in it.
30 |
31 | ### `anchorMetaName`
32 |
33 | The name of a meta tag as a mount position anchor.
34 |
35 | ### `parent`
36 | The parent element that mounts the style.
37 | Default `document.head`.
38 | This is useful for shadow DOM.
39 |
40 | ### Return Value
41 | In non-ssr environment, the id element for the style to be mounted on.
42 |
43 |
44 | ## Unmount
45 | Unmount the style of the CNode.
46 |
47 | ```typescript
48 | type unmount = (
49 | options?: {
50 | id?: string
51 | parent?: ParentNode
52 | }
53 | ) => void
54 | ```
55 |
56 | ### `id`
57 | - If `id` or `options` is `undefined`, every mounted elements of the `CNode` will be unmounted.
58 | - If `id` is a `string`. It will unmount `style[cssr-id="${id}"]` element mounted by the `CNode`.
59 |
60 | ### `parent`
61 |
62 | The parent of mounted style.
63 | Default `document.head`.
64 |
--------------------------------------------------------------------------------
/packages/docs/docs/get-started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 | `css-render` has a concept of `CNode`.
3 |
4 | Basically, a `CNode` is an object with `selector`, `properties` and `children`(child `CNodes`) properties.
5 |
6 | `CNode` has methods to render it to CSS literals, mount to `document` or unmount it from `document`.
7 |
8 | ## Create a `CNode`
9 | ```js
10 | import { CssRender } from 'css-render'
11 | /**
12 | * CommonJS:
13 | * const { CssRender } = require('css-render')
14 | */
15 |
16 | const {
17 | c
18 | } = CssRender() // create a css-render instance
19 |
20 | // style is a CNode
21 | const style = c('body', ({ props }) => ({
22 | margin: 0,
23 | backgroundColor: props.backgroundColor
24 | }), [
25 | c('&.dark', {
26 | backgroundColor: 'black'
27 | }),
28 | c('.container', {
29 | width: '100%'
30 | })
31 | ])
32 | ```
33 | Now you have a `CNode` contains your `style`.
34 | ## Render the `CNode` to String
35 | ```js
36 | console.log(style.render({ backgroundColor: 'white' }))
37 | ```
38 | Now you have
39 | ```css
40 | body {
41 | margin: 0;
42 | background-color: white;
43 | }
44 |
45 | body.dark {
46 | background-color: black;
47 | }
48 |
49 | body .container {
50 | width: 100%;
51 | }
52 | ```
53 | ## Mount `CNode` to HTML document
54 | Use `style.mount()` to mount the style.
55 | ```js
56 | style.mount()
57 |
58 | // Style can be mount with id.
59 | // If you provide an id, the style won't be regenerated again and again.
60 | style.mount({ id: 'my-style' })
61 | ```
62 | It will create a `HTMLStyleElement` with rendered style and mount it to `document.head`.
63 | ## Unmount style of the `CNode`
64 | Use `style.unmount()` to unmount the style.
65 | ```js
66 | // unmount all the mounted style from the CNode
67 | style.unmount()
68 |
69 | // Style can be unmount with id.
70 | // Only the mounted style with same id will be affected.
71 | style.unmount({ id: 'my-style' })
72 | ```
73 | It will remove all the mounted style elemented of the `style` object.
74 |
75 | ---
76 |
77 | - If you want to mount & unmount percisely, see [Advanced Mount & Unmount Options](mount.md).
78 |
79 | - If you want to create `CNode` and render `CNode` tree with more options, see [Create a CNode & Render a CNode Tree](cnode-and-render.md).
80 |
81 | - If you want to know what does the `CssRender` function returns, see [CssRender Instance](css-render-instance.md).
82 |
83 | - If you are interested in developing a plugin, see [Plugin Development](plugin-development.md).
84 |
--------------------------------------------------------------------------------
/packages/docs/docs/plugin-development.md:
--------------------------------------------------------------------------------
1 | # Plugin Development
2 | A `css-render` plugin should be a function that return a plugin object.
3 |
4 | The plugin need to follow the following rules:
5 | 1. If you want to access the properties of a [CssRender](css-render-instance.md) instance, the object should has a `install` method.
6 | 2. If you want your plugin has options, you should accept the options from the plugin function's params.
7 | ```js
8 | import CssRender from 'css-render'
9 |
10 | const cssr = CssRender()
11 |
12 | const plugin = function (options) {
13 | return {
14 | install (CssRenderInstance) {
15 | // do something on CssRenderInstance.context as initialization
16 | // or get the c method of the CssRenderInstance
17 | }
18 | }
19 | }
20 |
21 | cssr.use(plugin) // it will make install methods called
22 | ```
23 |
24 | There is no limitation on what other properties should the plugin object have. But it is recommended that provide the functionality by create some high-order functions of `c` method. (My convention is that the high-order function need to starts with 'c'.)
25 |
26 | Here is an example accumulator plugin (which is literally nonsense).
27 | ```js
28 | import CssRender from 'css-render'
29 |
30 | const cssr = CssRender()
31 |
32 | const Plugin = function ({
33 | startsFrom
34 | } = {
35 | startsFrom: 1
36 | }) {
37 | let context
38 | let c
39 | return {
40 | install (CssRenderInstance) {
41 | c = CssRenderInstance.c
42 | context = CssRenderInstance.context
43 | context.number = startsFrom
44 | },
45 | cAcc () {
46 | return c({
47 | $: '.number',
48 | before: (context) => {
49 | context.number += 1
50 | }
51 | }, ({ context }) => ({
52 | value: context.number
53 | }))
54 | }
55 | }
56 | }
57 |
58 | const plugin = Plugin({ startsFrom: 0 })
59 | cssr.use(plugin) // it will make install methods called
60 |
61 | const { cAcc } = plugin
62 |
63 | console.log(cAcc().render())
64 | console.log()
65 | console.log(cAcc().render())
66 | console.log()
67 | console.log(cAcc().render())
68 | ```
69 | It outputs:
70 |
71 | ```css
72 | .number {
73 | value: 1;
74 | }
75 |
76 | .number {
77 | value: 2;
78 | }
79 |
80 | .number {
81 | value: 3;
82 | }
83 | ```
84 |
85 | If you still have no idea on how to create a plugin, you may see [@css-render/plugin-bem](https://github.com/07akioni/css-render/tree/master/packages/plugin-bem) for some inspirations.
86 |
--------------------------------------------------------------------------------
/packages/css-render/src/c.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CNode,
3 | CProperties,
4 | CContext,
5 | CssRenderInstance,
6 | createCNodeForCssRenderInstance,
7 | baseCreateCNodeForCssRenderInstance,
8 | CRenderProps,
9 | CSelector,
10 | CNodeChildren,
11 | UnmountOption,
12 | MountOption,
13 | SsrAdapter
14 | } from './types'
15 | import { render } from './render'
16 | import { mount, unmount } from './mount'
17 |
18 | function wrappedRender (this: CNode, props?: T): string {
19 | return render(this, this.instance, props)
20 | }
21 |
22 | // do not guard node calling, it should throw an error.
23 | function wrappedMount (
24 | this: CNode,
25 | options: MountOption = {}
26 | // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
27 | ): T extends undefined ? HTMLStyleElement : void {
28 | const { id, ssr, props, head = false, force = false, anchorMetaName, parent } = options
29 | const targetElement = mount(
30 | this.instance,
31 | this,
32 | id,
33 | props,
34 | head,
35 | force,
36 | anchorMetaName,
37 | parent,
38 | ssr
39 | )
40 | return targetElement as any
41 | }
42 |
43 | function wrappedUnmount (
44 | this: CNode,
45 | options: UnmountOption = {}
46 | ): void {
47 | /* istanbul ignore next */
48 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
49 | const {
50 | id,
51 | parent
52 | } = options
53 | unmount(this.instance, this, id, parent)
54 | }
55 |
56 | const createCNode: baseCreateCNodeForCssRenderInstance = function (
57 | instance: CssRenderInstance,
58 | $: CSelector,
59 | props: CProperties,
60 | children: CNodeChildren
61 | ): CNode {
62 | return {
63 | instance,
64 | $,
65 | props,
66 | children,
67 | els: [],
68 | render: wrappedRender,
69 | mount: wrappedMount,
70 | unmount: wrappedUnmount
71 | }
72 | }
73 |
74 | export const c: createCNodeForCssRenderInstance = function (
75 | instance: CssRenderInstance,
76 | $: any,
77 | props: any,
78 | children: any
79 | ): CNode {
80 | if (Array.isArray($)) {
81 | return createCNode(instance, { $: null }, null, $)
82 | } else if (Array.isArray(props)) {
83 | return createCNode(instance, $, null, props)
84 | } else if (Array.isArray(children)) {
85 | return createCNode(instance, $, props, children)
86 | } else {
87 | return createCNode(instance, $, props, null)
88 | }
89 | } as createCNodeForCssRenderInstance
90 |
91 | export type {
92 | CNode,
93 | CProperties,
94 | CContext
95 | }
96 |
--------------------------------------------------------------------------------
/packages/plugin-bem/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log - @css-render/plugin-bem
2 |
3 | This log was last generated on Sun, 05 May 2024 06:34:46 GMT and should not be manually modified.
4 |
5 | ## 0.15.14
6 | Sun, 05 May 2024 06:34:46 GMT
7 |
8 | _Version update only_
9 |
10 | ## 0.15.13
11 | Sun, 05 May 2024 05:43:33 GMT
12 |
13 | _Version update only_
14 |
15 | ## 0.15.12
16 | Sat, 24 Dec 2022 17:17:08 GMT
17 |
18 | _Version update only_
19 |
20 | ## 0.15.11
21 | Sun, 21 Aug 2022 07:56:48 GMT
22 |
23 | _Version update only_
24 |
25 | ## 0.15.10
26 | Wed, 23 Mar 2022 12:33:17 GMT
27 |
28 | _Version update only_
29 |
30 | ## 0.15.9
31 | Sat, 05 Mar 2022 14:58:36 GMT
32 |
33 | _Version update only_
34 |
35 | ## 0.15.8
36 | Tue, 28 Dec 2021 18:59:30 GMT
37 |
38 | ### Updates
39 |
40 | - fix peer deps
41 |
42 | ## 0.15.7
43 | Tue, 28 Dec 2021 17:54:23 GMT
44 |
45 | _Version update only_
46 |
47 | ## 0.15.6
48 | Thu, 02 Sep 2021 15:52:34 GMT
49 |
50 | _Version update only_
51 |
52 | ## 0.15.5
53 | Sat, 24 Jul 2021 17:34:06 GMT
54 |
55 | _Version update only_
56 |
57 | ## 0.15.4
58 | Sun, 13 Jun 2021 04:56:14 GMT
59 |
60 | _Version update only_
61 |
62 | ## 0.15.3
63 | Sat, 12 Jun 2021 09:50:10 GMT
64 |
65 | _Version update only_
66 |
67 | ## 0.15.2
68 | Wed, 02 Jun 2021 04:15:14 GMT
69 |
70 | _Version update only_
71 |
72 | ## 0.15.1
73 | Tue, 01 Jun 2021 13:35:47 GMT
74 |
75 | _Version update only_
76 |
77 | ## 0.15.0
78 | Tue, 01 Jun 2021 13:10:25 GMT
79 |
80 | _Version update only_
81 |
82 | ## 0.14.1
83 | Wed, 19 May 2021 05:06:20 GMT
84 |
85 | _Version update only_
86 |
87 | ## 0.14.0
88 | Wed, 19 May 2021 05:03:05 GMT
89 |
90 | _Version update only_
91 |
92 | ## 0.13.9
93 | Sun, 16 May 2021 19:46:15 GMT
94 |
95 | _Version update only_
96 |
97 | ## 0.13.8
98 | Wed, 12 May 2021 11:45:08 GMT
99 |
100 | _Version update only_
101 |
102 | ## 0.13.7
103 | Wed, 12 May 2021 11:35:11 GMT
104 |
105 | _Version update only_
106 |
107 | ## 0.13.6
108 | Wed, 14 Apr 2021 17:53:56 GMT
109 |
110 | _Version update only_
111 |
112 | ## 0.13.5
113 | Wed, 14 Apr 2021 17:06:44 GMT
114 |
115 | ### Updates
116 |
117 | - feat(plugin-bem): add bPrefix prop
118 |
119 | ## 0.13.4
120 | Wed, 14 Apr 2021 16:25:11 GMT
121 |
122 | _Version update only_
123 |
124 | ## 0.13.3
125 | Wed, 14 Apr 2021 16:24:04 GMT
126 |
127 | _Version update only_
128 |
129 | ## 0.13.2
130 | Fri, 26 Feb 2021 08:24:05 GMT
131 |
132 | _Version update only_
133 |
134 | ## 0.13.1
135 | Fri, 26 Feb 2021 08:18:28 GMT
136 |
137 | ### Updates
138 |
139 | - No changes
140 |
141 | ## 0.13.0
142 | Wed, 24 Feb 2021 09:35:28 GMT
143 |
144 | ### Minor changes
145 |
146 | - No changes
147 |
148 |
--------------------------------------------------------------------------------
/packages/vue3-ssr/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log - @css-render/vue3-ssr
2 |
3 | This log was last generated on Sun, 05 May 2024 06:34:46 GMT and should not be manually modified.
4 |
5 | ## 0.15.14
6 | Sun, 05 May 2024 06:34:46 GMT
7 |
8 | _Version update only_
9 |
10 | ## 0.15.13
11 | Sun, 05 May 2024 05:43:33 GMT
12 |
13 | _Version update only_
14 |
15 | ## 0.15.12
16 | Sat, 24 Dec 2022 17:17:08 GMT
17 |
18 | _Version update only_
19 |
20 | ## 0.15.11
21 | Sun, 21 Aug 2022 07:56:48 GMT
22 |
23 | _Version update only_
24 |
25 | ## 0.15.10
26 | Wed, 23 Mar 2022 12:33:17 GMT
27 |
28 | ### Updates
29 |
30 | - Fix useSsrAdapter may return non-undefined value in browser.
31 |
32 | ## 0.15.9
33 | Sat, 05 Mar 2022 14:58:36 GMT
34 |
35 | _Version update only_
36 |
37 | ## 0.15.8
38 | Tue, 28 Dec 2021 18:59:30 GMT
39 |
40 | _Version update only_
41 |
42 | ## 0.15.7
43 | Tue, 28 Dec 2021 17:54:23 GMT
44 |
45 | _Version update only_
46 |
47 | ## 0.15.6
48 | Thu, 02 Sep 2021 15:52:34 GMT
49 |
50 | _Version update only_
51 |
52 | ## 0.15.5
53 | Sat, 24 Jul 2021 17:34:06 GMT
54 |
55 | ### Updates
56 |
57 | - Update readme
58 |
59 | ## 0.15.4
60 | Sun, 13 Jun 2021 04:56:14 GMT
61 |
62 | ### Updates
63 |
64 | - (breaking) Remove `ssrAdapter` exports
65 |
66 | ## 0.15.3
67 | Sat, 12 Jun 2021 09:50:10 GMT
68 |
69 | ### Updates
70 |
71 | - Add useSsrAdapter hook
72 |
73 | ## 0.15.2
74 | Wed, 02 Jun 2021 04:15:14 GMT
75 |
76 | _Version update only_
77 |
78 | ## 0.15.1
79 | Tue, 01 Jun 2021 13:35:47 GMT
80 |
81 | ### Updates
82 |
83 | - no main & module in pkg.json
84 |
85 | ## 0.15.0
86 | Tue, 01 Jun 2021 13:10:25 GMT
87 |
88 | ### Updates
89 |
90 | - support stream ssr
91 |
92 | ## 0.14.1
93 | Wed, 19 May 2021 05:06:20 GMT
94 |
95 | _Version update only_
96 |
97 | ## 0.14.0
98 | Wed, 19 May 2021 05:03:05 GMT
99 |
100 | _Version update only_
101 |
102 | ## 0.13.9
103 | Sun, 16 May 2021 19:46:15 GMT
104 |
105 | _Version update only_
106 |
107 | ## 0.13.8
108 | Wed, 12 May 2021 11:45:08 GMT
109 |
110 | _Version update only_
111 |
112 | ## 0.13.7
113 | Wed, 12 May 2021 11:35:11 GMT
114 |
115 | _Version update only_
116 |
117 | ## 0.13.6
118 | Wed, 14 Apr 2021 17:53:56 GMT
119 |
120 | _Version update only_
121 |
122 | ## 0.13.5
123 | Wed, 14 Apr 2021 17:06:44 GMT
124 |
125 | ### Updates
126 |
127 | - feat(plugin-bem): add bPrefix prop
128 |
129 | ## 0.13.4
130 | Wed, 14 Apr 2021 16:25:11 GMT
131 |
132 | _Version update only_
133 |
134 | ## 0.13.3
135 | Wed, 14 Apr 2021 16:24:04 GMT
136 |
137 | _Version update only_
138 |
139 | ## 0.13.2
140 | Fri, 26 Feb 2021 08:24:05 GMT
141 |
142 | _Version update only_
143 |
144 | ## 0.13.1
145 | Fri, 26 Feb 2021 08:18:28 GMT
146 |
147 | ### Updates
148 |
149 | - No changes
150 |
151 | ## 0.13.0
152 | Wed, 24 Feb 2021 09:35:28 GMT
153 |
154 | ### Minor changes
155 |
156 | - Add SsrContext component & ssrAdapter
157 |
158 |
--------------------------------------------------------------------------------
/packages/css-render/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log - css-render
2 |
3 | This log was last generated on Sun, 05 May 2024 06:34:46 GMT and should not be manually modified.
4 |
5 | ## 0.15.14
6 | Sun, 05 May 2024 06:34:46 GMT
7 |
8 | _Version update only_
9 |
10 | ## 0.15.13
11 | Sun, 05 May 2024 05:43:33 GMT
12 |
13 | _Version update only_
14 |
15 | ## 0.15.12
16 | Sat, 24 Dec 2022 17:17:08 GMT
17 |
18 | _Version update only_
19 |
20 | ## 0.15.11
21 | Sun, 21 Aug 2022 07:56:48 GMT
22 |
23 | ### Updates
24 |
25 | - Limit scope of style id check
26 |
27 | ## 0.15.10
28 | Wed, 23 Mar 2022 12:33:17 GMT
29 |
30 | _Version update only_
31 |
32 | ## 0.15.9
33 | Sat, 05 Mar 2022 14:58:36 GMT
34 |
35 | _Version update only_
36 |
37 | ## 0.15.8
38 | Tue, 28 Dec 2021 18:59:30 GMT
39 |
40 | _Version update only_
41 |
42 | ## 0.15.7
43 | Tue, 28 Dec 2021 17:54:23 GMT
44 |
45 | ### Updates
46 |
47 | - support metaAnchorName on mount
48 |
49 | ## 0.15.6
50 | Thu, 02 Sep 2021 15:52:34 GMT
51 |
52 | ### Updates
53 |
54 | - fix: mount before link element when head is true
55 |
56 | ## 0.15.5
57 | Sat, 24 Jul 2021 17:34:06 GMT
58 |
59 | _Version update only_
60 |
61 | ## 0.15.4
62 | Sun, 13 Jun 2021 04:56:14 GMT
63 |
64 | ### Updates
65 |
66 | - Add `exists` function to check if style is mounted
67 |
68 | ## 0.15.3
69 | Sat, 12 Jun 2021 09:50:10 GMT
70 |
71 | _Version update only_
72 |
73 | ## 0.15.2
74 | Wed, 02 Jun 2021 04:15:14 GMT
75 |
76 | ### Updates
77 |
78 | - fix throwing error in node env
79 |
80 | ## 0.15.1
81 | Tue, 01 Jun 2021 13:35:47 GMT
82 |
83 | _Version update only_
84 |
85 | ## 0.15.0
86 | Tue, 01 Jun 2021 13:10:25 GMT
87 |
88 | _Version update only_
89 |
90 | ## 0.14.1
91 | Wed, 19 May 2021 05:06:20 GMT
92 |
93 | _Version update only_
94 |
95 | ## 0.14.0
96 | Wed, 19 May 2021 05:03:05 GMT
97 |
98 | ### Updates
99 |
100 | - Remove count prop in mount option & unmount option. Rename boost prop in mount option to silent
101 |
102 | ## 0.13.9
103 | Sun, 16 May 2021 19:46:15 GMT
104 |
105 | ### Updates
106 |
107 | - add option.force for mount
108 |
109 | ## 0.13.8
110 | Wed, 12 May 2021 11:45:08 GMT
111 |
112 | ### Updates
113 |
114 | - Fix render multiple times when mount with same id
115 |
116 | ## 0.13.7
117 | Wed, 12 May 2021 11:35:11 GMT
118 |
119 | _Version update only_
120 |
121 | ## 0.13.6
122 | Wed, 14 Apr 2021 17:53:56 GMT
123 |
124 | _Version update only_
125 |
126 | ## 0.13.5
127 | Wed, 14 Apr 2021 17:06:44 GMT
128 |
129 | ### Updates
130 |
131 | - feat(plugin-bem): add bPrefix prop
132 |
133 | ## 0.13.4
134 | Wed, 14 Apr 2021 16:25:11 GMT
135 |
136 | _Version update only_
137 |
138 | ## 0.13.3
139 | Wed, 14 Apr 2021 16:24:04 GMT
140 |
141 | ### Updates
142 |
143 | - feat(mount): add `head` option to insert style before every style node in doc.head
144 |
145 | ## 0.13.2
146 | Fri, 26 Feb 2021 08:24:05 GMT
147 |
148 | _Version update only_
149 |
150 | ## 0.13.1
151 | Fri, 26 Feb 2021 08:18:28 GMT
152 |
153 | ### Updates
154 |
155 | - Deprecate MountOptions.count & UnmountOptions.count
156 |
157 | ## 0.13.0
158 | Wed, 24 Feb 2021 09:35:28 GMT
159 |
160 | ### Minor changes
161 |
162 | - Support ssrAdapter in mount
163 |
164 |
--------------------------------------------------------------------------------
/packages/css-render/src/parse.ts:
--------------------------------------------------------------------------------
1 | import { CSelectorPath } from './types'
2 |
3 | function ampCount (selector: string): number {
4 | let cnt = 0
5 | for (let i = 0; i < selector.length; ++i) {
6 | if (selector[i] === '&') ++cnt
7 | }
8 | return cnt
9 | }
10 |
11 | /**
12 | * Don't just use ',' to separate css selector. For example:
13 | * x:(a, b) {} will be split into 'x:(a' and 'b)', which is not expected.
14 | * Make sure comma doesn't exist inside parentheses.
15 | */
16 | const separatorRegex = /\s*,(?![^(]*\))\s*/g
17 | const extraSpaceRegex = /\s+/g
18 |
19 | /**
20 | * selector must includes '&'
21 | * selector is trimmed
22 | * every part of amp is trimmed
23 | */
24 | function resolveSelectorWithAmp (amp: string[], selector: string): string[] {
25 | const nextAmp: string[] = []
26 | selector.split(separatorRegex).forEach(partialSelector => {
27 | let round = ampCount(partialSelector)
28 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
29 | if (!round) {
30 | amp.forEach(partialAmp => {
31 | nextAmp.push(
32 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
33 | (partialAmp && partialAmp + ' ') + partialSelector
34 | )
35 | })
36 | return
37 | } else if (round === 1) {
38 | amp.forEach(partialAmp => {
39 | nextAmp.push(partialSelector.replace('&', partialAmp))
40 | })
41 | return
42 | }
43 | let partialNextAmp: string[] = [
44 | partialSelector
45 | ]
46 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
47 | while (round--) {
48 | const nextPartialNextAmp: string[] = []
49 | partialNextAmp.forEach(selectorItr => {
50 | amp.forEach(
51 | partialAmp => {
52 | nextPartialNextAmp.push(selectorItr.replace('&', partialAmp))
53 | }
54 | )
55 | })
56 | partialNextAmp = nextPartialNextAmp
57 | }
58 | partialNextAmp.forEach(part => nextAmp.push(part))
59 | })
60 | return nextAmp
61 | }
62 |
63 | /**
64 | * selector mustn't includes '&'
65 | * selector is trimmed
66 | */
67 | function resolveSelector (amp: string[], selector: string): string[] {
68 | const nextAmp: string[] = []
69 | selector.split(separatorRegex).forEach(partialSelector => {
70 | amp.forEach(partialAmp => {
71 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
72 | nextAmp.push(((partialAmp && partialAmp + ' ') + partialSelector))
73 | })
74 | })
75 | return nextAmp
76 | }
77 |
78 | export function parseSelectorPath (
79 | selectorPaths: CSelectorPath
80 | ): string {
81 | let amp: string[] = ['']
82 | selectorPaths.forEach(selector => {
83 | // eslint-disable-next-line
84 | selector = selector && selector.trim()
85 | if (
86 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
87 | !selector
88 | ) {
89 | /**
90 | * if it's a empty selector, do nothing
91 | */
92 | return
93 | }
94 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
95 | if (selector.includes('&')) {
96 | amp = resolveSelectorWithAmp(amp, selector)
97 | } else {
98 | amp = resolveSelector(amp, selector)
99 | }
100 | })
101 | return amp.join(', ').replace(extraSpaceRegex, ' ')
102 | }
103 |
--------------------------------------------------------------------------------
/packages/vue3-ssr/__tests__/index.spec.ts:
--------------------------------------------------------------------------------
1 | import { CssRender } from 'css-render'
2 | import { h, createSSRApp, defineComponent } from 'vue'
3 | import { renderToString } from '@vue/server-renderer'
4 | import { useSsrAdapter, setup } from '../src/index'
5 |
6 | const { c } = CssRender()
7 |
8 | describe('ssr', () => {
9 | describe('render to string', () => {
10 | const Child = defineComponent({
11 | setup () {
12 | c('div', {
13 | color: 'red'
14 | }).mount({
15 | id: 'mount-id',
16 | ssr: useSsrAdapter()
17 | })
18 | },
19 | render () {
20 | return h('div', null, 'Child')
21 | }
22 | })
23 | const App = defineComponent({
24 | render () {
25 | return h(Child)
26 | }
27 | })
28 | const app = createSSRApp(App)
29 | const { collect } = setup(app)
30 | it('should work', (done) => {
31 | renderToString(app).then((v) => {
32 | expect(collect() + v).toMatchSnapshot()
33 | done()
34 | })
35 | })
36 | })
37 | describe('useSsrAdapter', () => {
38 | const Child = defineComponent({
39 | setup () {
40 | c('div', {
41 | color: 'red'
42 | }).mount({
43 | id: 'mount-id',
44 | ssr: useSsrAdapter()
45 | })
46 | },
47 | render () {
48 | return h('div', null, 'Child')
49 | }
50 | })
51 | const App = defineComponent({
52 | render () {
53 | return h(Child)
54 | }
55 | })
56 | const app = createSSRApp(App)
57 | const { collect } = setup(app)
58 | it('should work', (done) => {
59 | renderToString(app).then((v) => {
60 | expect(collect() + v).toMatchSnapshot()
61 | done()
62 | })
63 | })
64 | })
65 | // uncomment after vue fixes the stream ssr bug
66 | // describe('render to stream', () => {
67 | // it('should work', (done) => {
68 | // const app = createSSRApp({
69 | // render () {
70 | // return [
71 | // h(Foo),
72 | // h(Suspense, null, {
73 | // default: () => h(Bar),
74 | // ssFallback: () => 'suspense'
75 | // }),
76 | // h(Foo)
77 | // ]
78 | // }
79 | // })
80 | // const Foo = {
81 | // setup () {
82 | // c('div', {
83 | // color: 'foo'
84 | // }).mount({
85 | // id: 'foo',
86 | // ssr: ssrAdapter
87 | // })
88 | // },
89 | // render: () => h('div', 'foo')
90 | // }
91 | // const Bar = {
92 | // async setup () {
93 | // c('div', {
94 | // color: 'bar'
95 | // }).mount({
96 | // id: 'bar',
97 | // ssr: ssrAdapter
98 | // })
99 | // await new Promise((resolve) => setTimeout(resolve, 1000))
100 | // },
101 | // render: () => {
102 | // return [h('div', 'bar'), h(Foo)]
103 | // }
104 | // }
105 | // const { collect } = setup(app)
106 | // const rs = renderToStream(app)
107 | // let html = ''
108 | // rs.on('data', (chunk) => {
109 | // html += `${collect()}\n${chunk.toString() as string}`
110 | // })
111 | // rs.on('end', () => {
112 | // expect(html).toMatchSnapshot()
113 | // done()
114 | // })
115 | // })
116 | // })
117 | })
118 |
--------------------------------------------------------------------------------
/packages/css-render/src/mount.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/prefer-ts-expect-error */
2 | /* eslint-disable @typescript-eslint/strict-boolean-expressions */
3 | import hash from './hash'
4 | import {
5 | CNode,
6 | CssRenderInstance,
7 | CRenderProps,
8 | MountId,
9 | SsrAdapter
10 | } from './types'
11 | import { createElement, queryElement, removeElement } from './utils'
12 |
13 | if (typeof window !== 'undefined') {
14 | (window as any).__cssrContext = {}
15 | }
16 |
17 | export function unmount (
18 | instance: CssRenderInstance,
19 | node: CNode,
20 | id: MountId,
21 | parent: ParentNode | undefined
22 | ): void {
23 | const { els } = node
24 | // If id is undefined, unmount all styles
25 | if (id === undefined) {
26 | els.forEach(removeElement)
27 | node.els = []
28 | } else {
29 | const target = queryElement(id, parent)
30 | // eslint-disable-next-line
31 | if (target && els.includes(target)) {
32 | removeElement(target)
33 | node.els = els.filter((el) => el !== target)
34 | }
35 | }
36 | }
37 |
38 | function addElementToList (
39 | els: HTMLStyleElement[],
40 | target: HTMLStyleElement
41 | ): void {
42 | els.push(target)
43 | }
44 |
45 | function mount (
46 | instance: CssRenderInstance,
47 | node: CNode,
48 | id: MountId,
49 | props: CRenderProps,
50 | head: boolean,
51 | force: boolean,
52 | anchorMetaName: string | undefined,
53 | parent: ParentNode | undefined,
54 | ssrAdapter: SsrAdapter
55 | ): void
56 | function mount (
57 | instance: CssRenderInstance,
58 | node: CNode,
59 | id: MountId,
60 | props: CRenderProps,
61 | head: boolean,
62 | force: boolean,
63 | anchorMetaName: string | undefined,
64 | parent: ParentNode | undefined,
65 | ssrAdapter?: undefined
66 | ): HTMLStyleElement
67 | function mount (
68 | instance: CssRenderInstance,
69 | node: CNode,
70 | id: MountId,
71 | props: CRenderProps,
72 | head: boolean,
73 | force: boolean,
74 | anchorMetaName: string | undefined,
75 | parent: ParentNode | undefined,
76 | ssrAdapter?: SsrAdapter
77 | // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
78 | ): HTMLStyleElement | void
79 | function mount (
80 | instance: CssRenderInstance,
81 | node: CNode,
82 | id: MountId,
83 | props: CRenderProps,
84 | head: boolean,
85 | force: boolean,
86 | anchorMetaName: string | undefined,
87 | parent: ParentNode | undefined,
88 | ssrAdapter?: SsrAdapter
89 | // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
90 | ): HTMLStyleElement | void {
91 | let style: string | undefined
92 | if (id === undefined) {
93 | style = node.render(props)
94 | id = hash(style)
95 | }
96 | if (ssrAdapter) {
97 | ssrAdapter.adapter(id, style ?? node.render(props))
98 | return
99 | }
100 | if (parent === undefined) {
101 | parent = document.head
102 | }
103 | const queriedTarget = queryElement(id, parent)
104 | if (queriedTarget !== null && !force) {
105 | return queriedTarget
106 | }
107 | const target = queriedTarget ?? createElement(id)
108 | if (style === undefined) style = node.render(props)
109 | target.textContent = style
110 | if (queriedTarget !== null) return queriedTarget
111 | if (anchorMetaName) {
112 | const anchorMetaEl = parent.querySelector(
113 | `meta[name="${anchorMetaName}"]`
114 | )
115 | if (anchorMetaEl) {
116 | parent.insertBefore(target, anchorMetaEl)
117 | addElementToList(node.els, target)
118 | return target
119 | }
120 | }
121 |
122 | if (head) {
123 | parent.insertBefore(
124 | target,
125 | parent.querySelector('style, link')
126 | )
127 | } else {
128 | parent.appendChild(target)
129 | }
130 | addElementToList(node.els, target)
131 | return target
132 | }
133 |
134 | export { mount }
135 |
--------------------------------------------------------------------------------
/packages/css-render/src/types.ts:
--------------------------------------------------------------------------------
1 | import { Properties } from 'csstype'
2 |
3 | interface CssrSsrContext {
4 | styles: string[]
5 | ids: Set
6 | }
7 |
8 | export interface SsrAdapter {
9 | adapter: (id: string, style: string) => void
10 | context: CssrSsrContext
11 | }
12 |
13 | export interface CContext {
14 | [key: string]: any
15 | }
16 |
17 | /** render related */
18 | export type CRenderProps = any
19 |
20 | export interface CRenderOption {
21 | context: CContext
22 | props: CRenderProps
23 | }
24 |
25 | /** mount related */
26 | export type MountId = string | undefined
27 | export interface UnmountOption {
28 | id?: MountId
29 | parent?: ParentNode
30 | }
31 |
32 | export interface MountOption {
33 | id?: MountId
34 | props?: CRenderProps
35 | ssr?: T
36 | head?: boolean
37 | force?: boolean
38 | anchorMetaName?: string
39 | parent?: ParentNode
40 | }
41 |
42 | /** find related */
43 | export type CFindTarget = (target: string, parent?: ParentNode) => HTMLStyleElement | null
44 |
45 | /** CNode */
46 | export interface CNode {
47 | $: CSelector
48 | props: CProperties
49 | children: CNodeChildren
50 | instance: CssRenderInstance
51 | els: HTMLStyleElement[]
52 | render: (props?: T) => string
53 | mount: (
54 | options?: MountOption
55 | // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
56 | ) => T extends undefined ? HTMLStyleElement : void
57 | unmount: (options?: UnmountOption) => void
58 | }
59 |
60 | /** Node Children */
61 | type CNodeLazyChild = (
62 | option: CRenderOption
63 | ) => CNodePlainChild | CNode | null | undefined
64 | type CNodePlainChild =
65 | | CNode
66 | | string
67 | | Array
68 | export type CNodeChildren = Array<
69 | CNodePlainChild | CNodeLazyChild | null | undefined
70 | > | null
71 |
72 | /** Properties */
73 | export type CProperty = CPlainProperties | string | number | undefined | null
74 | export interface CPlainProperties extends Properties {
75 | raw?: string
76 | [nonPropertyLiteral: string]: CProperty
77 | }
78 | export type CLazyProperties = (options: {
79 | context?: CContext
80 | props?: CRenderProps
81 | }) => CPlainProperties | string | null | undefined
82 | export type CProperties =
83 | | CPlainProperties
84 | | CLazyProperties
85 | | string
86 | | null
87 | | undefined
88 |
89 | /** Selector */
90 | export type CStringSelector = string
91 | export type CLazySelector = (
92 | options: CRenderOption
93 | ) => T
94 | export interface COptionSelector {
95 | $?: CLazySelector | CStringSelector | null
96 | before?: (context: CContext) => any
97 | after?: (context: CContext) => any
98 | }
99 | export type CSelector =
100 | | CStringSelector
101 | | CLazySelector
102 | | COptionSelector
103 | | null
104 | | undefined
105 | export type CSelectorPath = Array
106 |
107 | /** CNode */
108 | export interface createCNode {
109 | (selector: T, props: CProperties, children: CNodeChildren): CNode
110 | (selector: T, props: CProperties): CNode
111 | (selector: T, children: CNodeChildren): CNode
112 | (children: CNodeChildren): CNode
113 | }
114 |
115 | export type baseCreateCNodeForCssRenderInstance = (
116 | instance: CssRenderInstance,
117 | selector: CSelector,
118 | props: CProperties,
119 | children: CNodeChildren
120 | ) => CNode
121 |
122 | export interface createCNodeForCssRenderInstance
123 | extends baseCreateCNodeForCssRenderInstance {
124 | (instance: CssRenderInstance, selector: CSelector, props: CProperties): CNode
125 | (
126 | instance: CssRenderInstance,
127 | selector: CSelector,
128 | children: CNodeChildren
129 | ): CNode
130 | (instance: CssRenderInstance, children: CNodeChildren): CNode
131 | }
132 |
133 | export interface CssRenderInstance {
134 | context: {
135 | [key: string]: any
136 | }
137 | c: createCNode
138 | use: (plugin: CssRenderPlugin, ...args: any[]) => void
139 | find: CFindTarget
140 | config: CssRenderConfig
141 | }
142 |
143 | export interface CssRenderPlugin {
144 | install: (instance: CssRenderInstance, ...args: any[]) => void
145 | }
146 |
147 | export interface CssRenderConfig {
148 | keepEmptyBlock?: boolean
149 | }
150 |
--------------------------------------------------------------------------------
/packages/css-render/__tests__/parse/pathTestCases.spec.ts:
--------------------------------------------------------------------------------
1 | interface CSelectorTestCase { input: Array, output: string }
2 |
3 | const testCases: CSelectorTestCase[] = [
4 | {
5 | input: ['', '.a', '.b'],
6 | output: '.a .b'
7 | },
8 | {
9 | input: ['.a', '.b'],
10 | output: '.a .b'
11 | },
12 | {
13 | input: ['.a', '&'],
14 | output: '.a'
15 | },
16 | {
17 | input: ['.a', '& + a'],
18 | output: '.a + a'
19 | },
20 | {
21 | input: ['.a', 'b, & c'],
22 | output: '.a b, .a c'
23 | },
24 | {
25 | input: ['.a > b', '&.c, &.d'],
26 | output: '.a > b.c, .a > b.d'
27 | },
28 | {
29 | input: ['.a', '> b', '&.c, &.d', '&.e, > .f'],
30 | output: '.a > b.c.e, .a > b.c > .f, .a > b.d.e, .a > b.d > .f'
31 | },
32 | {
33 | input: ['', '.a', '> b', '&.c, &.d', '&.e, > .f'],
34 | output: '.a > b.c.e, .a > b.c > .f, .a > b.d.e, .a > b.d > .f'
35 | },
36 | {
37 | input: ['.a > b', '&', '&', '&'],
38 | output: '.a > b'
39 | },
40 | {
41 | input: ['', '.a > b', '&', '&', '&'],
42 | output: '.a > b'
43 | },
44 | {
45 | input: ['a, b', 'c, d', 'e, f'],
46 | output: 'a c e, a c f, a d e, a d f, b c e, b c f, b d e, b d f'
47 | },
48 | {
49 | input: ['a, b', '& + c, d', 'e, & + f'],
50 | output: 'a + c e, a + c + f, a d e, a d + f, b + c e, b + c + f, b d e, b d + f'
51 | },
52 | {
53 | input: ['@keyframes good-animation'],
54 | output: '@keyframes good-animation'
55 | },
56 | {
57 | input: ['a, b', '.whatever:is(div, span, p), .freestyle', 'c, d'],
58 | output: 'a .whatever:is(div, span, p) c, a .whatever:is(div, span, p) d, a .freestyle c, a .freestyle d, b .whatever:is(div, span, p) c, b .whatever:is(div, span, p) d, b .freestyle c, b .freestyle d'
59 | },
60 | {
61 | input: ['body', 'pfx &.dark'],
62 | output: 'pfx body.dark'
63 | },
64 | {
65 | input: ['body', 'pfx&.dark'],
66 | output: 'pfxbody.dark'
67 | },
68 | {
69 | input: ['body', '', 'body'],
70 | output: 'body body'
71 | },
72 | {
73 | input: ['body', '', 'pfx &.dark'],
74 | output: 'pfx body.dark'
75 | },
76 | {
77 | input: ['body', null, 'pfx &.dark'],
78 | output: 'pfx body.dark'
79 | },
80 | {
81 | input: ['body', ' ', 'pfx &.dark'],
82 | output: 'pfx body.dark'
83 | },
84 | {
85 | input: [' body ', ' ', ' pfx &.dark '],
86 | output: 'pfx body.dark'
87 | },
88 | {
89 | input: [' a, b ', '', ' & + c, d', ' ', 'e, & + f '],
90 | output: 'a + c e, a + c + f, a d e, a d + f, b + c e, b + c + f, b d e, b d + f'
91 | },
92 | {
93 | input: [' a, b ', '', '', null, ' & + c, d', ' ', 'e, & + f '],
94 | output: 'a + c e, a + c + f, a d e, a d + f, b + c e, b + c + f, b d e, b d + f'
95 | },
96 | {
97 | input: ['a, b', undefined, '& + c, d', null, 'e, & + f'],
98 | output: 'a + c e, a + c + f, a d e, a d + f, b + c e, b + c + f, b d e, b d + f'
99 | },
100 | {
101 | input: ['', '&.a'],
102 | output: '.a'
103 | },
104 | {
105 | input: ['.header', '.menu', '.no-borderradius &'],
106 | output: '.no-borderradius .header .menu'
107 | },
108 | {
109 | input: ['.grand', '.parent', '& > &'],
110 | output: '.grand .parent > .grand .parent'
111 | },
112 | {
113 | input: ['.grand', '.parent', '&&'],
114 | output: '.grand .parent.grand .parent'
115 | },
116 | {
117 | input: ['.grand', '.parent', '&, &ish'],
118 | output: '.grand .parent, .grand .parentish'
119 | },
120 | {
121 | input: ['a, b', '&.c'],
122 | output: 'a.c, b.c'
123 | },
124 | {
125 | input: ['a, b', '& &'],
126 | output: 'a a, a b, b a, b b'
127 | },
128 | {
129 | input: ['p, a, ul, li', '& + &'],
130 | output: 'p + p, p + a, p + ul, p + li, a + p, a + a, a + ul, a + li, ul + p, ul + a, ul + ul, ul + li, li + p, li + a, li + ul, li + li'
131 | },
132 | {
133 | input: ['div', 'span, p, span'],
134 | output: 'div span, div p, div span'
135 | },
136 | {
137 | input: ['a, b, c', 'x'],
138 | output: 'a x, b x, c x'
139 | },
140 | {
141 | input: ['a, b, c', 'x + y'],
142 | output: 'a x + y, b x + y, c x + y'
143 | },
144 | {
145 | input: [' a ', ' & ', ' &b '],
146 | output: 'ab'
147 | },
148 | {
149 | input: [' a', 'b&', ''],
150 | output: 'ba'
151 | }
152 | ]
153 |
154 | export default testCases
155 |
--------------------------------------------------------------------------------
/packages/plugin-bem/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/restrict-template-expressions */
2 | /* eslint-disable @typescript-eslint/strict-boolean-expressions */
3 |
4 | import {
5 | COptionSelector,
6 | CLazySelector,
7 | CStringSelector,
8 | CssRenderPlugin,
9 | createCNode,
10 | CSelector
11 | } from 'css-render'
12 |
13 | interface BEMPluginOptions {
14 | blockPrefix?: string
15 | elementPrefix?: string
16 | modifierPrefix?: string
17 | }
18 |
19 | type AvailableSelector = CStringSelector | CLazySelector
20 |
21 | interface CssRenderBemPlugin extends CssRenderPlugin {
22 | cB: createCNode
23 | cE: createCNode
24 | cM: createCNode
25 | cNotM: createCNode
26 | }
27 |
28 | function plugin (options?: BEMPluginOptions): CssRenderBemPlugin {
29 | let _bPrefix: string = '.'
30 | let _ePrefix: string = '__'
31 | let _mPrefix: string = '--'
32 | let c: createCNode
33 | if (options) {
34 | let t = options.blockPrefix
35 | if (t) {
36 | _bPrefix = t
37 | }
38 | t = options.elementPrefix
39 | if (t) {
40 | _ePrefix = t
41 | }
42 | t = options.modifierPrefix
43 | if (t) {
44 | _mPrefix = t
45 | }
46 | }
47 |
48 | const _plugin: CssRenderPlugin = {
49 | install (instance) {
50 | c = instance.c
51 | const ctx = instance.context
52 | ctx.bem = {}
53 | ctx.bem.b = null
54 | ctx.bem.els = null
55 | }
56 | }
57 |
58 | function b (arg: AvailableSelector): COptionSelector {
59 | let memorizedB: string | null
60 | let memorizedE: string | null
61 | return {
62 | before (ctx) {
63 | memorizedB = ctx.bem.b
64 | memorizedE = ctx.bem.els
65 | ctx.bem.els = null
66 | },
67 | after (ctx) {
68 | ctx.bem.b = memorizedB
69 | ctx.bem.els = memorizedE
70 | },
71 | $ ({ context, props }) {
72 | arg = typeof arg === 'string' ? arg : arg({ context, props })
73 | context.bem.b = arg
74 | return `${props?.bPrefix || _bPrefix}${context.bem.b as string}`
75 | }
76 | }
77 | }
78 |
79 | function e (arg: AvailableSelector): COptionSelector {
80 | let memorizedE: string | null
81 | return {
82 | before (ctx) {
83 | memorizedE = ctx.bem.els
84 | },
85 | after (ctx) {
86 | ctx.bem.els = memorizedE
87 | },
88 | $ ({ context, props }) {
89 | arg = typeof arg === 'string' ? arg : arg({ context, props })
90 | context.bem.els = arg.split(',').map(v => v.trim())
91 | return (context.bem.els as string[])
92 | .map(el => `${props?.bPrefix || _bPrefix}${context.bem.b as string}${_ePrefix}${el}`).join(', ')
93 | }
94 | }
95 | }
96 |
97 | function m (arg: AvailableSelector): COptionSelector {
98 | return {
99 | $ ({ context, props }) {
100 | arg = typeof arg === 'string' ? arg : arg({ context, props })
101 | const modifiers = arg.split(',').map(v => v.trim())
102 | function elementToSelector (el?: string): string {
103 | return modifiers.map(modifier => `&${props?.bPrefix || _bPrefix}${context.bem.b as string}${
104 | el !== undefined ? `${_ePrefix}${el}` : ''
105 | }${_mPrefix}${modifier}`).join(', ')
106 | }
107 | const els = context.bem.els
108 | if (els !== null) {
109 | if (process.env.NODE_ENV !== 'production' && els.length >= 2) {
110 | throw Error(
111 | `[css-render/plugin-bem]: m(${arg}) is invalid, using modifier inside multiple elements is not allowed`
112 | )
113 | }
114 | return elementToSelector(els[0])
115 | } else {
116 | return elementToSelector()
117 | }
118 | }
119 | }
120 | }
121 |
122 | function notM (arg: AvailableSelector): COptionSelector {
123 | return {
124 | $ ({ context, props }) {
125 | arg = typeof arg === 'string' ? arg : arg({ context, props })
126 | const els = context.bem.els as null | string[]
127 | if (process.env.NODE_ENV !== 'production' && els !== null && els.length >= 2) {
128 | throw Error(
129 | `[css-render/plugin-bem]: notM(${arg}) is invalid, using modifier inside multiple elements is not allowed`
130 | )
131 | }
132 | return `&:not(${props?.bPrefix || _bPrefix}${context.bem.b as string}${
133 | (els !== null && els.length > 0) ? `${_ePrefix}${els[0]}` : ''
134 | }${_mPrefix}${arg})`
135 | }
136 | }
137 | }
138 |
139 | const cB = ((...args: any[]) => c(b(args[0]), args[1], args[2])) as createCNode
140 | const cE = ((...args: any[]) => c(e(args[0]), args[1], args[2])) as createCNode
141 | const cM = ((...args: any[]) => c(m(args[0]), args[1], args[2])) as createCNode
142 | const cNotM = ((...args: any[]) => c(notM(args[0]), args[1], args[2])) as createCNode
143 |
144 | Object.assign(_plugin, {
145 | cB, cE, cM, cNotM
146 | })
147 |
148 | return _plugin as CssRenderBemPlugin
149 | }
150 |
151 | export { plugin }
152 | export default plugin
153 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # css-render · [](https://github.com/07akioni/css-render/blob/master/LICENSE) [](https://www.npmjs.com/package/css-render) [](https://lgtm.com/projects/g/07akioni/css-render/alerts/) [](https://codecov.io/gh/07akioni/css-render)
2 |
3 | Generating CSS using JS with considerable flexibility and extensibility, at both server side and client side.
4 |
5 | It's mainly built for **library builders** (who wants make their library work without css import at small overhead). It's not recommend to use it in a webapp.
6 |
7 | It is not designed to totally replace other style-related solutions, but to be a progressive tool which can just work as a supplementary of your style files or totally replace your `.css` files.
8 |
9 | ## Docs
10 | [css-render](https://css-render.vercel.app/)
11 |
12 | ## Why Using It
13 | 1. You want to ship a library without css at a small price (gzip < 2kb).
14 | 2. Reduce size compared with static css (which contains duplicate logic).
15 | 3. You can't write `sass-like` or `less-like` css-in-js (eg. `mixin` in sass or less).
16 | 4. You want to write style variables in JS.
17 | 5. Support an simple SSR API (now only for vue3).
18 |
19 | ## Comparasion with other CSS-in-JS framework
20 |
21 | Main differences between css-render and styled-component, jss or emotion:
22 | 1. It doesn't do the bindings between components and styles. It is more like a style generator with low level mount and unmount API.
23 | 2. It's easier to write like a sass mixin or less mixin.
24 |
25 |
26 | ## Examples
27 | ### Realworld Example
28 | - [DataTable](https://github.com/TuSimple/naive-ui/blob/main/src/data-table/src/styles/index.cssr.ts)
29 | - [XScroll](https://github.com/07akioni/vueuc/blob/main/src/x-scroll/src/index.ts)
30 | - [VirtualList](https://github.com/07akioni/vueuc/blob/main/src/virtual-list/src/VirtualList.ts)
31 |
32 | ### Basic Example
33 | ```bash
34 | $ npm install --save-dev css-render
35 | ```
36 | ```js
37 | import CssRender from 'css-render'
38 | /**
39 | * CommonJS:
40 | * const { CssRender } = require('css-render')
41 | */
42 |
43 | const {
44 | c
45 | } = CssRender()
46 |
47 | const style = c('body', ({ props }) => ({
48 | margin: 0,
49 | backgroundColor: props.backgroundColor
50 | }), [
51 | c('&.dark', {
52 | backgroundColor: 'black'
53 | }),
54 | c('.container', {
55 | width: '100%'
56 | })
57 | ])
58 |
59 | /** use it as string */
60 | console.log(style.render({ backgroundColor: 'white' }))
61 | /**
62 | * or mount on document.head. (the following lines only work in the browser.)
63 | */
64 | style.mount()
65 | // ...
66 | style.unmount()
67 | ```
68 | ```css
69 | body {
70 | margin: 0;
71 | background-color: white;
72 | }
73 |
74 | body.dark {
75 | background-color: black;
76 | }
77 |
78 | body .container {
79 | width: 100%;
80 | }
81 | ```
82 |
83 | ### BEM Plugin Example
84 | ```bash
85 | $ npm install --save-dev css-render @css-render/plugin-bem
86 | ```
87 |
88 | You can use bem plugin to generate bem CSS like this:
89 |
90 | ```js
91 | import CssRender from 'css-render'
92 | import bem from '@css-render/plugin-bem'
93 | /**
94 | * CommonJS:
95 | * const { CssRender } = require('css-render')
96 | * const { plugin: bem } = require('@css-render/plugin-bem')
97 | */
98 |
99 | const cssr = CssRender()
100 | const plugin = bem({
101 | blockPrefix: '.c-'
102 | })
103 | cssr.use(plugin) // bind the plugin with the cssr instance
104 | const {
105 | cB, cE, cM
106 | } = plugin
107 |
108 | const style = cB(
109 | 'container',
110 | [
111 | cE(
112 | 'left, right',
113 | {
114 | width: '50%'
115 | }
116 | ),
117 | cM(
118 | 'dark',
119 | [
120 | cE(
121 | 'left, right',
122 | {
123 | backgroundColor: 'black'
124 | }
125 | )
126 | ]
127 | )
128 | ]
129 | )
130 |
131 | /** use it as string */
132 | console.log(style.render())
133 | /**
134 | * or mount on document.head
135 | * the following lines only works in browser, don't call them in node.js
136 | */
137 | style.mount()
138 | // ...
139 | style.unmount()
140 | ```
141 | ```css
142 | .c-container .c-container__left, .c-container .c-container__right {
143 | width: 50%;
144 | }
145 |
146 | .c-container.c-container--dark .c-container__left, .c-container.c-container--dark .c-container__right {
147 | background-color: black;
148 | }
149 | ```
150 |
151 | ## Packages
152 | |Name|Cov|
153 | |-|-|
154 | |css-render|[](https://codecov.io/gh/07akioni/css-render)|
155 | |@css-render/plugin-bem| [](https://codecov.io/gh/07akioni/css-render)|
156 | |vue3-ssr| [](https://codecov.io/gh/07akioni/css-render)|
--------------------------------------------------------------------------------
/packages/docs/docs/cnode-and-render.md:
--------------------------------------------------------------------------------
1 | # Create a `CNode` & Render a `CNode` Tree
2 | To create a `CNode`, you need to create a [CssRender instance](https://github.com/07akioni/css-render/blob/master/docs/css-render-instance.md) and get the `c` method out of it. For example:
3 | ```js
4 | import CssRender from 'css-render'
5 |
6 | const {
7 | c
8 | } = CssRender() // return a CssRender instance
9 | ```
10 | The `c` method is use to create a `CNode`.
11 | ## `c` method
12 | It can be use in the following forms:
13 | - `c(selector: string | Function | Object | null, cssProps: Object | string | Function)`
14 | - `c(selector: string | Function | Object | null, cssProps: Object | string | Function, children: Array)`
15 | - `c(selector: string | Function | Object | null, children: Array)`
16 | - `c(children: Array)`
17 | Both of them will return a `CNode`.
18 | ### `selector`
19 | #### String `selector`
20 | selector can be any CSS selector, it also support `&` syntax.
21 | For example:
22 | ```js
23 | c('.button', {
24 | color: 'black'
25 | }, [
26 | c('.button__icon', {
27 | fill: 'black'
28 | }),
29 | c('&.button--error', {
30 | color: 'red'
31 | })
32 | ]).render()
33 | ```
34 | ```css
35 | .button {
36 | color: black;
37 | }
38 |
39 | .button .button__icon {
40 | fill: black;
41 | }
42 |
43 | .button.button--error {
44 | color: red;
45 | }
46 | ```
47 | #### Function `selector`
48 | Except `string`, `selector` can be a function that returns a string:
49 | ```js
50 | c(({
51 | context,
52 | props
53 | }) => props.selector, {
54 | color: 'black'
55 | }).render({
56 | selector: '.selector'
57 | })
58 | ```
59 | ```css
60 | .selector {
61 | color: black;
62 | }
63 | ```
64 | - `props` is passed from render function of itself or its ascendant's `CNode`. For example, in `cnode.render({ color: 'black' })`, `{ color: 'black' }` is the `props`.
65 | - `context` is the `context` of `c` method's corresponding `CssRender` instance.
66 |
67 | If you want to know more about `context`, see [CssRender](https://github.com/07akioni/css-render/blob/master/docs/css-render-instance.md).
68 |
69 | #### Object `selector`
70 |
71 | `selector` can also be a object which has more utilities. The object can be typed as
72 | ```typescript
73 | {
74 | $?: (({ context, props }) => string | null) | string | null,
75 | before?: (context) => void,
76 | after?: (context) => void
77 | }
78 | ```
79 | - `$` can be a string selector or a function selector or `null`.
80 | - `before` is the hook before the `CNode` tree is rendered. You can setup the `context` in the hook.
81 | - `after` is the hook after the `CNode` tree is rendered. You can do some cleaning in the hook.
82 |
83 | #### Null `selector`
84 | It will work as if the selector does not exist:
85 | ```js
86 | c('div', [
87 | c(null, [
88 | c('button', {
89 | color: 'black'
90 | })
91 | ])
92 | ]).render()
93 | ```
94 | ```css
95 | div button {
96 | color: black;
97 | }
98 | ```
99 |
100 | ### `cssProps`
101 | #### Object `cssProps` & string `cssProps`
102 | You can put just a plain object or string:
103 | ```js
104 | c('.button', `
105 | background-color: green;
106 | `).render()
107 |
108 | c('@keyframes my-animation', {
109 | from: {
110 | color: 'white'
111 | },
112 | to: {
113 | color: 'black'
114 | }
115 | }).render()
116 | ```
117 | ```css
118 | .button {
119 | background-color: green;
120 | }
121 |
122 | @keyframes my-animation {
123 | from {
124 | color: white;
125 | }
126 | to {
127 | color: black;
128 | }
129 | }
130 | ```
131 | #### Function `cssProps`
132 | If you want to determine the cssProps of a `CNode` at rendering phase. Use a `({ context, props }) => string | Object` typed function as the cssProps of `CNode`.
133 |
134 | ```js
135 | const style = c('.button', ({
136 | context,
137 | props
138 | }) => ({
139 | color: props.color
140 | }))
141 |
142 | style.render({ color: 'red' })
143 |
144 | style.render({ color: 'blue' })
145 | ```
146 | ```css
147 | .button {
148 | color: red;
149 | }
150 |
151 | .button {
152 | color: blue;
153 | }
154 | ```
155 |
156 | Lazy evaluation is good for performance because you can reuse a `CNode` tree rather than rebuild one.
157 |
158 | ### `children`
159 | children is an array of `CNode` and function. If a function is in the array, its need to return a `CNode` or an array of `CNode`.
160 | ```js
161 | c('div', [
162 | // function returns a CNode
163 | ({ context, props }) => c('button', {
164 | color: props.color
165 | }),
166 | // CNode
167 | c('ul', {
168 | backgroundColor: 'red'
169 | }),
170 | // Nested CNode Array
171 | [
172 | [c('dl', {
173 | backgroundColor: 'red'
174 | })]
175 | ],
176 | // function returns a CNode Array
177 | () => [
178 | c('ol', {
179 | backgroundColor: 'red'
180 | })
181 | ]
182 | ]).render({
183 | color: 'black'
184 | })
185 | ```
186 | ```css
187 | div button {
188 | color: black;
189 | }
190 |
191 | div ul {
192 | background-color: red;
193 | }
194 |
195 | div dl {
196 | background-color: red;
197 | }
198 |
199 | div ol {
200 | background-color: red;
201 | }
202 | ```
203 |
204 | Also, you can render a style fragment like this:
205 | ```js
206 | c([
207 | c('.button.button--blue', {
208 | color: 'blue'
209 | }),
210 | c('.button.button--red', {
211 | color: 'red'
212 | })
213 | ]).render()
214 | ```
215 | ```css
216 | .button.button--blue {
217 | color: blue;
218 | }
219 |
220 | .button.button--red {
221 | color: red;
222 | }
223 | ```
224 |
225 | Maybe the only limitation of style generation is your imagination.
--------------------------------------------------------------------------------
/packages/plugin-bem/CHANGELOG.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@css-render/plugin-bem",
3 | "entries": [
4 | {
5 | "version": "0.15.14",
6 | "tag": "@css-render/plugin-bem_v0.15.14",
7 | "date": "Sun, 05 May 2024 06:34:46 GMT",
8 | "comments": {}
9 | },
10 | {
11 | "version": "0.15.13",
12 | "tag": "@css-render/plugin-bem_v0.15.13",
13 | "date": "Sun, 05 May 2024 05:43:33 GMT",
14 | "comments": {}
15 | },
16 | {
17 | "version": "0.15.12",
18 | "tag": "@css-render/plugin-bem_v0.15.12",
19 | "date": "Sat, 24 Dec 2022 17:17:08 GMT",
20 | "comments": {}
21 | },
22 | {
23 | "version": "0.15.11",
24 | "tag": "@css-render/plugin-bem_v0.15.11",
25 | "date": "Sun, 21 Aug 2022 07:56:48 GMT",
26 | "comments": {}
27 | },
28 | {
29 | "version": "0.15.10",
30 | "tag": "@css-render/plugin-bem_v0.15.10",
31 | "date": "Wed, 23 Mar 2022 12:33:17 GMT",
32 | "comments": {}
33 | },
34 | {
35 | "version": "0.15.9",
36 | "tag": "@css-render/plugin-bem_v0.15.9",
37 | "date": "Sat, 05 Mar 2022 14:58:36 GMT",
38 | "comments": {}
39 | },
40 | {
41 | "version": "0.15.8",
42 | "tag": "@css-render/plugin-bem_v0.15.8",
43 | "date": "Tue, 28 Dec 2021 18:59:30 GMT",
44 | "comments": {
45 | "none": [
46 | {
47 | "comment": "fix peer deps"
48 | }
49 | ]
50 | }
51 | },
52 | {
53 | "version": "0.15.7",
54 | "tag": "@css-render/plugin-bem_v0.15.7",
55 | "date": "Tue, 28 Dec 2021 17:54:23 GMT",
56 | "comments": {}
57 | },
58 | {
59 | "version": "0.15.6",
60 | "tag": "@css-render/plugin-bem_v0.15.6",
61 | "date": "Thu, 02 Sep 2021 15:52:34 GMT",
62 | "comments": {}
63 | },
64 | {
65 | "version": "0.15.5",
66 | "tag": "@css-render/plugin-bem_v0.15.5",
67 | "date": "Sat, 24 Jul 2021 17:34:06 GMT",
68 | "comments": {}
69 | },
70 | {
71 | "version": "0.15.4",
72 | "tag": "@css-render/plugin-bem_v0.15.4",
73 | "date": "Sun, 13 Jun 2021 04:56:14 GMT",
74 | "comments": {}
75 | },
76 | {
77 | "version": "0.15.3",
78 | "tag": "@css-render/plugin-bem_v0.15.3",
79 | "date": "Sat, 12 Jun 2021 09:50:10 GMT",
80 | "comments": {}
81 | },
82 | {
83 | "version": "0.15.2",
84 | "tag": "@css-render/plugin-bem_v0.15.2",
85 | "date": "Wed, 02 Jun 2021 04:15:14 GMT",
86 | "comments": {}
87 | },
88 | {
89 | "version": "0.15.1",
90 | "tag": "@css-render/plugin-bem_v0.15.1",
91 | "date": "Tue, 01 Jun 2021 13:35:47 GMT",
92 | "comments": {}
93 | },
94 | {
95 | "version": "0.15.0",
96 | "tag": "@css-render/plugin-bem_v0.15.0",
97 | "date": "Tue, 01 Jun 2021 13:10:25 GMT",
98 | "comments": {}
99 | },
100 | {
101 | "version": "0.14.1",
102 | "tag": "@css-render/plugin-bem_v0.14.1",
103 | "date": "Wed, 19 May 2021 05:06:20 GMT",
104 | "comments": {}
105 | },
106 | {
107 | "version": "0.14.0",
108 | "tag": "@css-render/plugin-bem_v0.14.0",
109 | "date": "Wed, 19 May 2021 05:03:05 GMT",
110 | "comments": {}
111 | },
112 | {
113 | "version": "0.13.9",
114 | "tag": "@css-render/plugin-bem_v0.13.9",
115 | "date": "Sun, 16 May 2021 19:46:15 GMT",
116 | "comments": {}
117 | },
118 | {
119 | "version": "0.13.8",
120 | "tag": "@css-render/plugin-bem_v0.13.8",
121 | "date": "Wed, 12 May 2021 11:45:08 GMT",
122 | "comments": {}
123 | },
124 | {
125 | "version": "0.13.7",
126 | "tag": "@css-render/plugin-bem_v0.13.7",
127 | "date": "Wed, 12 May 2021 11:35:11 GMT",
128 | "comments": {}
129 | },
130 | {
131 | "version": "0.13.6",
132 | "tag": "@css-render/plugin-bem_v0.13.6",
133 | "date": "Wed, 14 Apr 2021 17:53:56 GMT",
134 | "comments": {}
135 | },
136 | {
137 | "version": "0.13.5",
138 | "tag": "@css-render/plugin-bem_v0.13.5",
139 | "date": "Wed, 14 Apr 2021 17:06:44 GMT",
140 | "comments": {
141 | "none": [
142 | {
143 | "comment": "feat(plugin-bem): add bPrefix prop"
144 | }
145 | ]
146 | }
147 | },
148 | {
149 | "version": "0.13.4",
150 | "tag": "@css-render/plugin-bem_v0.13.4",
151 | "date": "Wed, 14 Apr 2021 16:25:11 GMT",
152 | "comments": {}
153 | },
154 | {
155 | "version": "0.13.3",
156 | "tag": "@css-render/plugin-bem_v0.13.3",
157 | "date": "Wed, 14 Apr 2021 16:24:04 GMT",
158 | "comments": {}
159 | },
160 | {
161 | "version": "0.13.2",
162 | "tag": "@css-render/plugin-bem_v0.13.2",
163 | "date": "Fri, 26 Feb 2021 08:24:05 GMT",
164 | "comments": {}
165 | },
166 | {
167 | "version": "0.13.1",
168 | "tag": "@css-render/plugin-bem_v0.13.1",
169 | "date": "Fri, 26 Feb 2021 08:18:28 GMT",
170 | "comments": {
171 | "none": [
172 | {
173 | "comment": "No changes"
174 | }
175 | ]
176 | }
177 | },
178 | {
179 | "version": "0.13.0",
180 | "tag": "@css-render/plugin-bem_v0.13.0",
181 | "date": "Wed, 24 Feb 2021 09:35:28 GMT",
182 | "comments": {
183 | "minor": [
184 | {
185 | "comment": "No changes"
186 | }
187 | ],
188 | "dependency": [
189 | {
190 | "comment": "Updating dependency \"css-render\" from `~0.12.0` to `~0.13.0`"
191 | }
192 | ]
193 | }
194 | }
195 | ]
196 | }
197 |
--------------------------------------------------------------------------------
/packages/css-render/src/render.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/strict-boolean-expressions */
2 | import {
3 | CNode,
4 | CProperties,
5 | CssRenderInstance,
6 | CProperty,
7 | CPlainProperties,
8 | CRenderProps,
9 | CNodeChildren,
10 | CRenderOption,
11 | CSelectorPath
12 | } from './types'
13 | import { parseSelectorPath } from './parse'
14 | import { isMediaOrSupports } from './utils'
15 |
16 | const kebabRegex = /[A-Z]/g
17 |
18 | function kebabCase (pattern: string): string {
19 | return pattern.replace(kebabRegex, match => '-' + match.toLowerCase())
20 | }
21 |
22 | /** TODO: refine it to solve nested object */
23 | function unwrapProperty (
24 | prop: CProperty,
25 | indent: string = ' '
26 | ): string {
27 | if (typeof prop === 'object' && prop !== null) {
28 | return (
29 | ' {\n' +
30 | Object.entries(prop).map(v => {
31 | return indent + ` ${kebabCase(v[0])}: ${v[1] as string};`
32 | }).join('\n') +
33 | '\n' + indent + '}'
34 | )
35 | }
36 | return `: ${prop as string};`
37 | }
38 |
39 | /** unwrap properties */
40 | function unwrapProperties (
41 | props: CProperties,
42 | instance: CssRenderInstance,
43 | params: T
44 | ): CPlainProperties | string | undefined | null {
45 | if (typeof props === 'function') {
46 | return props({
47 | context: instance.context,
48 | props: params
49 | })
50 | }
51 | return props
52 | }
53 |
54 | function createStyle (
55 | selector: string,
56 | props: CProperties,
57 | instance: CssRenderInstance,
58 | params: T
59 | ): string {
60 | if (!props) return ''
61 | // eslint-disable-next-line
62 | const unwrappedProps = unwrapProperties(props, instance, params)
63 | if (!unwrappedProps) return ''
64 | if (typeof unwrappedProps === 'string') {
65 | return `${selector} {\n${unwrappedProps}\n}`
66 | }
67 | const propertyNames = Object.keys(unwrappedProps)
68 | if (propertyNames.length === 0) {
69 | if (instance.config.keepEmptyBlock) return selector + ' {\n}'
70 | return ''
71 | }
72 | const statements = selector
73 | ? [
74 | selector + ' {'
75 | ]
76 | : []
77 | propertyNames.forEach(propertyName => {
78 | const property = unwrappedProps[propertyName]
79 | if (propertyName === 'raw') {
80 | statements.push('\n' + (property as string) + '\n')
81 | return
82 | }
83 | propertyName = kebabCase(propertyName)
84 | if (property !== null && property !== undefined) {
85 | statements.push(` ${propertyName}${unwrapProperty(property)}`)
86 | }
87 | })
88 | if (selector) {
89 | statements.push('}')
90 | }
91 | return statements.join('\n')
92 | }
93 |
94 | function loopCNodeListWithCallback (children: CNodeChildren, options: CRenderOption, callback: (node: CNode | string) => any): void {
95 | /* istanbul ignore if */
96 | if (!children) return
97 | children.forEach(child => {
98 | if (Array.isArray(child)) {
99 | loopCNodeListWithCallback(child, options, callback)
100 | } else if (typeof child === 'function') {
101 | const grandChildren = child(options)
102 | if (Array.isArray(grandChildren)) {
103 | loopCNodeListWithCallback(grandChildren, options, callback)
104 | } else if (grandChildren) {
105 | callback(grandChildren)
106 | }
107 | } else if (child) {
108 | callback(child)
109 | }
110 | })
111 | }
112 |
113 | function traverseCNode (
114 | node: CNode,
115 | selectorPaths: CSelectorPath,
116 | styles: string[],
117 | instance: CssRenderInstance,
118 | params: T
119 | ): void {
120 | const $ = node.$
121 | let blockSelector = ''
122 | if (!$ || typeof $ === 'string') {
123 | if (isMediaOrSupports($)) {
124 | blockSelector = $
125 | } else {
126 | // as a string selector
127 | selectorPaths.push($)
128 | }
129 | } else if (typeof $ === 'function') {
130 | const selector = $({
131 | context: instance.context,
132 | props: params
133 | })
134 | if (isMediaOrSupports(selector)) {
135 | blockSelector = selector
136 | } else {
137 | // as a lazy selector
138 | selectorPaths.push(selector)
139 | }
140 | } else { // as a option selector
141 | if ($.before) $.before(instance.context)
142 | if (!$.$ || typeof $.$ === 'string') {
143 | if (isMediaOrSupports($.$)) {
144 | blockSelector = $.$
145 | } else {
146 | // as a string selector
147 | selectorPaths.push($.$)
148 | }
149 | } else /* istanbul ignore else */ if ($.$) {
150 | const selector = $.$({
151 | context: instance.context,
152 | props: params
153 | })
154 | if (isMediaOrSupports(selector)) {
155 | blockSelector = selector
156 | } else {
157 | // as a lazy selector
158 | selectorPaths.push(selector)
159 | }
160 | }
161 | }
162 | const selector = parseSelectorPath(selectorPaths)
163 | const style = createStyle(selector, node.props, instance, params)
164 | if (blockSelector) {
165 | styles.push(`${blockSelector} {`)
166 | } else if (style.length) {
167 | styles.push(style)
168 | }
169 | if (node.children) {
170 | loopCNodeListWithCallback(node.children, {
171 | context: instance.context,
172 | props: params
173 | }, childNode => {
174 | if (typeof childNode === 'string') {
175 | const style = createStyle(selector, { raw: childNode }, instance, params)
176 | styles.push(style)
177 | } else {
178 | traverseCNode(childNode, selectorPaths, styles, instance, params)
179 | }
180 | })
181 | }
182 | selectorPaths.pop()
183 | if (blockSelector) {
184 | styles.push('}')
185 | }
186 | if ($ && ($ as any).after) ($ as any).after(instance.context)
187 | }
188 |
189 | export function render (
190 | node: CNode,
191 | instance: CssRenderInstance,
192 | props?: T
193 | ): string {
194 | const styles: string[] = []
195 | traverseCNode(
196 | node,
197 | [],
198 | styles,
199 | instance,
200 | props
201 | )
202 | return styles.join('\n\n')
203 | }
204 |
--------------------------------------------------------------------------------
/packages/docs/docs/index.md:
--------------------------------------------------------------------------------
1 | # css-render · [](https://github.com/07akioni/css-render/blob/master/LICENSE) [](https://www.npmjs.com/package/css-render) [](https://lgtm.com/projects/g/07akioni/css-render/alerts/) [](https://codecov.io/gh/07akioni/css-render)
2 |
3 | Generating CSS using JS with considerable flexibility and extensibility, at both server side and client side.
4 |
5 | It's mainly built for **library builders** (who wants make their library work without css import at small overhead). It's not recommend to use it in a webapp.
6 |
7 | It is not designed to totally replace other style-related solutions, but to be a progressive tool which can just work as a supplementary of your style files or totally replace your `.css` files.
8 |
9 | ## Docs
10 | [css-render](https://css-render.vercel.app/)
11 |
12 | ## Why Using It
13 | 1. You want to ship a library without css at a small price (gzip < 2kb).
14 | 2. Reduce size compared with static css (which contains duplicate logic).
15 | 3. You can't write `sass-like` or `less-like` css-in-js (eg. `mixin` in sass or less).
16 | 4. You want to write style variables in JS.
17 | 5. Support an simple SSR API (now only for vue3).
18 |
19 | ## Comparasion with other CSS-in-JS framework
20 |
21 | Main differences between css-render and styled-component, jss or emotion:
22 | 1. It doesn't do the bindings between components and styles. It is more like a style generator with low level mount and unmount API.
23 | 2. It's easier to write like a sass mixin or less mixin.
24 |
25 |
26 | ## Examples
27 | ### Realword Example
28 | - [XScroll](https://github.com/07akioni/vueuc/blob/main/src/x-scroll/src/index.ts)
29 | - [VirtualList](https://github.com/07akioni/vueuc/blob/main/src/virtual-list/src/VirtualList.ts)
30 |
31 | ### Basic Example
32 | ```bash
33 | $ npm install --save-dev css-render
34 | ```
35 | ```js
36 | import CssRender from 'css-render'
37 | /**
38 | * CommonJS:
39 | * const { CssRender } = require('css-render')
40 | */
41 |
42 | const {
43 | c
44 | } = CssRender()
45 |
46 | const style = c('body', ({ props }) => ({
47 | margin: 0,
48 | backgroundColor: props.backgroundColor
49 | }), [
50 | c('&.dark', {
51 | backgroundColor: 'black'
52 | }),
53 | c('.container', {
54 | width: '100%'
55 | })
56 | ])
57 |
58 | /** use it as string */
59 | console.log(style.render({ backgroundColor: 'white' }))
60 | /**
61 | * or mount on document.head. (the following lines only work in the browser.)
62 | */
63 | style.mount()
64 | // ...
65 | style.unmount()
66 | ```
67 | ```css
68 | body {
69 | margin: 0;
70 | background-color: white;
71 | }
72 |
73 | body.dark {
74 | background-color: black;
75 | }
76 |
77 | body .container {
78 | width: 100%;
79 | }
80 | ```
81 |
82 | ### BEM Plugin Example
83 | ```bash
84 | $ npm install --save-dev css-render @css-render/plugin-bem
85 | ```
86 |
87 | You can use bem plugin to generate bem CSS like this:
88 |
89 | ```js
90 | import CssRender from 'css-render'
91 | import bem from '@css-render/plugin-bem'
92 | /**
93 | * CommonJS:
94 | * const { CssRender } = require('css-render')
95 | * const { plugin: bem } = require('@css-render/plugin-bem')
96 | */
97 |
98 | const cssr = CssRender()
99 | const plugin = bem({
100 | blockPrefix: '.c-'
101 | })
102 | cssr.use(plugin) // bind the plugin with the cssr instance
103 | const {
104 | cB, cE, cM
105 | } = plugin
106 |
107 | const style = cB(
108 | 'container',
109 | [
110 | cE(
111 | 'left, right',
112 | {
113 | width: '50%'
114 | }
115 | ),
116 | cM(
117 | 'dark',
118 | [
119 | cE(
120 | 'left, right',
121 | {
122 | backgroundColor: 'black'
123 | }
124 | )
125 | ]
126 | )
127 | ]
128 | )
129 |
130 | /** use it as string */
131 | console.log(style.render())
132 | /**
133 | * or mount on document.head
134 | * the following lines only works in browser, don't call them in node.js
135 | */
136 | style.mount()
137 | // ...
138 | style.unmount()
139 | ```
140 | ```css
141 | .c-container .c-container__left, .c-container .c-container__right {
142 | width: 50%;
143 | }
144 |
145 | .c-container.c-container--dark .c-container__left, .c-container.c-container--dark .c-container__right {
146 | background-color: black;
147 | }
148 | ```
149 |
150 | ## Vue3 SSR
151 | ```bash
152 | $ npm install --save-dev css-render @css-render/vue3-ssr
153 | ```
154 |
155 | To make ssr works, you need to make
156 | ```tsx
157 | import { h, createSSRApp, defineComponent } from 'vue'
158 | import { renderToString } from '@vue/server-renderer'
159 |
160 | import { CssRender } from 'css-render'
161 | import { SsrContext, ssrAdapter } from '@css-render/vue3-ssr'
162 |
163 | const Child = defineComponent({
164 | setup () {
165 | c('div', {
166 | color: 'red'
167 | }).mount({
168 | id: 'mount-id',
169 | // You need to pass the ssrAdapter to `mount` function
170 | // to make ssr work.
171 | // If you want it work with CSR, just set it to undefined
172 | ssr: ssrAdapter
173 | })
174 | },
175 | render () {
176 | return 'Child'
177 | }
178 | })
179 |
180 | const App = defineComponent({
181 | render () {
182 | // Wrap the SsrContext at the root of your app
183 | return h(SsrContext, null, {
184 | default: () => h(Child)
185 | })
186 | }
187 | })
188 |
189 | const app = createSSRApp(App)
190 |
191 | renderToString(app).then(v => { console.log(v) })
192 | ```
193 |
194 | Finally you will find the rendered SSR HTML includes mounted style.
195 |
196 | ## Packages
197 | |Name|Cov|
198 | |-|-|
199 | |css-render|[](https://codecov.io/gh/07akioni/css-render)|
200 | |@css-render/plugin-bem| [](https://codecov.io/gh/07akioni/css-render)|
201 | |vue3-ssr| [](https://codecov.io/gh/07akioni/css-render)|
--------------------------------------------------------------------------------
/packages/css-render/README.md:
--------------------------------------------------------------------------------
1 | # css-render · [](https://github.com/07akioni/css-render/blob/master/LICENSE) [](https://www.npmjs.com/package/css-render) [](https://lgtm.com/projects/g/07akioni/css-render/alerts/) [](https://codecov.io/gh/07akioni/css-render)
2 |
3 | Generating CSS using JS with considerable flexibility and extensibility, at both server side and client side.
4 |
5 | It's mainly built for **library builders** (who wants make their library work without css import at small overhead). It's not recommend to use it in a webapp.
6 |
7 | It is not designed to totally replace other style-related solutions, but to be a progressive tool which can just work as a supplementary of your style files or totally replace your `.css` files.
8 |
9 | ## Docs
10 | [css-render](https://css-render.vercel.app/)
11 |
12 | ## Why Using It
13 | 1. You want to ship a library without css at a small price (gzip < 2kb).
14 | 2. Reduce size compared with static css (which contains duplicate logic).
15 | 3. You can't write `sass-like` or `less-like` css-in-js (eg. `mixin` in sass or less).
16 | 4. You want to write style variables in JS.
17 | 5. Support an simple SSR API (now only for vue3).
18 |
19 | ## Comparasion with other CSS-in-JS framework
20 |
21 | Main differences between css-render and styled-component, jss or emotion:
22 | 1. It doesn't do the bindings between components and styles. It is more like a style generator with low level mount and unmount API.
23 | 2. It's easier to write like a sass mixin or less mixin.
24 |
25 |
26 | ## Examples
27 | ### Realword Example
28 | - [XScroll](https://github.com/07akioni/vueuc/blob/main/src/x-scroll/src/index.ts)
29 | - [VirtualList](https://github.com/07akioni/vueuc/blob/main/src/virtual-list/src/VirtualList.ts)
30 |
31 | ### Basic Example
32 | ```bash
33 | $ npm install --save-dev css-render
34 | ```
35 | ```js
36 | import CssRender from 'css-render'
37 | /**
38 | * CommonJS:
39 | * const { CssRender } = require('css-render')
40 | */
41 |
42 | const {
43 | c
44 | } = CssRender()
45 |
46 | const style = c('body', ({ props }) => ({
47 | margin: 0,
48 | backgroundColor: props.backgroundColor
49 | }), [
50 | c('&.dark', {
51 | backgroundColor: 'black'
52 | }),
53 | c('.container', {
54 | width: '100%'
55 | })
56 | ])
57 |
58 | /** use it as string */
59 | console.log(style.render({ backgroundColor: 'white' }))
60 | /**
61 | * or mount on document.head. (the following lines only work in the browser.)
62 | */
63 | style.mount()
64 | // ...
65 | style.unmount()
66 | ```
67 | ```css
68 | body {
69 | margin: 0;
70 | background-color: white;
71 | }
72 |
73 | body.dark {
74 | background-color: black;
75 | }
76 |
77 | body .container {
78 | width: 100%;
79 | }
80 | ```
81 |
82 | ### BEM Plugin Example
83 | ```bash
84 | $ npm install --save-dev css-render @css-render/plugin-bem
85 | ```
86 |
87 | You can use bem plugin to generate bem CSS like this:
88 |
89 | ```js
90 | import CssRender from 'css-render'
91 | import bem from '@css-render/plugin-bem'
92 | /**
93 | * CommonJS:
94 | * const { CssRender } = require('css-render')
95 | * const { plugin: bem } = require('@css-render/plugin-bem')
96 | */
97 |
98 | const cssr = CssRender()
99 | const plugin = bem({
100 | blockPrefix: '.c-'
101 | })
102 | cssr.use(plugin) // bind the plugin with the cssr instance
103 | const {
104 | cB, cE, cM
105 | } = plugin
106 |
107 | const style = cB(
108 | 'container',
109 | [
110 | cE(
111 | 'left, right',
112 | {
113 | width: '50%'
114 | }
115 | ),
116 | cM(
117 | 'dark',
118 | [
119 | cE(
120 | 'left, right',
121 | {
122 | backgroundColor: 'black'
123 | }
124 | )
125 | ]
126 | )
127 | ]
128 | )
129 |
130 | /** use it as string */
131 | console.log(style.render())
132 | /**
133 | * or mount on document.head
134 | * the following lines only works in browser, don't call them in node.js
135 | */
136 | style.mount()
137 | // ...
138 | style.unmount()
139 | ```
140 | ```css
141 | .c-container .c-container__left, .c-container .c-container__right {
142 | width: 50%;
143 | }
144 |
145 | .c-container.c-container--dark .c-container__left, .c-container.c-container--dark .c-container__right {
146 | background-color: black;
147 | }
148 | ```
149 |
150 | ## Vue3 SSR
151 | ```bash
152 | $ npm install --save-dev css-render @css-render/vue3-ssr
153 | ```
154 |
155 | To make ssr works, you need to make
156 | ```tsx
157 | import { h, createSSRApp, defineComponent } from 'vue'
158 | import { renderToString } from '@vue/server-renderer'
159 |
160 | import { CssRender } from 'css-render'
161 | import { SsrContext, ssrAdapter } from '@css-render/vue3-ssr'
162 |
163 | const Child = defineComponent({
164 | setup () {
165 | c('div', {
166 | color: 'red'
167 | }).mount({
168 | id: 'mount-id',
169 | // You need to pass the ssrAdapter to `mount` function
170 | // to make ssr work.
171 | // If you want it work with CSR, just set it to undefined
172 | ssr: ssrAdapter
173 | })
174 | },
175 | render () {
176 | return 'Child'
177 | }
178 | })
179 |
180 | const App = defineComponent({
181 | render () {
182 | // Wrap the SsrContext at the root of your app
183 | return h(SsrContext, null, {
184 | default: () => h(Child)
185 | })
186 | }
187 | })
188 |
189 | const app = createSSRApp(App)
190 |
191 | renderToString(app).then(v => { console.log(v) })
192 | ```
193 |
194 | Finally you will find the rendered SSR HTML includes mounted style.
195 |
196 | ## Packages
197 | |Name|Cov|
198 | |-|-|
199 | |css-render|[](https://codecov.io/gh/07akioni/css-render)|
200 | |@css-render/plugin-bem| [](https://codecov.io/gh/07akioni/css-render)|
201 | |vue3-ssr| [](https://codecov.io/gh/07akioni/css-render)|
--------------------------------------------------------------------------------
/packages/vue3-ssr/CHANGELOG.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@css-render/vue3-ssr",
3 | "entries": [
4 | {
5 | "version": "0.15.14",
6 | "tag": "@css-render/vue3-ssr_v0.15.14",
7 | "date": "Sun, 05 May 2024 06:34:46 GMT",
8 | "comments": {}
9 | },
10 | {
11 | "version": "0.15.13",
12 | "tag": "@css-render/vue3-ssr_v0.15.13",
13 | "date": "Sun, 05 May 2024 05:43:33 GMT",
14 | "comments": {}
15 | },
16 | {
17 | "version": "0.15.12",
18 | "tag": "@css-render/vue3-ssr_v0.15.12",
19 | "date": "Sat, 24 Dec 2022 17:17:08 GMT",
20 | "comments": {}
21 | },
22 | {
23 | "version": "0.15.11",
24 | "tag": "@css-render/vue3-ssr_v0.15.11",
25 | "date": "Sun, 21 Aug 2022 07:56:48 GMT",
26 | "comments": {}
27 | },
28 | {
29 | "version": "0.15.10",
30 | "tag": "@css-render/vue3-ssr_v0.15.10",
31 | "date": "Wed, 23 Mar 2022 12:33:17 GMT",
32 | "comments": {
33 | "none": [
34 | {
35 | "comment": "Fix useSsrAdapter may return non-undefined value in browser."
36 | }
37 | ]
38 | }
39 | },
40 | {
41 | "version": "0.15.9",
42 | "tag": "@css-render/vue3-ssr_v0.15.9",
43 | "date": "Sat, 05 Mar 2022 14:58:36 GMT",
44 | "comments": {}
45 | },
46 | {
47 | "version": "0.15.8",
48 | "tag": "@css-render/vue3-ssr_v0.15.8",
49 | "date": "Tue, 28 Dec 2021 18:59:30 GMT",
50 | "comments": {}
51 | },
52 | {
53 | "version": "0.15.7",
54 | "tag": "@css-render/vue3-ssr_v0.15.7",
55 | "date": "Tue, 28 Dec 2021 17:54:23 GMT",
56 | "comments": {}
57 | },
58 | {
59 | "version": "0.15.6",
60 | "tag": "@css-render/vue3-ssr_v0.15.6",
61 | "date": "Thu, 02 Sep 2021 15:52:34 GMT",
62 | "comments": {}
63 | },
64 | {
65 | "version": "0.15.5",
66 | "tag": "@css-render/vue3-ssr_v0.15.5",
67 | "date": "Sat, 24 Jul 2021 17:34:06 GMT",
68 | "comments": {
69 | "none": [
70 | {
71 | "comment": "Update readme"
72 | }
73 | ]
74 | }
75 | },
76 | {
77 | "version": "0.15.4",
78 | "tag": "@css-render/vue3-ssr_v0.15.4",
79 | "date": "Sun, 13 Jun 2021 04:56:14 GMT",
80 | "comments": {
81 | "none": [
82 | {
83 | "comment": "(breaking) Remove `ssrAdapter` exports"
84 | }
85 | ]
86 | }
87 | },
88 | {
89 | "version": "0.15.3",
90 | "tag": "@css-render/vue3-ssr_v0.15.3",
91 | "date": "Sat, 12 Jun 2021 09:50:10 GMT",
92 | "comments": {
93 | "none": [
94 | {
95 | "comment": "Add useSsrAdapter hook"
96 | }
97 | ]
98 | }
99 | },
100 | {
101 | "version": "0.15.2",
102 | "tag": "@css-render/vue3-ssr_v0.15.2",
103 | "date": "Wed, 02 Jun 2021 04:15:14 GMT",
104 | "comments": {}
105 | },
106 | {
107 | "version": "0.15.1",
108 | "tag": "@css-render/vue3-ssr_v0.15.1",
109 | "date": "Tue, 01 Jun 2021 13:35:47 GMT",
110 | "comments": {
111 | "none": [
112 | {
113 | "comment": "no main & module in pkg.json"
114 | }
115 | ]
116 | }
117 | },
118 | {
119 | "version": "0.15.0",
120 | "tag": "@css-render/vue3-ssr_v0.15.0",
121 | "date": "Tue, 01 Jun 2021 13:10:25 GMT",
122 | "comments": {
123 | "none": [
124 | {
125 | "comment": "support stream ssr"
126 | }
127 | ]
128 | }
129 | },
130 | {
131 | "version": "0.14.1",
132 | "tag": "@css-render/vue3-ssr_v0.14.1",
133 | "date": "Wed, 19 May 2021 05:06:20 GMT",
134 | "comments": {}
135 | },
136 | {
137 | "version": "0.14.0",
138 | "tag": "@css-render/vue3-ssr_v0.14.0",
139 | "date": "Wed, 19 May 2021 05:03:05 GMT",
140 | "comments": {}
141 | },
142 | {
143 | "version": "0.13.9",
144 | "tag": "@css-render/vue3-ssr_v0.13.9",
145 | "date": "Sun, 16 May 2021 19:46:15 GMT",
146 | "comments": {}
147 | },
148 | {
149 | "version": "0.13.8",
150 | "tag": "@css-render/vue3-ssr_v0.13.8",
151 | "date": "Wed, 12 May 2021 11:45:08 GMT",
152 | "comments": {}
153 | },
154 | {
155 | "version": "0.13.7",
156 | "tag": "@css-render/vue3-ssr_v0.13.7",
157 | "date": "Wed, 12 May 2021 11:35:11 GMT",
158 | "comments": {}
159 | },
160 | {
161 | "version": "0.13.6",
162 | "tag": "@css-render/vue3-ssr_v0.13.6",
163 | "date": "Wed, 14 Apr 2021 17:53:56 GMT",
164 | "comments": {}
165 | },
166 | {
167 | "version": "0.13.5",
168 | "tag": "@css-render/vue3-ssr_v0.13.5",
169 | "date": "Wed, 14 Apr 2021 17:06:44 GMT",
170 | "comments": {
171 | "none": [
172 | {
173 | "comment": "feat(plugin-bem): add bPrefix prop"
174 | }
175 | ]
176 | }
177 | },
178 | {
179 | "version": "0.13.4",
180 | "tag": "@css-render/vue3-ssr_v0.13.4",
181 | "date": "Wed, 14 Apr 2021 16:25:11 GMT",
182 | "comments": {}
183 | },
184 | {
185 | "version": "0.13.3",
186 | "tag": "@css-render/vue3-ssr_v0.13.3",
187 | "date": "Wed, 14 Apr 2021 16:24:04 GMT",
188 | "comments": {}
189 | },
190 | {
191 | "version": "0.13.2",
192 | "tag": "@css-render/vue3-ssr_v0.13.2",
193 | "date": "Fri, 26 Feb 2021 08:24:05 GMT",
194 | "comments": {}
195 | },
196 | {
197 | "version": "0.13.1",
198 | "tag": "@css-render/vue3-ssr_v0.13.1",
199 | "date": "Fri, 26 Feb 2021 08:18:28 GMT",
200 | "comments": {
201 | "none": [
202 | {
203 | "comment": "No changes"
204 | }
205 | ]
206 | }
207 | },
208 | {
209 | "version": "0.13.0",
210 | "tag": "@css-render/vue3-ssr_v0.13.0",
211 | "date": "Wed, 24 Feb 2021 09:35:28 GMT",
212 | "comments": {
213 | "minor": [
214 | {
215 | "comment": "Add SsrContext component & ssrAdapter"
216 | }
217 | ],
218 | "dependency": [
219 | {
220 | "comment": "Updating dependency \"css-render\" from `~0.12.0` to `~0.13.0`"
221 | }
222 | ]
223 | }
224 | }
225 | ]
226 | }
227 |
--------------------------------------------------------------------------------
/packages/css-render/CHANGELOG.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "css-render",
3 | "entries": [
4 | {
5 | "version": "0.15.14",
6 | "tag": "css-render_v0.15.14",
7 | "date": "Sun, 05 May 2024 06:34:46 GMT",
8 | "comments": {}
9 | },
10 | {
11 | "version": "0.15.13",
12 | "tag": "css-render_v0.15.13",
13 | "date": "Sun, 05 May 2024 05:43:33 GMT",
14 | "comments": {}
15 | },
16 | {
17 | "version": "0.15.12",
18 | "tag": "css-render_v0.15.12",
19 | "date": "Sat, 24 Dec 2022 17:17:08 GMT",
20 | "comments": {}
21 | },
22 | {
23 | "version": "0.15.11",
24 | "tag": "css-render_v0.15.11",
25 | "date": "Sun, 21 Aug 2022 07:56:48 GMT",
26 | "comments": {
27 | "none": [
28 | {
29 | "comment": "Limit scope of style id check"
30 | }
31 | ]
32 | }
33 | },
34 | {
35 | "version": "0.15.10",
36 | "tag": "css-render_v0.15.10",
37 | "date": "Wed, 23 Mar 2022 12:33:17 GMT",
38 | "comments": {}
39 | },
40 | {
41 | "version": "0.15.9",
42 | "tag": "css-render_v0.15.9",
43 | "date": "Sat, 05 Mar 2022 14:58:36 GMT",
44 | "comments": {}
45 | },
46 | {
47 | "version": "0.15.8",
48 | "tag": "css-render_v0.15.8",
49 | "date": "Tue, 28 Dec 2021 18:59:30 GMT",
50 | "comments": {}
51 | },
52 | {
53 | "version": "0.15.7",
54 | "tag": "css-render_v0.15.7",
55 | "date": "Tue, 28 Dec 2021 17:54:23 GMT",
56 | "comments": {
57 | "none": [
58 | {
59 | "comment": "support metaAnchorName on mount"
60 | }
61 | ]
62 | }
63 | },
64 | {
65 | "version": "0.15.6",
66 | "tag": "css-render_v0.15.6",
67 | "date": "Thu, 02 Sep 2021 15:52:34 GMT",
68 | "comments": {
69 | "none": [
70 | {
71 | "comment": "fix: mount before link element when head is true"
72 | }
73 | ]
74 | }
75 | },
76 | {
77 | "version": "0.15.5",
78 | "tag": "css-render_v0.15.5",
79 | "date": "Sat, 24 Jul 2021 17:34:06 GMT",
80 | "comments": {}
81 | },
82 | {
83 | "version": "0.15.4",
84 | "tag": "css-render_v0.15.4",
85 | "date": "Sun, 13 Jun 2021 04:56:14 GMT",
86 | "comments": {
87 | "none": [
88 | {
89 | "comment": "Add `exists` function to check if style is mounted"
90 | }
91 | ]
92 | }
93 | },
94 | {
95 | "version": "0.15.3",
96 | "tag": "css-render_v0.15.3",
97 | "date": "Sat, 12 Jun 2021 09:50:10 GMT",
98 | "comments": {}
99 | },
100 | {
101 | "version": "0.15.2",
102 | "tag": "css-render_v0.15.2",
103 | "date": "Wed, 02 Jun 2021 04:15:14 GMT",
104 | "comments": {
105 | "none": [
106 | {
107 | "comment": "fix throwing error in node env"
108 | }
109 | ]
110 | }
111 | },
112 | {
113 | "version": "0.15.1",
114 | "tag": "css-render_v0.15.1",
115 | "date": "Tue, 01 Jun 2021 13:35:47 GMT",
116 | "comments": {}
117 | },
118 | {
119 | "version": "0.15.0",
120 | "tag": "css-render_v0.15.0",
121 | "date": "Tue, 01 Jun 2021 13:10:25 GMT",
122 | "comments": {}
123 | },
124 | {
125 | "version": "0.14.1",
126 | "tag": "css-render_v0.14.1",
127 | "date": "Wed, 19 May 2021 05:06:20 GMT",
128 | "comments": {}
129 | },
130 | {
131 | "version": "0.14.0",
132 | "tag": "css-render_v0.14.0",
133 | "date": "Wed, 19 May 2021 05:03:05 GMT",
134 | "comments": {
135 | "none": [
136 | {
137 | "comment": "Remove count prop in mount option & unmount option. Rename boost prop in mount option to silent"
138 | }
139 | ]
140 | }
141 | },
142 | {
143 | "version": "0.13.9",
144 | "tag": "css-render_v0.13.9",
145 | "date": "Sun, 16 May 2021 19:46:15 GMT",
146 | "comments": {
147 | "none": [
148 | {
149 | "comment": "add option.force for mount"
150 | }
151 | ]
152 | }
153 | },
154 | {
155 | "version": "0.13.8",
156 | "tag": "css-render_v0.13.8",
157 | "date": "Wed, 12 May 2021 11:45:08 GMT",
158 | "comments": {
159 | "none": [
160 | {
161 | "comment": "Fix render multiple times when mount with same id"
162 | }
163 | ]
164 | }
165 | },
166 | {
167 | "version": "0.13.7",
168 | "tag": "css-render_v0.13.7",
169 | "date": "Wed, 12 May 2021 11:35:11 GMT",
170 | "comments": {}
171 | },
172 | {
173 | "version": "0.13.6",
174 | "tag": "css-render_v0.13.6",
175 | "date": "Wed, 14 Apr 2021 17:53:56 GMT",
176 | "comments": {}
177 | },
178 | {
179 | "version": "0.13.5",
180 | "tag": "css-render_v0.13.5",
181 | "date": "Wed, 14 Apr 2021 17:06:44 GMT",
182 | "comments": {
183 | "none": [
184 | {
185 | "comment": "feat(plugin-bem): add bPrefix prop"
186 | }
187 | ]
188 | }
189 | },
190 | {
191 | "version": "0.13.4",
192 | "tag": "css-render_v0.13.4",
193 | "date": "Wed, 14 Apr 2021 16:25:11 GMT",
194 | "comments": {}
195 | },
196 | {
197 | "version": "0.13.3",
198 | "tag": "css-render_v0.13.3",
199 | "date": "Wed, 14 Apr 2021 16:24:04 GMT",
200 | "comments": {
201 | "none": [
202 | {
203 | "comment": "feat(mount): add `head` option to insert style before every style node in doc.head"
204 | }
205 | ]
206 | }
207 | },
208 | {
209 | "version": "0.13.2",
210 | "tag": "css-render_v0.13.2",
211 | "date": "Fri, 26 Feb 2021 08:24:05 GMT",
212 | "comments": {}
213 | },
214 | {
215 | "version": "0.13.1",
216 | "tag": "css-render_v0.13.1",
217 | "date": "Fri, 26 Feb 2021 08:18:28 GMT",
218 | "comments": {
219 | "none": [
220 | {
221 | "comment": "Deprecate MountOptions.count & UnmountOptions.count"
222 | }
223 | ]
224 | }
225 | },
226 | {
227 | "version": "0.13.0",
228 | "tag": "css-render_v0.13.0",
229 | "date": "Wed, 24 Feb 2021 09:35:28 GMT",
230 | "comments": {
231 | "minor": [
232 | {
233 | "comment": "Support ssrAdapter in mount"
234 | }
235 | ]
236 | }
237 | }
238 | ]
239 | }
240 |
--------------------------------------------------------------------------------
/packages/plugin-bem/__tests__/index.spec.ts:
--------------------------------------------------------------------------------
1 | import CssRender from 'css-render'
2 | import { assertEqual } from '@css-render/test-shared'
3 | import CssRenderBEMPlugin from '../index'
4 |
5 | const cssr = CssRender({
6 | keepEmptyBlock: true
7 | })
8 | const plugin = CssRenderBEMPlugin({
9 | blockPrefix: '.c-',
10 | elementPrefix: '__',
11 | modifierPrefix: '--'
12 | })
13 | cssr.use(plugin)
14 |
15 | const { c } = cssr
16 |
17 | const {
18 | cB,
19 | cE,
20 | cM,
21 | cNotM
22 | } = plugin
23 |
24 | describe('default prefixes', function () {
25 | it('should use default prefixes', function () {
26 | const plugin = CssRenderBEMPlugin({})
27 | cssr.use(plugin)
28 | const {
29 | cB,
30 | cE,
31 | cM,
32 | cNotM
33 | } = plugin
34 | assertEqual(
35 | cB('b', [
36 | cM('m', {}),
37 | cNotM('nm', {}),
38 | cE('e', [
39 | cM('m', {}),
40 | cNotM('nm', {})
41 | ])
42 | ]).render(),
43 | `
44 | .b.b--m {}
45 | .b:not(.b--nm) {}
46 | .b .b__e.b__e--m {}
47 | .b .b__e:not(.b__e--nm) {}
48 | `
49 | )
50 | })
51 | it('should use default prefixes', function () {
52 | const plugin = CssRenderBEMPlugin()
53 | cssr.use(plugin)
54 | const {
55 | cB,
56 | cE,
57 | cM,
58 | cNotM
59 | } = plugin
60 | assertEqual(
61 | cB('b', [
62 | cM('m', {}),
63 | cNotM('nm', {}),
64 | cE('e', [
65 | cM('m', {}),
66 | cNotM('nm', {})
67 | ])
68 | ]).render(),
69 | `
70 | .b.b--m {}
71 | .b:not(.b--nm) {}
72 | .b .b__e.b__e--m {}
73 | .b .b__e:not(.b__e--nm) {}
74 | `
75 | )
76 | })
77 | })
78 |
79 | describe('# bem.b', function () {
80 | it('should use blockPrefix', function () {
81 | assertEqual(
82 | cB('b', {}).render(),
83 | '.c-b {}'
84 | )
85 | })
86 | it('should generate correct selector when nested', function () {
87 | assertEqual(
88 | cB(
89 | 'b',
90 | [cB('b2', {})]
91 | ).render(),
92 | '.c-b .c-b2 {}'
93 | )
94 | })
95 | })
96 |
97 | describe('# bem.e', function () {
98 | it('should work with bem.b', function () {
99 | assertEqual(
100 | cB('b', [cE('e', {})]).render(),
101 | '.c-b .c-b__e {}'
102 | )
103 | })
104 | it('should work with comma selector', function () {
105 | assertEqual(
106 | cB('b', [cE('e1, e2', {})]).render(),
107 | '.c-b .c-b__e1, .c-b .c-b__e2 {}'
108 | )
109 | })
110 | it('should work with nested e', function () {
111 | assertEqual(
112 | cB('b', [
113 | cE('e1', {}, [
114 | cM('m1', {}),
115 | cE('e2', {}, [
116 | cM('m2', {})
117 | ])
118 | ])
119 | ]).render(),
120 | `
121 | .c-b .c-b__e1 {}
122 | .c-b .c-b__e1.c-b__e1--m1 {}
123 | .c-b .c-b__e1 .c-b__e2 {}
124 | .c-b .c-b__e1 .c-b__e2.c-b__e2--m2 {}
125 | `
126 | )
127 | assertEqual(
128 | cB('b', [
129 | cE('e1', [
130 | c('& +', [
131 | cE('e2', [
132 | cM('m2', {})
133 | ])
134 | ])
135 | ])
136 | ]).render(),
137 | `
138 | .c-b .c-b__e1 + .c-b__e2.c-b__e2--m2 {}
139 | `
140 | )
141 | })
142 | })
143 |
144 | describe('# bem.m', function () {
145 | it('should work with bem.b', function () {
146 | assertEqual(
147 | cB('b', [cM('m', {})]).render(),
148 | '.c-b.c-b--m {}'
149 | )
150 | })
151 | it('should work with bem.e', function () {
152 | assertEqual(
153 | cB('b', [cE('e', [cM('m', {})])]).render(),
154 | '.c-b .c-b__e.c-b__e--m {}'
155 | )
156 | })
157 | it('should work with comma selector', function () {
158 | assertEqual(
159 | cB('b', [cM('m1, m2', {})]).render(),
160 | '.c-b.c-b--m1, .c-b.c-b--m2 {}'
161 | )
162 | assertEqual(
163 | cB('b', [cE('e', [cM('m1, m2', {})])]).render(),
164 | '.c-b .c-b__e.c-b__e--m1, .c-b .c-b__e.c-b__e--m2 {}'
165 | )
166 | })
167 | })
168 |
169 | describe('# bem.notM', function () {
170 | it('should work with bem.b', function () {
171 | assertEqual(
172 | cB('b', [cNotM('m', {})]).render(),
173 | '.c-b:not(.c-b--m) {}'
174 | )
175 | })
176 | it('should work with bem.e', function () {
177 | assertEqual(
178 | cB('b', [cE('e', [cNotM('m', {})])]).render(),
179 | '.c-b .c-b__e:not(.c-b__e--m) {}'
180 | )
181 | })
182 | })
183 |
184 | describe('# bem', function () {
185 | it('should pass test case#1', function () {
186 | assertEqual(
187 | cB(
188 | 'container',
189 | [
190 | cE(
191 | 'left, right',
192 | {
193 | width: '50%'
194 | }
195 | ),
196 | cM(
197 | 'dark',
198 | [
199 | cE(
200 | 'left, right',
201 | {
202 | backgroundColor: 'black'
203 | }
204 | )
205 | ]
206 | )
207 | ]
208 | ).render(),
209 | `.c-container .c-container__left, .c-container .c-container__right {
210 | width: 50%;
211 | }
212 |
213 | .c-container.c-container--dark .c-container__left,
214 | .c-container.c-container--dark .c-container__right {
215 | background-color: black;
216 | }`
217 | )
218 | })
219 | })
220 |
221 | describe('# custom bPrefix', function () {
222 | const plugin = CssRenderBEMPlugin()
223 | cssr.use(plugin)
224 | const {
225 | cB,
226 | cE,
227 | cM,
228 | cNotM
229 | } = plugin
230 | assertEqual(
231 | cB('b', [
232 | cM('m', {}),
233 | cNotM('nm', {}),
234 | cE('e', [
235 | cM('m', {}),
236 | cNotM('nm', {})
237 | ])
238 | ]).render({
239 | bPrefix: '.x-'
240 | }),
241 | `
242 | .x-b.x-b--m {}
243 | .x-b:not(.x-b--nm) {}
244 | .x-b .x-b__e.x-b__e--m {}
245 | .x-b .x-b__e:not(.x-b__e--nm) {}
246 | `
247 | )
248 | })
249 |
250 | describe('# function typed selector', function () {
251 | it('should pass test case#1', function () {
252 | assertEqual(
253 | cB(({ props }) => props.value as string + 'block', {
254 | key: 'value'
255 | }, [
256 | cE(({ props }) => props.value as string + 'el', {
257 | key: 'value'
258 | }, [
259 | cM(({ props }) => props.value as string + 'm', {
260 | key: 'value'
261 | }),
262 | cNotM(({ props }) => props.value as string + 'nm', {
263 | key: 'value'
264 | })
265 | ])
266 | ]).render({
267 | value: 'propValue'
268 | }),
269 | `
270 | .c-propValueblock {
271 | key: value;
272 | }
273 |
274 | .c-propValueblock .c-propValueblock__propValueel {
275 | key: value;
276 | }
277 |
278 | .c-propValueblock .c-propValueblock__propValueel.c-propValueblock__propValueel--propValuem {
279 | key: value;
280 | }
281 |
282 | .c-propValueblock .c-propValueblock__propValueel:not(.c-propValueblock__propValueel--propValuenm) {
283 | key: value;
284 | }
285 | `)
286 | })
287 | })
288 |
289 | describe('error info', () => {
290 | describe('shouldn\'t allow using modifier inside multiple elements', () => {
291 | it('#case m', (done) => {
292 | try {
293 | cB('b', [cE('e, e', [cM('m', {})])]).render()
294 | } catch (e) {
295 | done()
296 | }
297 | })
298 | it('#case not m', (done) => {
299 | try {
300 | cB('b', [cE('e, e', [cNotM('m', {})])]).render()
301 | } catch (e) {
302 | done()
303 | }
304 | })
305 | })
306 | })
307 |
--------------------------------------------------------------------------------
/packages/css-render/__tests__/mount/index.spec.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */
2 | import * as chai from 'chai'
3 | import CssRender, { exists } from '../../src'
4 | import { SinonSpy, spy } from 'sinon'
5 |
6 | const expect = chai.expect
7 | const cssr = CssRender()
8 |
9 | const { c } = cssr
10 |
11 | describe('# mount with no id', () => {
12 | let sandbox: HTMLElement
13 | const style = c('.red-block', {
14 | backgroundColor: 'red'
15 | })
16 | before(() => {
17 | style.mount()
18 | sandbox = document.createElement('div')
19 | document.body.appendChild(sandbox)
20 | })
21 | beforeEach(() => {
22 | spy(console, 'error')
23 | })
24 | afterEach(() => {
25 | (console.error as SinonSpy).restore()
26 | sandbox.innerHTML = ''
27 | })
28 | it('should mount only once', () => {
29 | expect(document.querySelectorAll('[cssr-id]').length).to.equal(1)
30 | style.mount()
31 | expect(document.querySelectorAll('[cssr-id]').length).to.equal(1)
32 | })
33 | it('should make element styled', () => {
34 | sandbox.innerHTML = ''
35 | const backgroundColor = getComputedStyle(
36 | sandbox.children[0]
37 | ).backgroundColor
38 | expect(backgroundColor).to.equal('rgb(255, 0, 0)')
39 | })
40 | it('should support props of render', () => {
41 | sandbox.innerHTML = ''
42 | const style = c('sel1', ({ props }) => {
43 | return {
44 | backgroundColor: props.color
45 | }
46 | })
47 | style.mount({
48 | props: {
49 | color: 'red'
50 | }
51 | })
52 | const backgroundColor = getComputedStyle(
53 | sandbox.children[0]
54 | ).backgroundColor
55 | expect(backgroundColor).to.equal('rgb(255, 0, 0)')
56 | })
57 | after(() => {
58 | document.body.removeChild(sandbox)
59 | style.unmount()
60 | })
61 | })
62 |
63 | describe('# mount & unmount with id (suite 1)', () => {
64 | const style = c('.red-block', {
65 | backgroundColor: 'red'
66 | })
67 | before(() => {
68 | style.mount({
69 | id: 'test-id-1'
70 | })
71 | style.mount({
72 | id: 'test-id-2'
73 | })
74 | style.mount({
75 | id: '14138'
76 | })
77 | style.mount({
78 | id: '14139'
79 | })
80 | })
81 | beforeEach(() => {
82 | spy(console, 'error')
83 | })
84 | afterEach(() => {
85 | (console.error as SinonSpy).restore()
86 | })
87 | it('should work in no-count mode', () => {
88 | expect(exists('jjy')).to.eq(false)
89 | style.mount({ id: 'jjy' })
90 | expect(exists('jjy')).to.eq(true)
91 | expect(document.head.querySelector('[cssr-id="jjy"]')).not.to.eq(null)
92 | style.unmount({ id: 'jjy' })
93 | expect(document.head.querySelector('[cssr-id="jjy"]')).to.eq(null)
94 | })
95 | it('should mount desired style element on head', () => {
96 | expect(document.head.querySelector('[cssr-id="test-id-1"]')).not.to.equal(
97 | null
98 | )
99 | expect(document.head.querySelector('[cssr-id="14138"]')).not.to.equal(null)
100 | })
101 | it('should do nothing when unmount with an not exist id', () => {
102 | style.unmount({
103 | id: 'letitbe'
104 | })
105 | expect(style.els.length).to.equal(4)
106 | })
107 | it('should unmount the desired style element', () => {
108 | style.unmount({
109 | id: 'test-id-1'
110 | })
111 | style.unmount({
112 | id: '14138'
113 | })
114 | expect(document.head.querySelector('[cssr-id="test-id-1"]')).to.equal(null)
115 | expect(document.head.querySelector('[cssr-id="test-id-2"]')).not.to.equal(
116 | null
117 | )
118 | expect(document.head.querySelector('[cssr-id="14138"]')).to.equal(null)
119 | expect(document.head.querySelector('[cssr-id="14139"]')).not.to.equal(null)
120 | expect(style.els.length).to.equal(2)
121 | })
122 | it('should unmount all related styles elements', () => {
123 | style.unmount()
124 | expect(style.els.length).to.equal(0)
125 | })
126 | after(() => {
127 | style.unmount()
128 | })
129 | })
130 |
131 | describe('# mount & unmount with id (suite 2)', function () {
132 | const style = c('.red-block', {
133 | backgroundColor: 'red'
134 | })
135 | beforeEach(() => {
136 | spy(console, 'error')
137 | })
138 | afterEach(() => {
139 | (console.error as SinonSpy).restore()
140 | style.unmount()
141 | })
142 | it('should mount same element with same id', function () {
143 | const el = style.mount({
144 | id: '14141'
145 | })
146 | const el2 = style.mount({
147 | id: '14141'
148 | })
149 | expect(el).to.equal(el2)
150 | })
151 | })
152 |
153 | describe('head', () => {
154 | const oldStyle = document.createElement('style')
155 | oldStyle.textContent = '.old-style { color: red; }'
156 | document.head.appendChild(oldStyle)
157 | const style = c('.old-style', 'color: blue;')
158 | const el = document.createElement('div')
159 | el.classList.add('old-style')
160 | document.body.appendChild(el)
161 | it("doesn't affect old style when mount with `head`", () => {
162 | style.mount({ id: 'head-test-head', head: true })
163 | expect(getComputedStyle(document.querySelector('.old-style')!).color).to.eq(
164 | 'rgb(255, 0, 0)'
165 | )
166 | style.mount({ id: 'head-test-no-head' })
167 | expect(getComputedStyle(document.querySelector('.old-style')!).color).to.eq(
168 | 'rgb(0, 0, 255)'
169 | )
170 | })
171 | it('mount before link element', () => {
172 | document.head.insertBefore(
173 | document.createElement('link'),
174 | document.head.firstElementChild
175 | )
176 | style.mount({ id: 'gogogo', head: true })
177 | expect(document.head.firstElementChild?.getAttribute('cssr-id')).to.equal(
178 | 'gogogo'
179 | )
180 | })
181 | })
182 |
183 | describe('force', () => {
184 | it('works', () => {
185 | const divA = document.createElement('div')
186 | divA.classList.add('a')
187 | document.body.appendChild(divA)
188 | const style = c('.a', ({ props }) => ({
189 | color: props.color
190 | }))
191 | style.mount({
192 | id: 'force',
193 | props: {
194 | color: 'red'
195 | },
196 | force: true
197 | })
198 | expect(getComputedStyle(document.querySelector('.a')!).color).to.equal(
199 | 'rgb(255, 0, 0)'
200 | )
201 | style.mount({
202 | id: 'force',
203 | props: {
204 | color: 'rgb(0, 255, 0)'
205 | },
206 | force: true
207 | })
208 | expect(getComputedStyle(document.querySelector('.a')!).color).to.equal(
209 | 'rgb(0, 255, 0)'
210 | )
211 | })
212 | })
213 |
214 | describe('anchorMetaName', () => {
215 | const metaA = document.createElement('meta')
216 | metaA.name = 'metaA'
217 | const metaB = document.createElement('meta')
218 | metaB.name = 'metaB'
219 | const metaC = document.createElement('meta')
220 | metaC.name = 'metaC'
221 | document.head.appendChild(metaA)
222 | document.head.appendChild(metaB)
223 | document.head.appendChild(metaC)
224 |
225 | const style = c('.a', { color: 'green' })
226 | style.mount({
227 | id: 'gigigi',
228 | anchorMetaName: 'metaB'
229 | })
230 | expect(document.head.querySelector('[cssr-id="gigigi"] + *')?.getAttribute('name')).to.equal('metaB')
231 | style.mount({
232 | id: 'gigigi2',
233 | anchorMetaName: 'metaB'
234 | })
235 | expect(document.head.querySelector('[cssr-id="gigigi"] + *')?.getAttribute('cssr-id')).to.equal('gigigi2')
236 | expect(document.querySelector('[cssr-id="gigigi2"] + *')?.getAttribute('name')).to.equal('metaB')
237 | style.unmount()
238 | console.log(document.head)
239 | })
240 |
241 | describe('shadow root', () => {
242 | it('style mounted on head and shadow root not affect each other', () => {
243 | const outerDiv = document.createElement('div')
244 | outerDiv.classList.add('a')
245 | document.body.appendChild(outerDiv)
246 | const innerDiv = outerDiv.cloneNode(true)
247 | const shadow = outerDiv.attachShadow({mode: 'open'})
248 | shadow.appendChild(innerDiv)
249 |
250 | const style = c('.a', ({ props }) => ({
251 | backgroundColor: props.backgroundColor
252 | }))
253 | style.mount({
254 | id: 'outer',
255 | props: {
256 | backgroundColor: 'red'
257 | }
258 | })
259 | style.mount({
260 | id: 'inner',
261 | props: {
262 | backgroundColor: 'lime'
263 | },
264 | parent: shadow
265 | })
266 | expect(getComputedStyle(document.querySelector('.a')!).backgroundColor).to.equal(
267 | 'rgb(255, 0, 0)'
268 | )
269 | expect(getComputedStyle(shadow.querySelector('.a')!).backgroundColor).to.equal(
270 | 'rgb(0, 255, 0)'
271 | )
272 |
273 | style.unmount({
274 | id: 'outer'
275 | })
276 | style.unmount({
277 | id: 'inner',
278 | parent: shadow
279 | })
280 | expect(style.els.length).to.equal(0)
281 | })
282 | })
283 |
--------------------------------------------------------------------------------
/playground/perf.log:
--------------------------------------------------------------------------------
1 | -----log-start-----
2 | Commit hash : d13ce297b5a013812a0174e098cd792bb77be915
3 | Raw logs : 13.05,13.09,12.22,14.46,12,13.37,11.8,13.32,13.23,11.99,12.01,13.04,11.9,13.27,12.96,11.81,13.28,14.3,13.31,11.62,11.69,11.72,12.92,13.72,12.06,13.22,11.88,11.95,13.04,11.71,12.43,11.97,11.85,12.95,11.91,13.16,12.87,13.99,14.14,14.99,12.74,11.81,11.89,11.83,12.24,12.11,11.83,12.01,12.87,11.96,12.95,14.96,13.01,11.85,11.87,13.08,12.89,12.4,11.89,11.96,15.83,11.93,11.73,11.89,12.53,13.15,11.76,12.98,12.2,11.85,11.8,12.23,13.3,12.41,13.07,12.01,12.32,13.07,12.3,11.85,12.52,11.78,12.03,12.17,12.19,12.16,11.89,12.91,13.19,15.43,20.87,20.61,19.91,11.98,11.86,13.08,12.59,15.94,15.39,16.9,
4 | Max time : 20.87
5 | Min time : 11.62
6 | Average time : 12.7190
7 | ------log-end------
8 |
9 | -----log-start-----
10 | Commit hash : d13ce297b5a013812a0174e098cd792bb77be915
11 | Raw logs : 17.4,16.04,11.75,13.72,12.12,12,12.75,12.33,12.01,11.71,12.96,12.03,11.74,13.18,11.87,11.81,11.73,11.87,11.67,12.03,11.77,13.03,11.73,13.14,15.32,11.75,11.74,14.19,15.52,12.33,13.08,13.03,11.8,12.9,11.91,12.42,13.31,12,13.2,13.27,11.68,12.95,13.41,11.9,11.86,13.1,11.96,13.01,13.02,12.22,12.42,14.61,13.01,12.9,12.61,11.72,11.82,11.8,12.24,13.1,13.1,11.72,11.72,11.8,16.17,13.31,12.89,12.88,11.92,12.84,12.01,11.61,11.81,11.74,11.52,12.1,11.92,12.67,11.69,11.75,12.04,11.78,11.78,13.02,12.31,13,12.01,12.92,11.7,11.72,11.92,14.68,13.05,13.08,12.89,11.76,11.73,11.97,12,11.68,
12 | Max time : 17.4
13 | Min time : 11.52
14 | Average time : 12.4959
15 | ------log-end------
16 |
17 | -----log-start-----
18 | Commit hash : d13ce297b5a013812a0174e098cd792bb77be915
19 | Raw logs : 13.34,13.34,12.08,13.56,12.14,13.36,12.11,13.55,14.61,15.77,14.06,12.82,11.98,11.63,11.64,11.9,14.34,12.21,13.15,12.36,12.03,12.72,12.01,12.78,12.81,14.06,11.97,12.48,13.21,11.58,15.06,13.02,13.07,11.82,11.61,11.7,11.89,16.42,13.88,12.61,16.55,12.98,13.95,13.3,12.56,18.28,13.24,11.95,12.23,12.12,12.49,13.4,12,12.06,13.27,12.22,12.69,12.36,12.51,13.24,13.34,13.12,12.3,11.98,11.96,12.07,11.93,13.56,14.67,13.26,12.12,13.5,12.17,13.56,13.03,12.28,12.11,15.02,12.54,13.11,15.15,14.2,14.06,12.64,12.7,13.19,12.91,12.1,13.09,12.12,12.31,12.45,13.1,11.93,13.82,12.1,12.04,13.22,11.89,11.86,
20 | Max time : 18.28
21 | Min time : 11.58
22 | Average time : 12.9137
23 | ------log-end------
24 |
25 | -----log-start-----
26 | Commit hash : 4587d662c924ab3b7e6ff196c38f96cb9981fdc6
27 | Raw logs : 13.68,12.64,11.82,11.88,12.93,11.89,12.22,14.38,14.17,11.83,11.63,11.78,11.76,11.74,12.12,11.69,11.83,12.97,12.9,14.84,13.19,13.02,12.67,11.86,11.82,14.49,11.82,11.7,12.06,12.81,11.66,12.92,11.84,12.26,11.85,12.63,11.65,11.92,13,11.62,13.08,11.91,11.77,12.36,13,12.97,11.71,11.66,11.83,16.1,11.71,12.78,11.89,11.74,13.08,11.96,14.82,12.86,12.2,11.78,11.67,11.87,12.22,12.26,12.37,11.63,11.8,11.6,11.74,11.72,11.68,11.78,11.75,12.91,12.99,11.97,11.8,12.93,11.8,11.81,13.1,11.87,11.83,13.03,11.91,11.78,11.86,11.68,11.72,13.54,11.76,13.02,11.7,13.13,11.76,11.78,12.95,12.95,11.64,13.02,
28 | Max time : 16.1
29 | Min time : 11.6
30 | Average time : 12.3491
31 | ------log-end------
32 |
33 | -----log-start-----
34 | Commit hash : 4587d662c924ab3b7e6ff196c38f96cb9981fdc6
35 | Raw logs : 12.35,11.66,11.62,11.96,11.63,11.63,12.05,12.25,13.56,11.63,11.88,11.82,11.84,12.81,12.04,12.75,11.37,12.1,12,11.7,16.44,11.93,11.75,16.73,11.63,11.92,11.78,12.91,11.74,11.54,13.55,14.29,11.64,13.77,11.69,11.69,12.87,11.58,12.07,11.79,11.74,12.56,11.61,11.58,12.88,11.69,13.08,11.62,12.74,11.65,11.78,12.81,12.14,12.45,12.96,11.87,12.77,12.57,14.7,13.08,12.13,12.66,12.06,12.27,12.09,11.89,11.89,11.84,12.51,12.9,11.77,11.67,11.71,12.85,11.59,11.98,11.83,11.8,12.87,12.17,11.9,11.8,11.64,12.86,11.93,11.61,11.96,12.84,11.92,12.7,12.94,12.2,12.87,11.67,11.93,12.82,11.85,11.65,11.66,11.92,
36 | Max time : 16.73
37 | Min time : 11.37
38 | Average time : 12.2639
39 | ------log-end------
40 |
41 | -----log-start-----
42 | Commit hash : 4587d662c924ab3b7e6ff196c38f96cb9981fdc6
43 | Raw logs : 11.95,13.04,13.43,11.99,12.06,11.98,13.24,12.88,11.98,11.82,11.74,11.75,12.99,12.94,11.79,13.13,12.97,11.62,12.81,11.69,12.56,12.76,11.49,12.11,13.14,11.41,12.15,14.26,12.11,12.73,13.01,12.82,11.78,11.78,12.88,12.08,13.03,12.12,11.63,12.39,13.07,11.72,13.1,11.75,11.88,11.66,11.61,11.71,11.59,13.54,12.81,12.06,11.65,12.55,13.46,11.82,13.23,16.98,11.75,13.21,11.67,11.68,11.76,11.87,11.57,13.21,11.73,11.56,12.65,12.9,12.25,11.83,12.88,12.67,11.76,11.71,11.71,15.09,19.94,17.7,14.85,14.56,11.66,11.72,11.86,14.01,15.71,12.83,12.92,12.8,11.61,13.23,11.94,11.62,12.66,11.74,14.25,11.65,11.74,11.77,
44 | Max time : 19.94
45 | Min time : 11.41
46 | Average time : 12.4879
47 | ------log-end------
48 |
49 | -----log-start-----
50 | Commit hash : 4587d662c924ab3b7e6ff196c38f96cb9981fdc6
51 | Raw logs : 12.02,12.06,12.79,11.6,13.04,12.81,11.67,12.1,12.79,11.73,11.56,11.81,12.93,13.08,13.14,11.71,12.95,11.66,11.93,11.72,11.65,11.7,11.81,11.73,12.82,11.74,11.87,11.77,12.84,11.77,11.56,11.77,16.92,11.7,11.72,11.8,12.01,12.28,14.22,17.08,11.75,11.79,11.84,11.76,11.63,15.38,14.94,13.02,11.81,11.99,11.83,11.82,12.12,13.3,11.63,11.69,11.64,11.9,11.72,12.95,11.72,12.35,11.76,12.11,12.83,11.61,11.61,11.81,12.6,12.8,11.64,11.79,11.73,12.81,11.98,12.79,14.16,14.12,12.88,12.81,11.6,12.81,11.7,11.72,11.74,12.9,11.94,11.7,11.72,11.71,11.81,13.45,11.74,11.77,11.68,11.87,11.64,11.64,11.83,11.7,
52 | Max time : 17.08
53 | Min time : 11.56
54 | Average time : 12.2225
55 | ------log-end------
56 |
57 | -----log-start-----
58 | Commit hash : 67d49f0b5b8e0724e075553d1f2dad88d8142882
59 | Raw logs : 11.75,11.8,11.78,12.38,13.05,12.45,22.71,18.03,15.13,11.75,11.52,13.33,11.4,12.12,12.02,13.56,15.51,13.82,13.36,12.39,12.24,12.14,12.97,13.4,12.3,13.06,12.15,13.14,13.3,11.67,14.6,13.63,11.82,11.76,13.16,12.31,12.25,12.18,11.64,11.81,13.28,12.59,11.72,11.73,13.83,14.88,16.63,14.64,12.46,14.67,14.84,11.79,11.52,11.68,12.12,12.22,13.7,12.84,11.81,12.22,13.05,11.47,11.74,11.46,11.71,12.7,12.8,11.54,11.78,13.29,11.81,12.26,12.12,11.69,11.84,12.53,13.46,15.66,12.08,11.81,13.69,13.71,13.04,12.54,11.44,12.2,11.57,12.67,11.92,11.92,12.02,13.59,11.93,11.97,11.81,12.83,11.86,13.62,12.61,13.51,
60 | Max time : 22.71
61 | Min time : 11.4
62 | Average time : 12.6694
63 | ------log-end------
64 |
65 | -----log-start-----
66 | Commit hash : 67d49f0b5b8e0724e075553d1f2dad88d8142882
67 | Raw logs : 12.21,12.03,12.44,11.36,12.78,11.49,11.55,12.56,11.66,10.99,11.78,12.36,10.99,11.09,11.71,12.17,11.48,11.52,11.09,11.09,12.51,11.2,11.15,12.06,11.47,12.42,10.98,10.99,11.03,12.23,12.38,11.2,11.49,11.39,11.38,11.59,11.19,11.23,11.64,11.48,11.42,11.79,11.27,12.45,11.55,11.38,12.66,11.57,11.26,11.43,11.58,11.33,11.37,11.84,11.25,11.7,12.13,11.39,11.2,11.41,11.86,11.57,12.56,11.51,13.2,11.67,11.23,11.6,11.54,11.68,11.32,12.47,11.65,11.32,12.52,12.52,11.4,11.64,11.52,12.29,12.46,12.76,11.26,11.45,12.35,11.42,11.2,12.53,11.41,11.85,11.52,11.62,12.78,11.47,12.11,12.38,11.35,12.7,11.52,11.43,
68 | Max time : 13.2
69 | Min time : 10.98
70 | Average time : 11.8191
71 | ------log-end------
72 |
73 | -----log-start-----
74 | Commit hash : 67d49f0b5b8e0724e075553d1f2dad88d8142882
75 | Raw logs : 11.49,12.84,12.3,11.78,12.63,11.68,12.06,11.8,12.42,11.87,12.82,11.69,19.59,22.09,20.77,12.56,11.67,13.8,11.63,11.51,19.09,17.85,19.68,18.47,11.58,12.7,11.4,11.52,11.34,11.32,11.43,12.88,11.33,11.62,11.72,12.5,12.73,11.66,12.65,12.51,12.56,12.78,11.57,12.14,11.52,11.38,12.57,12.01,11.39,11.45,12.34,11.5,12.84,11.85,11.74,13.46,12.67,11.59,11.49,11.42,12.24,12.89,12.55,11.34,11.37,12.53,11.24,12.07,11.57,11.65,12.62,11.51,11.46,11.35,12.41,12.65,11.45,11.25,12.52,11.5,11.5,11.4,11.34,11.5,12.56,11.33,11.58,12.83,11.72,11.52,12.72,12.01,11.55,11.55,12.85,11.32,11.29,11.7,11.37,11.46,
76 | Max time : 22.09
77 | Min time : 11.24
78 | Average time : 12.1134
79 | ------log-end------
80 |
81 | -----log-start-----
82 | Commit hash : 67d49f0b5b8e0724e075553d1f2dad88d8142882
83 | Raw logs : 12.53,11.81,12.66,12.65,11.69,11.7,13.25,13.29,12.37,12.65,12.63,11.55,11.44,11.51,12.23,11.43,11.45,11.52,11.73,12.85,11.57,11.53,11.6,11.49,11.36,12.46,13.22,11.33,11.78,11.42,12.72,11.45,12.36,11.19,11.5,11.41,11.68,11.53,11.36,12.31,11.5,11.66,12.36,11.81,13.57,12.57,11.42,11.54,13.79,11.81,11.66,12.58,12.25,11.35,13.49,12.76,11.26,11.63,11.41,11.62,11.86,12.58,11.51,12.6,11.21,12.48,11.38,11.37,11.95,11.97,11.26,12.58,11.54,11.57,12.55,11.31,11.73,11.44,11.99,12.97,11.6,11.99,11.77,11.25,11.24,12.79,12.54,11.48,12.54,11.7,11.47,11.45,11.6,11.36,11.42,11.34,12.38,11.47,11.33,14.96,
84 | Max time : 14.96
85 | Min time : 11.19
86 | Average time : 12.0028
87 | ------log-end------
88 |
89 | -----log-start-----
90 | Commit hash : 67d49f0b5b8e0724e075553d1f2dad88d8142882
91 | Raw logs : 12.54,11.29,12.52,12.56,12.57,13.17,12.49,11.68,11.42,12.12,11.44,11.28,11.5,11.49,11.66,11.59,11.28,12.61,12.56,11.3,11.36,11.46,11.1,12.6,12.61,12.25,11.85,11.55,11.13,14.54,11.47,12.57,11.45,11.43,12.72,11.33,11.58,11.45,12.52,11.24,11.38,11.36,11.12,11.63,12.57,11.36,12.77,11.3,11.8,12.5,11.25,14.48,11.68,11.49,11.33,12.44,11.57,11.37,12.31,11.63,12.52,11.25,12.75,12.54,11.44,12.24,11.51,12.87,11.34,12.16,11.68,12.45,12.55,11.27,11.32,12.31,11.45,11.25,12.42,11.27,12.24,11.85,11.31,11.26,13.89,11.38,11.4,11.47,11.77,11.33,12.54,12.12,11.48,11.34,12.4,11.48,11.4,11.39,11.94,11.34,
92 | Max time : 14.54
93 | Min time : 11.1
94 | Average time : 11.9407
95 | ------log-end------
96 |
97 |
--------------------------------------------------------------------------------
/common/scripts/install-run-rush.js:
--------------------------------------------------------------------------------
1 | // THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
2 | //
3 | // This script is intended for usage in an automated build environment where the Rush command may not have
4 | // been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
5 | // specified in the rush.json configuration file (if not already installed), and then pass a command-line to it.
6 | // An example usage would be:
7 | //
8 | // node common/scripts/install-run-rush.js install
9 | //
10 | // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
11 |
12 | /******/ (() => { // webpackBootstrap
13 | /******/ "use strict";
14 | /******/ var __webpack_modules__ = ({
15 |
16 | /***/ 657147:
17 | /*!*********************!*\
18 | !*** external "fs" ***!
19 | \*********************/
20 | /***/ ((module) => {
21 |
22 | module.exports = require("fs");
23 |
24 | /***/ }),
25 |
26 | /***/ 371017:
27 | /*!***********************!*\
28 | !*** external "path" ***!
29 | \***********************/
30 | /***/ ((module) => {
31 |
32 | module.exports = require("path");
33 |
34 | /***/ })
35 |
36 | /******/ });
37 | /************************************************************************/
38 | /******/ // The module cache
39 | /******/ var __webpack_module_cache__ = {};
40 | /******/
41 | /******/ // The require function
42 | /******/ function __webpack_require__(moduleId) {
43 | /******/ // Check if module is in cache
44 | /******/ var cachedModule = __webpack_module_cache__[moduleId];
45 | /******/ if (cachedModule !== undefined) {
46 | /******/ return cachedModule.exports;
47 | /******/ }
48 | /******/ // Create a new module (and put it into the cache)
49 | /******/ var module = __webpack_module_cache__[moduleId] = {
50 | /******/ // no module.id needed
51 | /******/ // no module.loaded needed
52 | /******/ exports: {}
53 | /******/ };
54 | /******/
55 | /******/ // Execute the module function
56 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
57 | /******/
58 | /******/ // Return the exports of the module
59 | /******/ return module.exports;
60 | /******/ }
61 | /******/
62 | /************************************************************************/
63 | /******/ /* webpack/runtime/compat get default export */
64 | /******/ (() => {
65 | /******/ // getDefaultExport function for compatibility with non-harmony modules
66 | /******/ __webpack_require__.n = (module) => {
67 | /******/ var getter = module && module.__esModule ?
68 | /******/ () => (module['default']) :
69 | /******/ () => (module);
70 | /******/ __webpack_require__.d(getter, { a: getter });
71 | /******/ return getter;
72 | /******/ };
73 | /******/ })();
74 | /******/
75 | /******/ /* webpack/runtime/define property getters */
76 | /******/ (() => {
77 | /******/ // define getter functions for harmony exports
78 | /******/ __webpack_require__.d = (exports, definition) => {
79 | /******/ for(var key in definition) {
80 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
81 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
82 | /******/ }
83 | /******/ }
84 | /******/ };
85 | /******/ })();
86 | /******/
87 | /******/ /* webpack/runtime/hasOwnProperty shorthand */
88 | /******/ (() => {
89 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
90 | /******/ })();
91 | /******/
92 | /******/ /* webpack/runtime/make namespace object */
93 | /******/ (() => {
94 | /******/ // define __esModule on exports
95 | /******/ __webpack_require__.r = (exports) => {
96 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
97 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
98 | /******/ }
99 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
100 | /******/ };
101 | /******/ })();
102 | /******/
103 | /************************************************************************/
104 | var __webpack_exports__ = {};
105 | // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
106 | (() => {
107 | /*!************************************************!*\
108 | !*** ./lib-esnext/scripts/install-run-rush.js ***!
109 | \************************************************/
110 | __webpack_require__.r(__webpack_exports__);
111 | /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! path */ 371017);
112 | /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__);
113 | /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! fs */ 657147);
114 | /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__);
115 | // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
116 | // See the @microsoft/rush package's LICENSE file for license information.
117 |
118 |
119 | const { installAndRun, findRushJsonFolder, RUSH_JSON_FILENAME, runWithErrorAndStatusCode } = require('./install-run');
120 | const PACKAGE_NAME = '@microsoft/rush';
121 | const RUSH_PREVIEW_VERSION = 'RUSH_PREVIEW_VERSION';
122 | const INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE = 'INSTALL_RUN_RUSH_LOCKFILE_PATH';
123 | function _getRushVersion(logger) {
124 | const rushPreviewVersion = process.env[RUSH_PREVIEW_VERSION];
125 | if (rushPreviewVersion !== undefined) {
126 | logger.info(`Using Rush version from environment variable ${RUSH_PREVIEW_VERSION}=${rushPreviewVersion}`);
127 | return rushPreviewVersion;
128 | }
129 | const rushJsonFolder = findRushJsonFolder();
130 | const rushJsonPath = path__WEBPACK_IMPORTED_MODULE_0__.join(rushJsonFolder, RUSH_JSON_FILENAME);
131 | try {
132 | const rushJsonContents = fs__WEBPACK_IMPORTED_MODULE_1__.readFileSync(rushJsonPath, 'utf-8');
133 | // Use a regular expression to parse out the rushVersion value because rush.json supports comments,
134 | // but JSON.parse does not and we don't want to pull in more dependencies than we need to in this script.
135 | const rushJsonMatches = rushJsonContents.match(/\"rushVersion\"\s*\:\s*\"([0-9a-zA-Z.+\-]+)\"/);
136 | return rushJsonMatches[1];
137 | }
138 | catch (e) {
139 | throw new Error(`Unable to determine the required version of Rush from rush.json (${rushJsonFolder}). ` +
140 | "The 'rushVersion' field is either not assigned in rush.json or was specified " +
141 | 'using an unexpected syntax.');
142 | }
143 | }
144 | function _run() {
145 | const [nodePath /* Ex: /bin/node */, scriptPath /* /repo/common/scripts/install-run-rush.js */, ...packageBinArgs /* [build, --to, myproject] */] = process.argv;
146 | // Detect if this script was directly invoked, or if the install-run-rushx script was invokved to select the
147 | // appropriate binary inside the rush package to run
148 | const scriptName = path__WEBPACK_IMPORTED_MODULE_0__.basename(scriptPath);
149 | const bin = scriptName.toLowerCase() === 'install-run-rushx.js' ? 'rushx' : 'rush';
150 | if (!nodePath || !scriptPath) {
151 | throw new Error('Unexpected exception: could not detect node path or script path');
152 | }
153 | let commandFound = false;
154 | let logger = { info: console.log, error: console.error };
155 | for (const arg of packageBinArgs) {
156 | if (arg === '-q' || arg === '--quiet') {
157 | // The -q/--quiet flag is supported by both `rush` and `rushx`, and will suppress
158 | // any normal informational/diagnostic information printed during startup.
159 | //
160 | // To maintain the same user experience, the install-run* scripts pass along this
161 | // flag but also use it to suppress any diagnostic information normally printed
162 | // to stdout.
163 | logger = {
164 | info: () => { },
165 | error: console.error
166 | };
167 | }
168 | else if (!arg.startsWith('-') || arg === '-h' || arg === '--help') {
169 | // We either found something that looks like a command (i.e. - doesn't start with a "-"),
170 | // or we found the -h/--help flag, which can be run without a command
171 | commandFound = true;
172 | }
173 | }
174 | if (!commandFound) {
175 | console.log(`Usage: ${scriptName} [args...]`);
176 | if (scriptName === 'install-run-rush.js') {
177 | console.log(`Example: ${scriptName} build --to myproject`);
178 | }
179 | else {
180 | console.log(`Example: ${scriptName} custom-command`);
181 | }
182 | process.exit(1);
183 | }
184 | runWithErrorAndStatusCode(logger, () => {
185 | const version = _getRushVersion(logger);
186 | logger.info(`The rush.json configuration requests Rush version ${version}`);
187 | const lockFilePath = process.env[INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE];
188 | if (lockFilePath) {
189 | logger.info(`Found ${INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE}="${lockFilePath}", installing with lockfile.`);
190 | }
191 | return installAndRun(logger, PACKAGE_NAME, version, bin, packageBinArgs, lockFilePath);
192 | });
193 | }
194 | _run();
195 | //# sourceMappingURL=install-run-rush.js.map
196 | })();
197 |
198 | module.exports = __webpack_exports__;
199 | /******/ })()
200 | ;
201 | //# sourceMappingURL=install-run-rush.js.map
--------------------------------------------------------------------------------
/playground/button.js:
--------------------------------------------------------------------------------
1 | const { CssRender } = require('css-render')
2 | const { plugin } = require('@css-render/plugin-bem')
3 |
4 | const cssr = CssRender()
5 | const bemPlugin = plugin({
6 | blockPrefix: '.n-'
7 | })
8 | const { c } = cssr
9 | cssr.use(bemPlugin)
10 | const {
11 | cB, cE, cM, cNotM
12 | } = bemPlugin
13 |
14 | const buttonType = {
15 | default: 'default-type',
16 | primary: 'primary-type',
17 | info: 'info-type',
18 | success: 'success-type',
19 | warning: 'warning-type',
20 | error: 'error-type'
21 | }
22 |
23 | const buttonSize = {
24 | small: 'small-size',
25 | medium: 'medium-size',
26 | large: 'large-size'
27 | }
28 |
29 | const {
30 | performance
31 | } = require('perf_hooks')
32 |
33 | const start = performance.now()
34 |
35 | function buttonSizeMixin (size) {
36 | const bs = buttonSize[size]
37 | return (
38 | cM(size + '-size', {
39 | borderRadius: bs,
40 | fontSize: bs,
41 | whiteSpace: 'nowrap'
42 | }, [
43 | cNotM('text', {
44 | height: bs,
45 | lineHeight: bs,
46 | padding: bs
47 | }),
48 | cM('round', {
49 | padding: bs,
50 | borderRadius: bs
51 | }),
52 | cM('circle', {
53 | width: bs,
54 | borderRadius: bs,
55 | padding: '0 !important'
56 | }),
57 | cM('text', {
58 | padding: 0,
59 | borderRadius: 0
60 | }, [
61 | cE('icon', {
62 | height: bs,
63 | lineHeight: bs
64 | })
65 | ]),
66 | cE('content', {
67 | display: 'inline-block',
68 | lineHeight: 'inherit'
69 | }),
70 | cE('icon', {
71 | display: 'inline-block',
72 | position: 'relative',
73 | lineHeight: bs,
74 | height: bs,
75 | width: bs,
76 | maxWidth: bs,
77 | verticalAlign: 'bottom'
78 | }, [
79 | cB('icon', {
80 | fontSize: bs
81 | }),
82 | cB('base-loading', {
83 | height: bs,
84 | width: bs,
85 | raw: `
86 | position: absolute;
87 | left: 0;
88 | top: 50%;
89 | transform: translateY(-50%);
90 | display: block;
91 | `
92 | }),
93 | cM('slot', {
94 | width: bs,
95 | fontSize: bs,
96 | raw: `
97 | display: inline-block;
98 | align-items: center;
99 | vertical-align: bottom;
100 | `
101 | }, [
102 | cB('icon-slot', {
103 | raw: `
104 | position: absolute;
105 | left: 0;
106 | top: 50%;
107 | transform: translateY(-50%);
108 | display: block;
109 | `,
110 | lineHeight: bs,
111 | height: bs,
112 | width: bs,
113 | fontSize: bs
114 | })
115 | ])
116 | ])
117 | ])
118 | )
119 | }
120 |
121 | function buttonTypeMixin (type) {
122 | const bt = buttonType[type]
123 | const commonStyle = {
124 | backgroundColor: bt,
125 | color: bt
126 | }
127 | const borderStyle = {
128 | border: bt
129 | }
130 | const boxShadowStyle = {
131 | boxShadow: bt
132 | }
133 | const borderMaskMixin = cE('border-mask', {
134 | ...boxShadowStyle
135 | })
136 | const iconMixin = cE('icon', [
137 | cB('icon', {
138 | fill: bt,
139 | stroke: bt
140 | }),
141 | cB('base-loading', {
142 | fill: bt,
143 | stroke: bt
144 | })
145 | ])
146 | return cM(type + '-type', {
147 | ...borderStyle,
148 | ...commonStyle
149 | }, [
150 | cM('ghost, text', [
151 | cNotM('disabled', [
152 | cM('enter-pressed', {
153 | ...commonStyle
154 | }, [
155 | borderMaskMixin,
156 | iconMixin
157 | ]),
158 | c('&:not(:active):focus', [
159 | cNotM('enter-pressed', {
160 | ...commonStyle
161 | }, [
162 | borderMaskMixin,
163 | iconMixin
164 | ])
165 | ]),
166 | cNotM('enter-pressed', [
167 | c('&:hover', {
168 | ...commonStyle
169 | }, [
170 | borderMaskMixin,
171 | iconMixin
172 | ]),
173 | c('&:active', {
174 | ...commonStyle
175 | }, [
176 | borderMaskMixin,
177 | iconMixin
178 | ])
179 | ])
180 | ]),
181 | iconMixin
182 | ]),
183 | cM('text', {
184 | border: 'none',
185 | ...commonStyle
186 | }, [
187 | borderMaskMixin,
188 | cNotM('disabled', [
189 | cM('rippling', [
190 | c('&::after', {
191 | display: 'none'
192 | })
193 | ]),
194 | cM('enter-pressed', {
195 | ...commonStyle
196 | }),
197 | c('&:not(:active):focus', [
198 | cNotM('enter-pressed', {
199 | ...commonStyle
200 | })
201 | ]),
202 | cNotM('enter-pressed', [
203 | c('&:hover', {
204 | ...commonStyle
205 | }),
206 | c('&:active', {
207 | ...commonStyle
208 | })
209 | ])
210 | ])
211 | ]),
212 | iconMixin,
213 | cNotM('disabled', [
214 | cM('enter-pressed', {
215 | ...commonStyle
216 | }, [
217 | borderMaskMixin,
218 | iconMixin
219 | ]),
220 | cM('rippling', [
221 | c('&::after', {
222 | zIndex: 1,
223 | animationName: bt,
224 | animationDuration: bt,
225 | animationTimingFunction: bt
226 | }),
227 | c('&:not(:active):focus', [
228 | cNotM('enter-pressed', {
229 | ...commonStyle
230 | }, [
231 | borderMaskMixin,
232 | iconMixin
233 | ])
234 | ]),
235 | cNotM('enter-pressed', [
236 | c('&:hover', {
237 | ...commonStyle
238 | }, [
239 | borderMaskMixin,
240 | iconMixin
241 | ])
242 | ])
243 | ])
244 | ])
245 | ])
246 | }
247 |
248 | function buttonRippleMixin (type) {
249 | const bt = buttonType[type]
250 | return [
251 | c(`@keyframes ${type}-button-ripple--spread`, {
252 | from: { boxShadow: bt },
253 | to: { boxShadow: bt }
254 | }),
255 | c(`@keyframes ${type}-button-ripple--opacity`, {
256 | from: { opacity: 0.4 },
257 | to: { opacity: 0 }
258 | })
259 | ]
260 | }
261 |
262 | /** ripple */
263 | const rippleStyle = Object.keys(buttonType).map(type => {
264 | return buttonRippleMixin(type)
265 | })
266 |
267 | /** button */
268 | const buttonStyle = cB('button', {
269 | raw: `
270 | box-sizing: border-box;
271 | outline: none;
272 | position: relative;
273 | z-index: auto;
274 | font-family: inherit;
275 | display: inline-block;
276 | align-items: center;
277 | justify-content: center;
278 | user-select: none;
279 | text-align: center;
280 | transition:
281 | background-color .3s ease-in-out,
282 | opacity .3s ease-in-out,
283 | border-color .3s ease-in-out;
284 | cursor: pointer;
285 | `
286 | }, [
287 | c('&::after', {
288 | raw: `
289 | pointer-events: none;
290 | content: "",
291 | border-radius: inherit;
292 | position: absolute;
293 | left: -1px;
294 | top: -1px;
295 | right: -1px';
296 | bottom: -1px;
297 | `
298 | }),
299 | cE('border-mask', {
300 | raw: `
301 | position: absolute;
302 | left: -1px;
303 | top: -1px;
304 | right: -1px;
305 | bottom: -1px;
306 | border-radius: inherit;
307 | box-shadow: inset 0 0 0 1px transparent;
308 | transition: box-shadow .3s $--n-ease-in-out-cubic-bezier;
309 | pointerEvents: none;
310 | zIndex: 1;
311 | `
312 | }),
313 | cE('icon', {
314 | transition: 'color .3s $--n-ease-in-out-cubic-bezier'
315 | }),
316 | cE('content', {
317 | whiteSpace: 'nowrap',
318 | transition: 'color .3s $--n-ease-in-out-cubic-bezier'
319 | }),
320 | cM('left-icon', [
321 | cE('icon', {
322 | marginRight: '6px'
323 | })
324 | ]),
325 | cM('right-icon', [
326 | cE('icon', {
327 | marginLeft: '6px'
328 | })
329 | ]),
330 | cM('block', {
331 | display: 'block',
332 | width: '100%'
333 | }),
334 | cM('loading', {
335 | display: 'block',
336 | width: '100%'
337 | }),
338 | cM('disabled', {
339 | cursor: 'not-allowed'
340 | }),
341 | c('&::-moz-focus-inner', {
342 | border: 0
343 | }),
344 | buttonSizeMixin('tiny'),
345 | buttonSizeMixin('small'),
346 | buttonSizeMixin('medium'),
347 | buttonSizeMixin('large'),
348 | ...Object.keys(buttonType).map(type => buttonTypeMixin(type))
349 | ])
350 |
351 | const buttonGroupStyle = cB('button-group', {
352 | whiteSpace: 'nowrap',
353 | display: 'inline-block',
354 | position: 'relative'
355 | }, [
356 | cNotM('vertical', {
357 | display: 'flex',
358 | flexWrap: 'nowrap'
359 | }, [
360 | cE('button', {
361 | flexGrow: 1
362 | }),
363 | cB('button', [
364 | c('&:first-child:not(:last-child)', {
365 | marginRight: '0 !important',
366 | borderTopRightRadius: 0,
367 | borderBottomRightRadius: 0
368 | }),
369 | c('&:last-child:not(:first-child)', {
370 | marginLeft: '0 !important',
371 | borderTopLeftRadius: 0,
372 | borderBottomLeftRadius: 0
373 | }),
374 | c('&:not(first-child):not(:last-child)', {
375 | marginLeft: '0 !important',
376 | marginRight: '0 !important',
377 | borderRadius: '0 !important'
378 | }),
379 | cM('default-type', [
380 | c('& +', [
381 | cB('button', [
382 | cM('default-type', {
383 | borderLeftWidth: 0
384 | })
385 | ])
386 | ])
387 | ]),
388 | cM('ghost', [
389 | ...[
390 | 'primary',
391 | 'info',
392 | 'success',
393 | 'warning',
394 | 'error'
395 | ].map(v => cM(v + '-type', [
396 | c('& +', [
397 | cB('button', [
398 | cM('default-type', {
399 | borderLeftWidth: 0
400 | })
401 | ])
402 | ])
403 | ]))
404 | ])
405 | ])
406 | ]),
407 | cM('vertical', {
408 | display: 'inline-flex',
409 | flexDirection: 'column'
410 | }, [
411 | cB('button', [
412 | c('&:first-child:not(:last-child)', {
413 | raw: `
414 | margin-bottom: 0 !important;
415 | margin-left: 0 !important;
416 | margin-right: 0 !important;
417 | border-bottom-reft-radius: 0;
418 | border-bottom-right-radius: 0;
419 | `
420 | }),
421 | c('&:last-child:not(:first-child)', {
422 | raw: `
423 | margin-top: 0 !important;
424 | margin-left: 0 !important;
425 | margin-right: 0 !important;
426 | border-bottom-left-radius: 0;
427 | border-bottom-left-radius: 0;
428 | `
429 | }),
430 | c('&:not(first-child):not(:last-child)', {
431 | margin: '0 !important',
432 | borderRadius: '0 !important'
433 | }),
434 | cM('default-type', [
435 | c('& +', [
436 | cB('button', [
437 | cM('default-type', {
438 | borderTopWidth: 0
439 | })
440 | ])
441 | ])
442 | ]),
443 | cM('ghost', [
444 | ...[
445 | 'primary',
446 | 'info',
447 | 'success',
448 | 'warning',
449 | 'error'
450 | ].map(v => cM(v + '-type', [
451 | c('& +', [
452 | cB('button', [
453 | cM('default-type', {
454 | borderTopWidth: 0
455 | })
456 | ])
457 | ])
458 | ]))
459 | ])
460 | ])
461 | ])
462 | ])
463 |
464 | const output = c([
465 | rippleStyle,
466 | buttonStyle,
467 | buttonGroupStyle
468 | ]).render()
469 |
470 | const end = performance.now()
471 |
472 | // console.log(output)
473 | console.log(end - start)
474 |
--------------------------------------------------------------------------------
/playground/button.perf.js:
--------------------------------------------------------------------------------
1 | const { CssRender } = require('css-render')
2 | const { plugin } = require('@css-render/plugin-bem')
3 |
4 | const cssr = CssRender()
5 | const bemPlugin = plugin({
6 | blockPrefix: '.n-'
7 | })
8 | const { c } = cssr
9 | cssr.use(bemPlugin)
10 | const {
11 | cB, cE, cM, cNotM
12 | } = bemPlugin
13 |
14 | const buttonType = {
15 | default: 'default-type',
16 | primary: 'primary-type',
17 | info: 'info-type',
18 | success: 'success-type',
19 | warning: 'warning-type',
20 | error: 'error-type'
21 | }
22 |
23 | const buttonSize = {
24 | small: 'small-size',
25 | medium: 'medium-size',
26 | large: 'large-size'
27 | }
28 |
29 | const {
30 | performance
31 | } = require('perf_hooks')
32 |
33 | const start = performance.now()
34 |
35 | function buttonSizeMixin (size) {
36 | const bs = buttonSize[size]
37 | return (
38 | cM(size + '-size', {
39 | borderRadius: bs,
40 | fontSize: bs,
41 | whiteSpace: 'nowrap'
42 | }, [
43 | cNotM('text', {
44 | height: bs,
45 | lineHeight: bs,
46 | padding: bs
47 | }),
48 | cM('round', {
49 | padding: bs,
50 | borderRadius: bs
51 | }),
52 | cM('circle', {
53 | width: bs,
54 | borderRadius: bs,
55 | padding: '0 !important'
56 | }),
57 | cM('text', {
58 | padding: 0,
59 | borderRadius: 0
60 | }, [
61 | cE('icon', {
62 | height: bs,
63 | lineHeight: bs
64 | })
65 | ]),
66 | cE('content', {
67 | display: 'inline-block',
68 | lineHeight: 'inherit'
69 | }),
70 | cE('icon', {
71 | display: 'inline-block',
72 | position: 'relative',
73 | lineHeight: bs,
74 | height: bs,
75 | width: bs,
76 | maxWidth: bs,
77 | verticalAlign: 'bottom'
78 | }, [
79 | cB('icon', {
80 | fontSize: bs
81 | }),
82 | cB('base-loading', {
83 | height: bs,
84 | width: bs,
85 | raw: `
86 | position: absolute;
87 | left: 0;
88 | top: 50%;
89 | transform: translateY(-50%);
90 | display: block;
91 | `
92 | }),
93 | cM('slot', {
94 | width: bs,
95 | fontSize: bs,
96 | raw: `
97 | display: inline-block;
98 | align-items: center;
99 | vertical-align: bottom;
100 | `
101 | }, [
102 | cB('icon-slot', {
103 | raw: `
104 | position: absolute;
105 | left: 0;
106 | top: 50%;
107 | transform: translateY(-50%);
108 | display: block;
109 | `,
110 | lineHeight: bs,
111 | height: bs,
112 | width: bs,
113 | fontSize: bs
114 | })
115 | ])
116 | ])
117 | ])
118 | )
119 | }
120 |
121 | function buttonTypeMixin (type) {
122 | const bt = buttonType[type]
123 | const commonStyle = {
124 | backgroundColor: bt,
125 | color: bt
126 | }
127 | const borderStyle = {
128 | border: bt
129 | }
130 | const boxShadowStyle = {
131 | boxShadow: bt
132 | }
133 | const borderMaskMixin = cE('border-mask', {
134 | ...boxShadowStyle
135 | })
136 | const iconMixin = cE('icon', [
137 | cB('icon', {
138 | fill: bt,
139 | stroke: bt
140 | }),
141 | cB('base-loading', {
142 | fill: bt,
143 | stroke: bt
144 | })
145 | ])
146 | return cM(type + '-type', {
147 | ...borderStyle,
148 | ...commonStyle
149 | }, [
150 | cM('ghost, text', [
151 | cNotM('disabled', [
152 | cM('enter-pressed', {
153 | ...commonStyle
154 | }, [
155 | borderMaskMixin,
156 | iconMixin
157 | ]),
158 | c('&:not(:active):focus', [
159 | cNotM('enter-pressed', {
160 | ...commonStyle
161 | }, [
162 | borderMaskMixin,
163 | iconMixin
164 | ])
165 | ]),
166 | cNotM('enter-pressed', [
167 | c('&:hover', {
168 | ...commonStyle
169 | }, [
170 | borderMaskMixin,
171 | iconMixin
172 | ]),
173 | c('&:active', {
174 | ...commonStyle
175 | }, [
176 | borderMaskMixin,
177 | iconMixin
178 | ])
179 | ])
180 | ]),
181 | iconMixin
182 | ]),
183 | cM('text', {
184 | border: 'none',
185 | ...commonStyle
186 | }, [
187 | borderMaskMixin,
188 | cNotM('disabled', [
189 | cM('rippling', [
190 | c('&::after', {
191 | display: 'none'
192 | })
193 | ]),
194 | cM('enter-pressed', {
195 | ...commonStyle
196 | }),
197 | c('&:not(:active):focus', [
198 | cNotM('enter-pressed', {
199 | ...commonStyle
200 | })
201 | ]),
202 | cNotM('enter-pressed', [
203 | c('&:hover', {
204 | ...commonStyle
205 | }),
206 | c('&:active', {
207 | ...commonStyle
208 | })
209 | ])
210 | ])
211 | ]),
212 | iconMixin,
213 | cNotM('disabled', [
214 | cM('enter-pressed', {
215 | ...commonStyle
216 | }, [
217 | borderMaskMixin,
218 | iconMixin
219 | ]),
220 | cM('rippling', [
221 | c('&::after', {
222 | zIndex: 1,
223 | animationName: bt,
224 | animationDuration: bt,
225 | animationTimingFunction: bt
226 | }),
227 | c('&:not(:active):focus', [
228 | cNotM('enter-pressed', {
229 | ...commonStyle
230 | }, [
231 | borderMaskMixin,
232 | iconMixin
233 | ])
234 | ]),
235 | cNotM('enter-pressed', [
236 | c('&:hover', {
237 | ...commonStyle
238 | }, [
239 | borderMaskMixin,
240 | iconMixin
241 | ])
242 | ])
243 | ])
244 | ])
245 | ])
246 | }
247 |
248 | function buttonRippleMixin (type) {
249 | const bt = buttonType[type]
250 | return [
251 | c(`@keyframes ${type}-button-ripple--spread`, {
252 | from: { boxShadow: bt },
253 | to: { boxShadow: bt }
254 | }),
255 | c(`@keyframes ${type}-button-ripple--opacity`, {
256 | from: { opacity: 0.4 },
257 | to: { opacity: 0 }
258 | })
259 | ]
260 | }
261 |
262 | /** ripple */
263 | const rippleStyle = Object.keys(buttonType).map(type => {
264 | return buttonRippleMixin(type)
265 | })
266 |
267 | /** button */
268 | const buttonStyle = cB('button', {
269 | raw: `
270 | box-sizing: border-box;
271 | outline: none;
272 | position: relative;
273 | z-index: auto;
274 | font-family: inherit;
275 | display: inline-block;
276 | align-items: center;
277 | justify-content: center;
278 | user-select: none;
279 | text-align: center;
280 | transition:
281 | background-color .3s ease-in-out,
282 | opacity .3s ease-in-out,
283 | border-color .3s ease-in-out;
284 | cursor: pointer;
285 | `
286 | }, [
287 | c('&::after', {
288 | raw: `
289 | pointer-events: none;
290 | content: "",
291 | border-radius: inherit;
292 | position: absolute;
293 | left: -1px;
294 | top: -1px;
295 | right: -1px';
296 | bottom: -1px;
297 | `
298 | }),
299 | cE('border-mask', {
300 | raw: `
301 | position: absolute;
302 | left: -1px;
303 | top: -1px;
304 | right: -1px;
305 | bottom: -1px;
306 | border-radius: inherit;
307 | box-shadow: inset 0 0 0 1px transparent;
308 | transition: box-shadow .3s $--n-ease-in-out-cubic-bezier;
309 | pointerEvents: none;
310 | zIndex: 1;
311 | `
312 | }),
313 | cE('icon', {
314 | transition: 'color .3s $--n-ease-in-out-cubic-bezier'
315 | }),
316 | cE('content', {
317 | whiteSpace: 'nowrap',
318 | transition: 'color .3s $--n-ease-in-out-cubic-bezier'
319 | }),
320 | cM('left-icon', [
321 | cE('icon', {
322 | marginRight: '6px'
323 | })
324 | ]),
325 | cM('right-icon', [
326 | cE('icon', {
327 | marginLeft: '6px'
328 | })
329 | ]),
330 | cM('block', {
331 | display: 'block',
332 | width: '100%'
333 | }),
334 | cM('loading', {
335 | display: 'block',
336 | width: '100%'
337 | }),
338 | cM('disabled', {
339 | cursor: 'not-allowed'
340 | }),
341 | c('&::-moz-focus-inner', {
342 | border: 0
343 | }),
344 | buttonSizeMixin('tiny'),
345 | buttonSizeMixin('small'),
346 | buttonSizeMixin('medium'),
347 | buttonSizeMixin('large'),
348 | ...Object.keys(buttonType).map(type => buttonTypeMixin(type))
349 | ])
350 |
351 | const buttonGroupStyle = cB('button-group', {
352 | whiteSpace: 'nowrap',
353 | display: 'inline-block',
354 | position: 'relative'
355 | }, [
356 | cNotM('vertical', {
357 | display: 'flex',
358 | flexWrap: 'nowrap'
359 | }, [
360 | cE('button', {
361 | flexGrow: 1
362 | }),
363 | cB('button', [
364 | c('&:first-child:not(:last-child)', {
365 | marginRight: '0 !important',
366 | borderTopRightRadius: 0,
367 | borderBottomRightRadius: 0
368 | }),
369 | c('&:last-child:not(:first-child)', {
370 | marginLeft: '0 !important',
371 | borderTopLeftRadius: 0,
372 | borderBottomLeftRadius: 0
373 | }),
374 | c('&:not(first-child):not(:last-child)', {
375 | marginLeft: '0 !important',
376 | marginRight: '0 !important',
377 | borderRadius: '0 !important'
378 | }),
379 | cM('default-type', [
380 | c('& +', [
381 | cB('button', [
382 | cM('default-type', {
383 | borderLeftWidth: 0
384 | })
385 | ])
386 | ])
387 | ]),
388 | cM('ghost', [
389 | ...[
390 | 'primary',
391 | 'info',
392 | 'success',
393 | 'warning',
394 | 'error'
395 | ].map(v => cM(v + '-type', [
396 | c('& +', [
397 | cB('button', [
398 | cM('default-type', {
399 | borderLeftWidth: 0
400 | })
401 | ])
402 | ])
403 | ]))
404 | ])
405 | ])
406 | ]),
407 | cM('vertical', {
408 | display: 'inline-flex',
409 | flexDirection: 'column'
410 | }, [
411 | cB('button', [
412 | c('&:first-child:not(:last-child)', {
413 | raw: `
414 | margin-bottom: 0 !important;
415 | margin-left: 0 !important;
416 | margin-right: 0 !important;
417 | border-bottom-reft-radius: 0;
418 | border-bottom-right-radius: 0;
419 | `
420 | }),
421 | c('&:last-child:not(:first-child)', {
422 | raw: `
423 | margin-top: 0 !important;
424 | margin-left: 0 !important;
425 | margin-right: 0 !important;
426 | border-bottom-left-radius: 0;
427 | border-bottom-left-radius: 0;
428 | `
429 | }),
430 | c('&:not(first-child):not(:last-child)', {
431 | margin: '0 !important',
432 | borderRadius: '0 !important'
433 | }),
434 | cM('default-type', [
435 | c('& +', [
436 | cB('button', [
437 | cM('default-type', {
438 | borderTopWidth: 0
439 | })
440 | ])
441 | ])
442 | ]),
443 | cM('ghost', [
444 | ...[
445 | 'primary',
446 | 'info',
447 | 'success',
448 | 'warning',
449 | 'error'
450 | ].map(v => cM(v + '-type', [
451 | c('& +', [
452 | cB('button', [
453 | cM('default-type', {
454 | borderTopWidth: 0
455 | })
456 | ])
457 | ])
458 | ]))
459 | ])
460 | ])
461 | ])
462 | ])
463 |
464 | const output = c([
465 | rippleStyle,
466 | buttonStyle,
467 | buttonGroupStyle
468 | ]).render()
469 |
470 | const end = performance.now()
471 |
472 | // console.log(output)
473 | console.log(end - start)
474 |
--------------------------------------------------------------------------------
/packages/css-render/__tests__/render/index.spec.ts:
--------------------------------------------------------------------------------
1 | import CssRender from '../../src'
2 | import { assertEqual } from '@css-render/test-shared'
3 |
4 | const { c, config } = CssRender()
5 |
6 | describe('#render - common cases', () => {
7 | it('should work with nested nodes array', () => {
8 | assertEqual(
9 | c(
10 | 'body',
11 | {
12 | margin: 0,
13 | backgroundColor: 'white'
14 | },
15 | [
16 | [
17 | [
18 | c('&.dark', {
19 | backgroundColor: 'black'
20 | })
21 | ],
22 | c('.container', {
23 | width: '100%'
24 | })
25 | ]
26 | ]
27 | ).render(),
28 | `body {
29 | margin: 0;
30 | background-color: white;
31 | }
32 |
33 | body.dark {
34 | background-color: black;
35 | }
36 |
37 | body .container {
38 | width: 100%;
39 | }`
40 | )
41 | })
42 | it('should render as expected(1)', () => {
43 | assertEqual(
44 | c(
45 | 'body',
46 | {
47 | margin: 0,
48 | backgroundColor: 'white'
49 | },
50 | [
51 | c('&.dark', {
52 | backgroundColor: 'black'
53 | }),
54 | c('.container', {
55 | width: '100%'
56 | })
57 | ]
58 | ).render(),
59 | `body {
60 | margin: 0;
61 | background-color: white;
62 | }
63 |
64 | body.dark {
65 | background-color: black;
66 | }
67 |
68 | body .container {
69 | width: 100%;
70 | }`
71 | )
72 | })
73 | it('should render as expected(2)', () => {
74 | assertEqual(
75 | c('body', [
76 | c('&.dark', {
77 | backgroundColor: 'black'
78 | }),
79 | c('.container', {
80 | width: '100%'
81 | })
82 | ]).render(),
83 | `body.dark {
84 | background-color: black;
85 | }
86 |
87 | body .container {
88 | width: 100%;
89 | }`
90 | )
91 | })
92 | it('should render as excepted(3)', () => {
93 | assertEqual(
94 | c('@keyframes what-a-good-animation', {
95 | from: {
96 | color: 'white',
97 | backgroundColor: 'black'
98 | },
99 | to: {
100 | color: 'black',
101 | backgroundColor: 'white'
102 | }
103 | }).render(),
104 | `@keyframes what-a-good-animation {
105 | from {
106 | color: white;
107 | background-color: black;
108 | }
109 | to {
110 | color: black;
111 | background-color: white;
112 | }
113 | }`
114 | )
115 | })
116 | it('should render a function typed prop', () => {
117 | assertEqual(
118 | c('@keyframes what-a-good-animation', () => ({
119 | from: {
120 | color: 'white',
121 | backgroundColor: 'black'
122 | },
123 | to: {
124 | color: 'black',
125 | backgroundColor: 'white'
126 | }
127 | })).render(),
128 | `@keyframes what-a-good-animation {
129 | from {
130 | color: white;
131 | background-color: black;
132 | }
133 | to {
134 | color: black;
135 | background-color: white;
136 | }
137 | }`
138 | )
139 | })
140 | it('should render an array as root', () => {
141 | assertEqual(
142 | c([
143 | c(
144 | 'sel1',
145 | {
146 | position: 'relative'
147 | },
148 | [
149 | c('&.sel2', {
150 | position: 'relative'
151 | })
152 | ]
153 | ),
154 | c(
155 | 'sel1',
156 | {
157 | position: 'relative'
158 | },
159 | [
160 | c('&.sel2', {
161 | position: 'relative'
162 | })
163 | ]
164 | )
165 | ]).render(),
166 | `
167 | sel1 {
168 | position: relative;
169 | }
170 | sel1.sel2 {
171 | position: relative;
172 | }
173 | sel1 {
174 | position: relative;
175 | }
176 | sel1.sel2 {
177 | position: relative;
178 | }
179 | `
180 | )
181 | })
182 | it('should render with props', () => {
183 | const style = c('sel1', ({ props }) => {
184 | return {
185 | color: props.color
186 | }
187 | })
188 | assertEqual(
189 | style.render({
190 | color: 'red'
191 | }),
192 | `
193 | sel1 {
194 | color: red;
195 | }
196 | `
197 | )
198 | })
199 | it('should work with COptionSelector', () => {
200 | assertEqual(
201 | c(
202 | {
203 | $: 'body'
204 | },
205 | [
206 | c(
207 | {
208 | $: () => '&.dark'
209 | },
210 | {
211 | backgroundColor: 'black'
212 | }
213 | ),
214 | c('.container', {
215 | width: '100%'
216 | })
217 | ]
218 | ).render(),
219 | `body.dark {
220 | background-color: black;
221 | }
222 |
223 | body .container {
224 | width: 100%;
225 | }`
226 | )
227 | })
228 | it('should work with CLazySelector', () => {
229 | assertEqual(
230 | c(
231 | ({ props }) => (props.pfx as string) + 'body',
232 | [
233 | c(({ props }) => `&.${props.pfx as string}dark`, {
234 | backgroundColor: 'black'
235 | }),
236 | c(() => '.container', {
237 | width: '100%'
238 | })
239 | ]
240 | ).render({
241 | pfx: 'pfx'
242 | }),
243 | `pfxbody.pfxdark {
244 | background-color: black;
245 | }
246 |
247 | pfxbody .container {
248 | width: 100%;
249 | }`
250 | )
251 | })
252 | it('should work with function typed children', () => {
253 | assertEqual(
254 | c(
255 | () => 'body',
256 | [
257 | ({ props }) => [
258 | c((props.prefix as string) + '&.dark', {
259 | backgroundColor: 'black'
260 | }),
261 | c((props.prefix as string) + '.container', {
262 | width: '100%'
263 | })
264 | ]
265 | ]
266 | ).render({
267 | prefix: 'pfx'
268 | }),
269 | `pfxbody.dark {
270 | background-color: black;
271 | }
272 |
273 | body pfx.container {
274 | width: 100%;
275 | }`
276 | )
277 | assertEqual(
278 | c(
279 | () => 'body',
280 | [
281 | ({ props }) =>
282 | c((props.prefix as string) + '&.dark', {
283 | backgroundColor: 'black'
284 | }),
285 | ({ props }) =>
286 | c((props.prefix as string) + '.container', {
287 | width: '100%'
288 | })
289 | ]
290 | ).render({
291 | prefix: 'pfx'
292 | }),
293 | `pfxbody.dark {
294 | background-color: black;
295 | }
296 |
297 | body pfx.container {
298 | width: 100%;
299 | }`
300 | )
301 | })
302 | it('should work with empty selector', () => {
303 | assertEqual(
304 | c('', [
305 | c('&.a', {
306 | background: 'red'
307 | })
308 | ]).render(),
309 | `.a {
310 | background: red;
311 | }`
312 | )
313 | assertEqual(
314 | c(
315 | () => '',
316 | [
317 | c('&.a', {
318 | background: 'red'
319 | })
320 | ]
321 | ).render(),
322 | `.a {
323 | background: red;
324 | }`
325 | )
326 | assertEqual(
327 | c(
328 | () => null,
329 | [
330 | c('&.a', {
331 | background: 'red'
332 | })
333 | ]
334 | ).render(),
335 | `.a {
336 | background: red;
337 | }`
338 | )
339 | assertEqual(
340 | c({}, [
341 | c('&.a', {
342 | background: 'red'
343 | })
344 | ]).render(),
345 | `.a {
346 | background: red;
347 | }`
348 | )
349 | })
350 | it('should preserve empty block when keepEmptyBlock is false', () => {
351 | config.keepEmptyBlock = true
352 | assertEqual(c('body', {}).render(), 'body {}')
353 | config.keepEmptyBlock = false
354 | assertEqual(c('body', {}).render(), '')
355 | config.keepEmptyBlock = true
356 | })
357 | it("shouldn't render empty property", () => {
358 | assertEqual(
359 | c('body', {
360 | background: undefined,
361 | myColor: null
362 | }).render(),
363 | 'body {}'
364 | )
365 | })
366 | it('should work with empty selector', () => {
367 | assertEqual(
368 | c('body', [
369 | c(
370 | {},
371 | {
372 | background: 'red'
373 | }
374 | )
375 | ]).render(),
376 | `body {
377 | background: red;
378 | }`
379 | )
380 | assertEqual(
381 | c(
382 | 'body',
383 | {
384 | color: 'black'
385 | },
386 | [
387 | c(
388 | {},
389 | {
390 | background: 'red'
391 | }
392 | )
393 | ]
394 | ).render(),
395 | `
396 | body {
397 | color: black;
398 | }
399 |
400 | body {
401 | background: red;
402 | }
403 | `
404 | )
405 | })
406 | it('should work with functional typed option selector.$', () => {
407 | assertEqual(
408 | c(
409 | {
410 | $: ({ props }) => props.x
411 | },
412 | {
413 | background: 'red'
414 | }
415 | ).render({
416 | x: 'body'
417 | }),
418 | `body {
419 | background: red;
420 | }`
421 | )
422 | })
423 | it('should work with null & void returned props function', () => {
424 | assertEqual(
425 | c('body', () => null, [c('body2', {})]).render(),
426 | 'body body2 {}'
427 | )
428 | assertEqual(
429 | c('body', () => undefined, [c('body2', {})]).render(),
430 | 'body body2 {}'
431 | )
432 | })
433 | })
434 |
435 | describe('#render - doc cases', () => {
436 | it('#case1', () => {
437 | assertEqual(
438 | c(
439 | '.button',
440 | {
441 | color: 'black'
442 | },
443 | [
444 | c('.button__icon', {
445 | fill: 'black'
446 | }),
447 | c('&.button--error', {
448 | color: 'red'
449 | })
450 | ]
451 | ).render(),
452 | `
453 | .button {
454 | color: black;
455 | }
456 |
457 | .button .button__icon {
458 | fill: black;
459 | }
460 |
461 | .button.button--error {
462 | color: red;
463 | }
464 | `
465 | )
466 | })
467 | it('#case2', () => {
468 | assertEqual(
469 | c(({ context, props }) => props.selector, {
470 | color: 'black'
471 | }).render({
472 | selector: '.selector'
473 | }),
474 | `
475 | .selector {
476 | color: black;
477 | }
478 | `
479 | )
480 | })
481 | it('#case3', () => {
482 | assertEqual(
483 | c('div', [
484 | c(null, [
485 | c('button', {
486 | color: 'black'
487 | })
488 | ])
489 | ]).render(),
490 | `
491 | div button {
492 | color: black;
493 | }
494 | `
495 | )
496 | })
497 | it('#case3', () => {
498 | assertEqual(
499 | c('div', [
500 | ({ context, props }) =>
501 | c('button', {
502 | color: props.color
503 | }),
504 | c('ul', {
505 | backgroundColor: 'red'
506 | }),
507 | [
508 | [
509 | c('dl', {
510 | backgroundColor: 'red'
511 | })
512 | ]
513 | ],
514 | () => [
515 | c('ol', {
516 | backgroundColor: 'red'
517 | })
518 | ]
519 | ]).render({
520 | color: 'black'
521 | }),
522 | `div button {
523 | color: black;
524 | }
525 |
526 | div ul {
527 | background-color: red;
528 | }
529 |
530 | div dl {
531 | background-color: red;
532 | }
533 |
534 | div ol {
535 | background-color: red;
536 | }`
537 | )
538 | })
539 | })
540 |
541 | describe('#render - falsy node', () => {
542 | it('#case1', () => {
543 | assertEqual(
544 | c(
545 | '.button',
546 | {
547 | color: 'black'
548 | },
549 | [
550 | null,
551 | undefined,
552 | () => null,
553 | () => undefined,
554 | c('.button__icon', {
555 | fill: 'black'
556 | }),
557 | c('&.button--error', {
558 | color: 'red'
559 | })
560 | ]
561 | ).render(),
562 | `
563 | .button {
564 | color: black;
565 | }
566 |
567 | .button .button__icon {
568 | fill: black;
569 | }
570 |
571 | .button.button--error {
572 | color: red;
573 | }
574 | `
575 | )
576 | })
577 | })
578 |
579 | describe('#render - string property', () => {
580 | it('#case1', () => {
581 | assertEqual(c('x', '666').render(), 'x { 666 }')
582 | })
583 | it('#case2', () => {
584 | assertEqual(c('x', 'color: red;').render(), 'x { color: red; }')
585 | })
586 | it('#case3', () => {
587 | assertEqual(c('x', () => '666').render(), 'x { 666 }')
588 | })
589 | it('#case4', () => {
590 | assertEqual(c('x', () => 'color: red;').render(), 'x { color: red; }')
591 | })
592 | })
593 |
594 | describe('#render - raw property', () => {
595 | it('#case1', () => {
596 | assertEqual(
597 | c('x', {
598 | raw: '666'
599 | }).render(),
600 | `
601 | x {
602 | 666
603 | }
604 | `
605 | )
606 | assertEqual(
607 | c('x', {
608 | raw: '666',
609 | color: 'white'
610 | }).render(),
611 | `
612 | x {
613 | 666
614 | color: white;
615 | }
616 | `
617 | )
618 | })
619 | })
620 |
621 | describe('#render - string child', () => {
622 | it('#case 1', () => {
623 | assertEqual(c(['good { key: value }']).render(), 'good { key: value }')
624 | })
625 | it('#case 2', () => {
626 | assertEqual(
627 | c('parent', [c('gogogo', { key: 'value' }), 'key: value;']).render(),
628 | `
629 | parent gogogo { key: value; }
630 | parent { key: value; }
631 | `
632 | )
633 | })
634 | })
635 |
636 | describe('#render - media query', () => {
637 | it('#case 1', () => {
638 | assertEqual(
639 | c('@media', [c('a', { color: 'red' })]).render(),
640 | '@media { a { color: red; } }'
641 | )
642 | })
643 | it('#case 2', () => {
644 | assertEqual(
645 | c('@media', [
646 | c('a', { color: 'red' }),
647 | c('b', { color: 'red' })
648 | ]).render(),
649 | '@media { a { color: red; } b { color: red; } }'
650 | )
651 | })
652 | it('#case 3', () => {
653 | assertEqual(
654 | c('@supports', [
655 | c('@media', [c('a', { color: 'red' }), c('b', { color: 'red' })])
656 | ]).render(),
657 | '@supports { @media { a { color: red; } b { color: red; } } }'
658 | )
659 | })
660 | it('#case 4', () => {
661 | assertEqual(
662 | c(' @supports', [
663 | c(' @media', [c('a', { color: 'red' }), c('b', { color: 'red' })])
664 | ]).render(),
665 | '@supports { @media { a { color: red; } b { color: red; } } }'
666 | )
667 | })
668 | it('#case 4', () => {
669 | assertEqual(
670 | c('@supports', [
671 | c('a', { color: 'red' }),
672 | c('@media', [c('b', { color: 'red' })])
673 | ]).render(),
674 | '@supports { a { color: red; } @media { b { color: red; } } }'
675 | )
676 | })
677 | it('#case 5', () => {
678 | assertEqual(
679 | c([
680 | c('@supports', [
681 | c('a', { color: 'red' }),
682 | c('@media', [c('b', { color: 'red' })])
683 | ])
684 | ]).render(),
685 | '@supports { a { color: red; } @media { b { color: red; } } }'
686 | )
687 | })
688 | })
689 |
690 | describe('#render - at rules', () => {
691 | it('@page', () => {
692 | assertEqual(
693 | c('@page', { margin: '1em' }).render(),
694 | '@page { margin: 1em; } '
695 | )
696 | })
697 | it('@font-face', () => {
698 | assertEqual(
699 | c('@font-face', { fontFamily: 'ggg' }).render(),
700 | '@font-face { font-family: ggg; } '
701 | )
702 | })
703 | })
704 |
--------------------------------------------------------------------------------