├── .eslintrc.json
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .npmignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── __tests__
├── __snapshots__
│ └── incremental-classnames.js.snap
├── code
│ ├── errors.js
│ ├── fixtures.js
│ ├── fixtures
│ │ ├── classes
│ │ │ ├── complex-ternary
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── destructuring-assignment
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── dynamic-bracket-access
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── empty-call
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── hoists-arrow-function-call
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── hoists-block-function-call
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── hoists-function-call
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── keeps-multiple-instances-of-same-value
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── member-expression-access
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── mixed
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── moves-test
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── no-keys
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── object
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── property-access
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── short-circuits-same-value
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── spread-assignment
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── spread-use
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── static-bracket-access
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── string-literal
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ └── ternary
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ ├── custom-properties
│ │ │ ├── does-not-change-capitalization
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ └── does-not-convert-number
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ ├── import
│ │ │ └── ignore-other-imports
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ ├── incremental-classnames
│ │ │ ├── generated-classname
│ │ │ │ ├── code.js
│ │ │ │ ├── options.json
│ │ │ │ └── output.js
│ │ │ └── object-classname
│ │ │ │ ├── code.js
│ │ │ │ ├── options.json
│ │ │ │ └── output.js
│ │ ├── keyframes
│ │ │ ├── basic
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── converts-from
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── converts-to
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ └── setting-animationName-directly
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ ├── minify-properties
│ │ │ ├── does-not-minify-by-default
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── hashes-unknown-properties
│ │ │ │ ├── code.js
│ │ │ │ ├── options.json
│ │ │ │ └── output.js
│ │ │ ├── minifies-known-properties
│ │ │ │ ├── code.js
│ │ │ │ ├── options.json
│ │ │ │ └── output.js
│ │ │ └── minifies-nested-properties
│ │ │ │ ├── code.js
│ │ │ │ ├── options.json
│ │ │ │ └── output.js
│ │ ├── nesting
│ │ │ ├── at-rule-key
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── at-rules
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── basic
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── deep
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ ├── generates-correct-class-names
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ │ └── translates-old-pseudo-element
│ │ │ │ ├── code.js
│ │ │ │ └── output.js
│ │ ├── typescript
│ │ │ └── casting
│ │ │ │ ├── code.ts
│ │ │ │ └── output.ts
│ │ └── values
│ │ │ ├── arrow-function
│ │ │ ├── code.js
│ │ │ └── output.js
│ │ │ ├── expands-shorthand-in-nesting
│ │ │ ├── code.js
│ │ │ └── output.js
│ │ │ ├── expands-shorthand
│ │ │ ├── code.js
│ │ │ └── output.js
│ │ │ ├── keeps-longhand
│ │ │ ├── code.js
│ │ │ └── output.js
│ │ │ └── removes-unused-keys
│ │ │ ├── code.js
│ │ │ └── output.js
│ ├── import.js
│ └── nesting.js
├── compile.js
├── css-sorter.js
├── incremental-classnames.js
├── resolver.js
└── styles
│ ├── comma-separated-properties.js
│ ├── custom-properties.js
│ ├── incremental-classnames.js
│ ├── keyframes.js
│ ├── multiple-imports.js
│ ├── nesting.js
│ ├── transition-property.js
│ └── values.js
├── babel.js
├── docs
├── Background.md
├── Bundler-plugins.md
├── Ecosystem.md
├── FAQ.md
├── How-it-works.md
├── TypeScript.md
└── Usage-guide.md
├── examples
├── gatsby
│ ├── .gitignore
│ ├── README.md
│ ├── gatsby-config.js
│ ├── package.json
│ ├── src
│ │ └── pages
│ │ │ └── index.tsx
│ ├── tsconfig.json
│ └── yarn.lock
├── nextjs
│ ├── .gitignore
│ ├── README.md
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ └── index.tsx
│ ├── shared
│ │ └── styles.js
│ ├── tsconfig.json
│ └── yarn.lock
├── rollup
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── rollup.config.js
│ ├── src
│ │ └── main.js
│ └── yarn.lock
├── vite
│ ├── index.html
│ ├── package.json
│ ├── src
│ │ ├── dynamic.js
│ │ ├── dynamic2.js
│ │ └── main.js
│ ├── vite.config.js
│ └── yarn.lock
├── webpack4
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ └── main.js
│ ├── webpack.config.js
│ └── yarn.lock
└── webpack5
│ ├── README.md
│ ├── package.json
│ ├── public
│ └── index.html
│ ├── src
│ └── main.js
│ ├── webpack.config.js
│ └── yarn.lock
├── gatsby
├── gatsby-node.js
└── package.json
├── index.js
├── index.mjs
├── jest.config.json
├── next-legacy.js
├── next.js
├── package.json
├── rollup.d.ts
├── rollup.js
├── scripts
└── test-examples.sh
├── src
├── helpers
│ ├── flatten-at-rules.js
│ ├── flatten-styles.js
│ ├── generate-classes.js
│ ├── generate-expression.js
│ ├── generate-styles.js
│ ├── get-style-object-value.js
│ ├── list-dynamic-keys.js
│ ├── list-function-call-keys.js
│ ├── list-function-calls.js
│ ├── list-references.js
│ ├── list-static-keys.js
│ ├── mutate-ast.js
│ ├── normalize-arguments.js
│ ├── strip-type-assertions.js
│ └── validate.js
├── plugin-utils.js
├── process-css.js
├── process-references.js
├── transpilers
│ ├── create.js
│ └── keyframes.js
└── utils
│ ├── ast.js
│ ├── constants.js
│ ├── helpers.js
│ ├── incremental-classnames.js
│ ├── styles.js
│ └── test-ast-shape.js
├── stryker.conf.json
├── types
├── Style.d.ts
├── index.d.ts
├── test
│ └── at-rules.ts
├── ts4.3
│ ├── index.d.ts
│ ├── test
│ │ ├── at-rules.ts
│ │ ├── basic.ts
│ │ ├── custom-properties.ts
│ │ ├── keyframes.ts
│ │ ├── properties.ts
│ │ └── pseudo.ts
│ └── tsconfig.json
├── tsconfig.json
└── tslint.json
├── vite.d.ts
├── vite.js
├── webpack
├── index.d.ts
├── index.js
├── loader-get-options.js
├── loader.d.ts
├── loader.js
└── virtualModules.js
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "prettier",
5 | "plugin:import/recommended",
6 | "plugin:import/errors",
7 | "plugin:import/warnings"
8 | ],
9 | "plugins": ["import"],
10 | "parserOptions": {
11 | "ecmaVersion": 2020,
12 | "sourceType": "module"
13 | },
14 | "rules": {
15 | "curly": ["error", "multi-line"],
16 | "linebreak-style": ["error", "unix"],
17 | "max-len": [
18 | "error",
19 | {
20 | "code": 80,
21 | "ignoreComments": true,
22 | "ignoreStrings": true,
23 | "ignoreTemplateLiterals": true
24 | }
25 | ],
26 | "new-cap": "off",
27 | "no-case-declarations": "error",
28 | "no-var": "error",
29 | "prefer-const": "error",
30 | "no-unused-vars": ["error", { "ignoreRestSiblings": true }],
31 | "import/order": "error"
32 | },
33 | "env": {
34 | "node": true,
35 | "es6": true
36 | },
37 | "ignorePatterns": [
38 | "__tests__/code/fixtures/",
39 | "node_modules",
40 | "build",
41 | "coverage",
42 | "examples"
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | on:
2 | pull_request:
3 | branches:
4 | - '*'
5 | push:
6 | branches:
7 | - '*'
8 |
9 | jobs:
10 | test:
11 | runs-on: ubuntu-20.04
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v2
15 |
16 | - name: Setup Node.js environment
17 | uses: actions/setup-node@v2
18 | with:
19 | node-version: 14.x
20 |
21 | - name: Install packages
22 | run: yarn --frozen-lockfile
23 |
24 | - name: Check linting
25 | run: yarn lint:check
26 |
27 | - name: Check formatting
28 | run: yarn format:check
29 |
30 | - name: Test types
31 | run: yarn test:types
32 |
33 | - name: Tests
34 | run: yarn test
35 |
36 | - name: Test examples
37 | run: yarn test:examples
38 |
39 | - name: Mutation tests
40 | run: yarn test:mutation
41 |
42 | - name: Upload Stryker report
43 | if: ${{ always() }}
44 | uses: actions/upload-artifact@v2
45 | with:
46 | name: stryker-report
47 | path: reports/mutation/html/index.html
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | .DS_Store
4 | *.log*
5 | coverage
6 | .stryker-tmp
7 | reports
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | __tests__
2 | examples
3 | scripts
4 | .github
5 | coverage
6 | .stryker-tmp
7 | reports
8 | .DS_Store
9 | *.log*
10 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | tag-version-prefix=""
2 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v14.21.2
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | __tests__/code/fixtures/**/output.js
2 | node_modules
3 | build
4 | coverage
5 | examples/gatsby/.cache
6 | examples/gatsby/public
7 | examples/nextjs/.next
8 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "none",
4 | "arrowParens": "avoid"
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021 Johan Holmerin
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # style9
2 |
3 | CSS-in-JS compiler inspired by Meta's [StyleX][stylex], with near-zero runtime, atomic CSS extraction and TypeScript support. Framework agnostic.
4 |
5 | > [!NOTE]
6 | > [StyleX][stylex] was open-sourced on 2023-12-5. Consider using that instead
7 |
8 | ## Basic usage
9 |
10 | *For a complete walkthrough of the API, see [Usage guide](docs/Usage-guide.md).*
11 |
12 | ```javascript
13 | import style9 from 'style9';
14 |
15 | const styles = style9.create({
16 | blue: {
17 | color: 'blue',
18 | },
19 | red: {
20 | color: 'red'
21 | }
22 | });
23 |
24 | document.body.className = styles('blue', isRed && 'red');
25 | ```
26 |
27 | For the above input, the compiler will generate the following output:
28 |
29 | ```javascript
30 | /* JavaScript */
31 | document.body.className = isRed ? 'cRCRUH ' : 'hxxstI ';
32 |
33 | /* CSS */
34 | .hxxstI { color: blue }
35 | .cRCRUH { color: red }
36 | ```
37 |
38 | ## Installation
39 |
40 | ```sh
41 | # Yarn
42 | yarn add style9
43 |
44 | # npm
45 | npm install style9
46 | ```
47 |
48 | ## Compiler setup - required
49 |
50 | The following is the minimally required Webpack setup for extracting styles to a CSS file. For Webpack options and Rollup, Next.js, Gatsby,Vite, and Babel plugins, see [Bundler plugins](docs/Bundler-plugins.md).
51 |
52 | ```javascript
53 | const Style9Plugin = require('style9/webpack');
54 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
55 |
56 | module.exports = {
57 | // Collect all styles in a single file - required
58 | optimization: {
59 | splitChunks: {
60 | cacheGroups: {
61 | styles: {
62 | name: 'styles',
63 | type: 'css/mini-extract',
64 | // For webpack@4 remove type and uncomment the line below
65 | // test: /\.css$/,
66 | chunks: 'all',
67 | enforce: true,
68 | }
69 | }
70 | }
71 | },
72 | module: {
73 | rules: [
74 | {
75 | test: /\.(tsx|ts|js|mjs|jsx)$/,
76 | use: Style9Plugin.loader,
77 | },
78 | {
79 | test: /\.css$/i,
80 | use: [MiniCssExtractPlugin.loader, 'css-loader']
81 | }
82 | ]
83 | },
84 | plugins: [
85 | new Style9Plugin(),
86 | new MiniCssExtractPlugin()
87 | ]
88 | };
89 | ```
90 |
91 | ## Documentation
92 |
93 | 1. [Background](docs/Background.md)
94 | 1. [Usage guide](docs/Usage-guide.md)
95 | 1. [Bundler plugins](docs/Bundler-plugins.md)
96 | 1. [TypeScript](docs/TypeScript.md)
97 | 1. [Ecosystem](docs/Ecosystem.md)
98 | 1. [How it works](docs/How-it-works.md)
99 | 1. [FAQ](docs/FAQ.md)
100 | 1. [Example apps](examples)
101 |
102 | ## Have a question?
103 |
104 | Look at the [FAQ](docs/FAQ.md), [search][search] the repo, or ask in [discussions][discussions].
105 |
106 | [stylex]: https://github.com/facebook/stylex
107 | [search]: https://github.com/johanholmerin/style9/search
108 | [discussions]: https://github.com/johanholmerin/style9/discussions
109 |
--------------------------------------------------------------------------------
/__tests__/code/errors.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const compile = require('../compile.js');
3 |
4 | it('only supports Member- and CallExpression on styles', () => {
5 | const input = `
6 | import style9 from 'style9';
7 | const styles = style9.create({
8 | default: {
9 | color: 'blue'
10 | }
11 | });
12 | foo(styles);
13 | `;
14 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
15 | "unknown: SyntaxError: Return value from style9.create has to be called as a function or accessed as an object
16 | 6 | }
17 | 7 | });
18 | > 8 | foo(styles);
19 | | ^^^^^^
20 | 9 | "
21 | `);
22 | });
23 |
24 | it('supports React Hot Loader call', () => {
25 | const input = `
26 | import style9 from 'style9';
27 | const styles = style9.create({
28 | default: {
29 | color: 'blue'
30 | }
31 | });
32 | reactHotLoader.register(styles);
33 | `;
34 | expect(() => compile(input)).not.toThrow();
35 | });
36 |
37 | it('throws on invalid React Hot Loader call', () => {
38 | const input = `
39 | import style9 from 'style9';
40 | const styles = style9.create({
41 | default: {
42 | color: 'blue'
43 | }
44 | });
45 | foo.register(styles);
46 | `;
47 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
48 | "unknown: SyntaxError: Return value from style9.create has to be called as a function or accessed as an object
49 | 6 | }
50 | 7 | });
51 | > 8 | foo.register(styles);
52 | | ^^^^^^
53 | 9 | "
54 | `);
55 | });
56 |
57 | it('throws on invalid React Hot Loader call2', () => {
58 | const input = `
59 | import style9 from 'style9';
60 | const styles = style9.create({
61 | default: {
62 | color: 'blue'
63 | }
64 | });
65 | reactHotLoader.foo(styles);
66 | `;
67 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
68 | "unknown: SyntaxError: Return value from style9.create has to be called as a function or accessed as an object
69 | 6 | }
70 | 7 | });
71 | > 8 | reactHotLoader.foo(styles);
72 | | ^^^^^^
73 | 9 | "
74 | `);
75 | });
76 |
77 | it('throws on non-existing property import', () => {
78 | const input = `
79 | import style9 from 'style9';
80 | style9.foo;
81 | `;
82 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
83 | "unknown: Unsupported use. Supported uses are: style9(), style9.create(), and style9.keyframes()
84 | 1 |
85 | 2 | import style9 from 'style9';
86 | > 3 | style9.foo;
87 | | ^^^^^^
88 | 4 | "
89 | `);
90 | });
91 |
92 | it('create throws when called without arguments', () => {
93 | const input = `
94 | import style9 from 'style9';
95 | style9.create();
96 | `;
97 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
98 | "unknown: Unsupported use. Supported uses are: style9(), style9.create(), and style9.keyframes()
99 | 1 |
100 | 2 | import style9 from 'style9';
101 | > 3 | style9.create();
102 | | ^^^^^^
103 | 4 | "
104 | `);
105 | });
106 |
107 | it('create throws when called multiple arguments', () => {
108 | const input = `
109 | import style9 from 'style9';
110 | style9.create({}, {});
111 | `;
112 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
113 | "unknown: Unsupported use. Supported uses are: style9(), style9.create(), and style9.keyframes()
114 | 1 |
115 | 2 | import style9 from 'style9';
116 | > 3 | style9.create({}, {});
117 | | ^^^^^^
118 | 4 | "
119 | `);
120 | });
121 |
122 | it('create throws non-object argument', () => {
123 | const input = `
124 | import style9 from 'style9';
125 | style9.create(1);
126 | `;
127 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
128 | "unknown: Unsupported use. Supported uses are: style9(), style9.create(), and style9.keyframes()
129 | 1 |
130 | 2 | import style9 from 'style9';
131 | > 3 | style9.create(1);
132 | | ^^^^^^
133 | 4 | "
134 | `);
135 | });
136 |
137 | it('styles throws on non-existing style key', () => {
138 | const input = `
139 | import style9 from 'style9';
140 | const styles = style9.create({
141 | default: {
142 | color: 'blue'
143 | }
144 | });
145 | styles('blue');
146 | `;
147 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
148 | "unknown: Property blue does not exist in style object
149 | 6 | }
150 | 7 | });
151 | > 8 | styles('blue');
152 | | ^^^^^^
153 | 9 | "
154 | `);
155 | });
156 |
157 | it('styles throws on unsupported operator', () => {
158 | const input = `
159 | import style9 from 'style9';
160 | const styles = style9.create({
161 | default: {
162 | color: 'blue'
163 | }
164 | });
165 | styles(foo & 'blue');
166 | `;
167 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
168 | "unknown: Unsupported type BinaryExpression
169 | 6 | }
170 | 7 | });
171 | > 8 | styles(foo & 'blue');
172 | | ^^^^^^^^^^^^
173 | 9 | "
174 | `);
175 | });
176 |
177 | it('styles throws on failure to evaluate values', () => {
178 | const input = `
179 | import style9 from 'style9';
180 | const styles = style9.create({
181 | default: {
182 | color: BLUE
183 | }
184 | });
185 | styles('blue');
186 | `;
187 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
188 | "unknown: Could not evaluate value
189 | 3 | const styles = style9.create({
190 | 4 | default: {
191 | > 5 | color: BLUE
192 | | ^^^^
193 | 6 | }
194 | 7 | });
195 | 8 | styles('blue');"
196 | `);
197 | });
198 |
199 | it('styles throws on spread', () => {
200 | const input = `
201 | import style9 from 'style9';
202 | const styles = style9.create({
203 | default: {
204 | color: 'red'
205 | }
206 | });
207 | styles({ ...foo })
208 | `;
209 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
210 | "unknown: Unsupported type SpreadElement
211 | 6 | }
212 | 7 | });
213 | > 8 | styles({ ...foo })
214 | | ^^^^^^
215 | 9 | "
216 | `);
217 | });
218 |
219 | it('styles throws non-string logical right hand', () => {
220 | const input = `
221 | import style9 from 'style9';
222 | const styles = style9.create({
223 | red: {
224 | color: 'red'
225 | }
226 | });
227 | styles(foo && red)
228 | `;
229 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
230 | "unknown: Unsupported type Identifier
231 | 6 | }
232 | 7 | });
233 | > 8 | styles(foo && red)
234 | | ^^^
235 | 9 | "
236 | `);
237 | });
238 |
239 | it('styles throws non-string ternary left hand', () => {
240 | const input = `
241 | import style9 from 'style9';
242 | const styles = style9.create({
243 | red: {
244 | color: 'red'
245 | }
246 | });
247 | styles(foo ? red : 'red')
248 | `;
249 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
250 | "unknown: Unsupported type Identifier
251 | 6 | }
252 | 7 | });
253 | > 8 | styles(foo ? red : 'red')
254 | | ^^^
255 | 9 | "
256 | `);
257 | });
258 |
259 | it('styles throws non-string ternary right hand', () => {
260 | const input = `
261 | import style9 from 'style9';
262 | const styles = style9.create({
263 | red: {
264 | color: 'red'
265 | }
266 | });
267 | styles(foo ? 'red' : red)
268 | `;
269 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
270 | "unknown: Unsupported type Identifier
271 | 6 | }
272 | 7 | });
273 | > 8 | styles(foo ? 'red' : red)
274 | | ^^^
275 | 9 | "
276 | `);
277 | });
278 |
279 | it('styles throws on identifier', () => {
280 | const input = `
281 | import style9 from 'style9';
282 | const styles = style9.create({
283 | default: {
284 | color: 'red'
285 | }
286 | });
287 | styles(foo)
288 | `;
289 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
290 | "unknown: Unsupported type Identifier
291 | 6 | }
292 | 7 | });
293 | > 8 | styles(foo)
294 | | ^^^
295 | 9 | "
296 | `);
297 | });
298 |
299 | it('styles throws on dynamic key', () => {
300 | const input = `
301 | import style9 from 'style9';
302 | const styles = style9.create({
303 | red: {
304 | color: 'red'
305 | }
306 | });
307 | styles({ [red]: foo })
308 | `;
309 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
310 | "unknown: Unsupported type ObjectProperty
311 | 6 | }
312 | 7 | });
313 | > 8 | styles({ [red]: foo })
314 | | ^^^^^^^^^^
315 | 9 | "
316 | `);
317 | });
318 |
319 | it('throws on unsupported logical expression', () => {
320 | const input = `
321 | import style9 from 'style9';
322 | const styles = style9.create({
323 | red: {
324 | color: 'red'
325 | }
326 | });
327 | styles(foo || red)
328 | `;
329 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
330 | "unknown: Unsupported type LogicalExpression
331 | 6 | }
332 | 7 | });
333 | > 8 | styles(foo || red)
334 | | ^^^^^^^^^^
335 | 9 | "
336 | `);
337 | });
338 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const pluginTester = require('babel-plugin-tester').default;
3 | const plugin = require('../../babel.js');
4 |
5 | pluginTester({
6 | plugin,
7 | pluginName: 'style9',
8 | fixtures: path.join(__dirname, 'fixtures'),
9 | babelOptions: {
10 | parserOpts: {
11 | plugins: ['typescript']
12 | }
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/complex-ternary/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | not_used: {
4 | padding: '0',
5 | cursor: 'pointer'
6 | },
7 | base: {
8 | pointerEvents: 'none',
9 | ':focus': {
10 | outline: 'none'
11 | }
12 | },
13 | enabled: {
14 | color: 'green'
15 | },
16 | disabled: {
17 | color: 'red',
18 | cursor: 'not-allowed'
19 | },
20 | hover: {
21 | ':hover': {
22 | color: 'blue'
23 | }
24 | },
25 | other: {
26 | backgroundColor: 'red'
27 | }
28 | });
29 |
30 | styles('base', checked ? 'enabled' : 'disabled', 'hover', 'other');
31 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/complex-ternary/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | 'fYFPyg eDssNQ larHMv ' + ((checked ? 'ciOZAH ' : 'RCRUH ') + 'kKPCVy cGdbor ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/destructuring-assignment/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const { blue } = style9.create({
3 | blue: {
4 | color: 'blue'
5 | },
6 | red: {
7 | color: 'red'
8 | }
9 | });
10 | console.log(blue);
11 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/destructuring-assignment/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const { blue } = {
3 | blue: {
4 | color: 'hxxstI'
5 | }
6 | };
7 | console.log(blue);
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/dynamic-bracket-access/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | blue: {
4 | color: 'blue'
5 | },
6 | red: {
7 | color: 'red'
8 | }
9 | })[blue];
10 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/dynamic-bracket-access/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {
3 | blue: {
4 | color: 'hxxstI'
5 | },
6 | red: {
7 | color: 'RCRUH'
8 | }
9 | }[blue];
10 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/empty-call/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue'
5 | }
6 | });
7 | styles();
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/empty-call/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/hoists-arrow-function-call/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue'
5 | }
6 | });
7 | const get = state => styles(state() && 'default');
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/hoists-arrow-function-call/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 |
4 | const get = state => {
5 | const _state = state();
6 |
7 | return _state ? 'hxxstI ' : '';
8 | };
9 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/hoists-block-function-call/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue'
5 | }
6 | });
7 | {
8 | styles({
9 | default: foo()
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/hoists-block-function-call/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | {
4 | const _foo = foo();
5 |
6 | _foo ? 'hxxstI ' : '';
7 | }
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/hoists-function-call/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue'
5 | }
6 | });
7 | styles({
8 | default: foo()
9 | });
10 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/hoists-function-call/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 |
4 | const _foo = foo();
5 |
6 | _foo ? 'hxxstI ' : '';
7 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/keeps-multiple-instances-of-same-value/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue'
5 | },
6 | blue: {
7 | color: 'blue'
8 | }
9 | });
10 | styles(false && 'default', true && 'blue');
11 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/keeps-multiple-instances-of-same-value/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | const _false = false;
4 | const _true = true;
5 | _true ? 'hxxstI ' : _false ? 'hxxstI ' : '';
6 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/member-expression-access/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const blue = style9.create({
3 | blue: {
4 | color: 'blue'
5 | },
6 | red: {
7 | color: 'red'
8 | }
9 | }).blue;
10 | console.log(blue);
11 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/member-expression-access/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const blue = {
3 | blue: {
4 | color: 'hxxstI'
5 | }
6 | }.blue;
7 | console.log(blue);
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/mixed/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue',
5 | opacity: 1
6 | },
7 | red: {
8 | color: 'red'
9 | }
10 | });
11 | styles(
12 | {
13 | default: foo
14 | },
15 | 'red'
16 | );
17 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/mixed/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | 'RCRUH ' + (foo ? 'gOeSjL ' : '');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/moves-test/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 |
3 | const styles = style9.create({
4 | default: {
5 | color: 'blue'
6 | }
7 | });
8 |
9 | styles(foo() && 'default');
10 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/moves-test/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 |
4 | const _foo = foo();
5 |
6 | _foo ? 'hxxstI ' : '';
7 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/no-keys/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 |
3 | const styles = style9.create({
4 | default: {
5 | color: 'red'
6 | }
7 | });
8 |
9 | styles();
10 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/no-keys/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/object/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue',
5 | opacity: 1
6 | },
7 | red: {
8 | color: 'red'
9 | }
10 | });
11 | styles({
12 | default: foo,
13 | red: bar
14 | });
15 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/object/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | (bar ? 'RCRUH ' : foo ? 'hxxstI ' : '') + (foo ? 'gOeSjL ' : '');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/property-access/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles1 = style9.create({
3 | default: {
4 | color: 'blue'
5 | }
6 | });
7 | const styles2 = style9.create({
8 | red: {
9 | color: 'red'
10 | }
11 | });
12 | style9(styles1.default, styles2.red);
13 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/property-access/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles1 = {
3 | default: {
4 | color: 'hxxstI'
5 | }
6 | };
7 | const styles2 = {
8 | red: {
9 | color: 'RCRUH'
10 | }
11 | };
12 | style9(styles1.default, styles2.red);
13 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/short-circuits-same-value/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue'
5 | },
6 | blue: {
7 | color: 'blue'
8 | },
9 | red: {
10 | color: 'red'
11 | }
12 | });
13 | styles('blue', foo && 'default', bar && 'red');
14 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/short-circuits-same-value/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | bar ? 'RCRUH ' : 'hxxstI ';
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/spread-assignment/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const { ...styles } = style9.create({
3 | blue: {
4 | color: 'blue'
5 | }
6 | });
7 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/spread-assignment/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const { ...styles } = {
3 | blue: {
4 | color: 'hxxstI'
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/spread-use/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | blue: {
4 | color: 'blue'
5 | }
6 | });
7 | console.log({ ...styles });
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/spread-use/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {
3 | blue: {
4 | color: 'hxxstI'
5 | }
6 | };
7 | console.log({ ...styles });
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/static-bracket-access/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const blue = style9.create({
3 | blue: {
4 | color: 'blue'
5 | },
6 | red: {
7 | color: 'red'
8 | }
9 | })['blue'];
10 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/static-bracket-access/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const blue = {
3 | blue: {
4 | color: 'hxxstI'
5 | }
6 | }['blue'];
7 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/string-literal/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 |
3 | const styles = style9.create({
4 | default: {
5 | color: 'blue',
6 | opacity: 1
7 | },
8 | red: {
9 | color: 'red'
10 | }
11 | });
12 |
13 | styles('default', 'red');
14 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/string-literal/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('RCRUH gOeSjL ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/ternary/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue',
5 | opacity: 1
6 | },
7 | red: {
8 | color: 'red'
9 | }
10 | });
11 | styles(foo ? 'default' : 'red');
12 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/classes/ternary/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | (foo ? 'hxxstI ' : 'RCRUH ') + (foo ? 'gOeSjL ' : '');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/custom-properties/does-not-change-capitalization/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | '--backgroundColor': 'red'
5 | }
6 | });
7 | styles('default');
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/custom-properties/does-not-change-capitalization/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('hJKoGo ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/custom-properties/does-not-convert-number/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | '--opacity': 1
5 | }
6 | });
7 | styles('default');
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/custom-properties/does-not-convert-number/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('jVMKrZ ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/import/ignore-other-imports/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style8';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue'
5 | }
6 | });
7 | styles('default');
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/import/ignore-other-imports/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style8';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue'
5 | }
6 | });
7 | styles('default');
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/incremental-classnames/generated-classname/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | paddingLeft: 2,
5 | paddingTop: 1
6 | },
7 | other: {
8 | paddingRight: 3
9 | }
10 | });
11 | styles('default', 'other');
12 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/incremental-classnames/generated-classname/options.json:
--------------------------------------------------------------------------------
1 | { "incrementalClassnames": true }
2 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/incremental-classnames/generated-classname/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('a b c ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/incremental-classnames/object-classname/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | paddingLeft: 2,
5 | paddingTop: 1
6 | },
7 | other: {
8 | paddingRight: 3
9 | }
10 | });
11 | styles.default;
12 | styles.other;
13 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/incremental-classnames/object-classname/options.json:
--------------------------------------------------------------------------------
1 | { "incrementalClassnames": true }
2 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/incremental-classnames/object-classname/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {
3 | default: {
4 | paddingLeft: 'a',
5 | paddingTop: 'b'
6 | },
7 | other: {
8 | paddingRight: 'c'
9 | }
10 | };
11 | styles.default;
12 | styles.other;
13 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/keyframes/basic/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | style9.keyframes({
3 | '0%': {
4 | color: 'blue'
5 | },
6 | '100%': {}
7 | });
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/keyframes/basic/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | ('duuCUn');
3 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/keyframes/converts-from/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | style9.keyframes({
3 | from: {
4 | color: 'blue'
5 | }
6 | });
7 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/keyframes/converts-from/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | ('duuCUn');
3 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/keyframes/converts-to/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | style9.keyframes({
3 | to: {
4 | color: 'blue'
5 | }
6 | });
7 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/keyframes/converts-to/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | ('lkltCV');
3 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/keyframes/setting-animationName-directly/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | animationName: style9.keyframes({
5 | '0%': {
6 | opacity: 0
7 | }
8 | })
9 | }
10 | });
11 | styles.default;
12 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/keyframes/setting-animationName-directly/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {
3 | default: {
4 | animationName: 'flTQwj'
5 | }
6 | };
7 | styles.default;
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/minify-properties/does-not-minify-by-default/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | opacity: 1
5 | }
6 | });
7 | styles.default;
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/minify-properties/does-not-minify-by-default/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {
3 | default: {
4 | opacity: 'gOeSjL'
5 | }
6 | };
7 | styles.default;
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/minify-properties/hashes-unknown-properties/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | foo: 'bar'
5 | }
6 | });
7 | styles.default;
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/minify-properties/hashes-unknown-properties/options.json:
--------------------------------------------------------------------------------
1 | { "minifyProperties": true }
2 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/minify-properties/hashes-unknown-properties/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {
3 | default: {
4 | eicqJS: 'knxHJH'
5 | }
6 | };
7 | styles.default;
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/minify-properties/minifies-known-properties/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | opacity: 1
5 | }
6 | });
7 | styles.default;
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/minify-properties/minifies-known-properties/options.json:
--------------------------------------------------------------------------------
1 | { "minifyProperties": true }
2 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/minify-properties/minifies-known-properties/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {
3 | default: {
4 | j9: 'gOeSjL'
5 | }
6 | };
7 | styles.default;
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/minify-properties/minifies-nested-properties/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | '@media (max-width: 1000px)': {
5 | '::before': {
6 | opacity: 1
7 | }
8 | }
9 | }
10 | });
11 | styles.default;
12 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/minify-properties/minifies-nested-properties/options.json:
--------------------------------------------------------------------------------
1 | { "minifyProperties": true }
2 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/minify-properties/minifies-nested-properties/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {
3 | default: {
4 | cagpug: {
5 | gYHkBN: {
6 | j9: 'kvroMm'
7 | }
8 | }
9 | }
10 | };
11 | styles.default;
12 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/at-rule-key/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | '@supports': {
5 | '(opacity: 1)': {
6 | opacity: 1,
7 | '@media': {
8 | '(max-width: 1000px)': {
9 | opacity: 1
10 | }
11 | }
12 | }
13 | }
14 | }
15 | });
16 | styles.default;
17 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/at-rule-key/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {
3 | default: {
4 | '@supports (opacity: 1)': {
5 | opacity: 'ksLciA',
6 | '@media (max-width: 1000px)': {
7 | opacity: 'keyTYY'
8 | }
9 | }
10 | }
11 | };
12 | styles.default;
13 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/at-rules/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | '@media (max-width: 1000px)': {
5 | opacity: 1
6 | },
7 | '@supports (color: blue)': {
8 | color: 'blue'
9 | }
10 | }
11 | });
12 | styles('default');
13 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/at-rules/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('Bbwnu cCpNNg ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/basic/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | '::before': {
5 | opacity: 1
6 | }
7 | }
8 | });
9 | styles('default');
10 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/basic/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('dLppjJ ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/deep/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | '@media (max-width: 1000px)': {
5 | ':hover': {
6 | '::before': {
7 | opacity: 1
8 | }
9 | }
10 | }
11 | }
12 | });
13 | styles('default');
14 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/deep/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('fViSUe ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/generates-correct-class-names/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | '::before': {
5 | opacity: 1
6 | }
7 | },
8 | hidden: {
9 | '::before': {
10 | opacity: 0
11 | }
12 | }
13 | });
14 | styles('default', 'hidden');
15 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/generates-correct-class-names/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('diXuqL ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/translates-old-pseudo-element/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | ':before': { opacity: 1 },
5 | ':after': { opacity: 1 },
6 | ':first-letter': { opacity: 1 },
7 | ':first-line': { opacity: 1 }
8 | }
9 | });
10 | styles.default;
11 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/nesting/translates-old-pseudo-element/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {
3 | default: {
4 | '::before': {
5 | opacity: 'dLppjJ'
6 | },
7 | '::after': {
8 | opacity: 'kMNmYO'
9 | },
10 | '::first-letter': {
11 | opacity: 'ezsObI'
12 | },
13 | '::first-line': {
14 | opacity: 'iaGYxt'
15 | }
16 | }
17 | };
18 | styles.default;
19 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/typescript/casting/code.ts:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | ['--bg-color' as any]: 'blue'
5 | }
6 | });
7 | styles('default');
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/typescript/casting/output.ts:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('eMOIeF ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/values/arrow-function/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue'
5 | }
6 | });
7 | const get = state => styles(state && 'default');
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/values/arrow-function/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 |
4 | const get = state => (state ? 'hxxstI ' : '');
5 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/values/expands-shorthand-in-nesting/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | '::before': {
5 | padding: '1rem'
6 | }
7 | }
8 | });
9 | styles('default');
10 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/values/expands-shorthand-in-nesting/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('gDvCuo cmluEy fyByHi isyUlq ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/values/expands-shorthand/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | padding: '1rem'
5 | }
6 | });
7 | styles('default');
8 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/values/expands-shorthand/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('jWWtke ftIldC bnHxUw iDuqPI ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/values/keeps-longhand/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | paddingTop: '.5rem',
5 | padding: '1rem',
6 | paddingLeft: '2rem'
7 | }
8 | });
9 | styles('default');
10 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/values/keeps-longhand/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {};
3 | ('lcGuBB ftIldC bnHxUw iigETV ');
4 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/values/removes-unused-keys/code.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = style9.create({
3 | default: {
4 | color: 'blue'
5 | },
6 | red: {
7 | color: 'red'
8 | },
9 | yellow: {
10 | color: 'yellow'
11 | }
12 | });
13 | styles('default');
14 | styles.red;
15 |
--------------------------------------------------------------------------------
/__tests__/code/fixtures/values/removes-unused-keys/output.js:
--------------------------------------------------------------------------------
1 | import style9 from 'style9';
2 | const styles = {
3 | red: {
4 | color: 'RCRUH'
5 | }
6 | };
7 | ('hxxstI ');
8 | styles.red;
9 |
--------------------------------------------------------------------------------
/__tests__/code/import.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const compile = require('../compile.js');
3 |
4 | it('ignores other imports', () => {
5 | const input = `import style9 from 'other';`;
6 | const { code } = compile(input);
7 | expect(code).toBe(input);
8 | });
9 |
--------------------------------------------------------------------------------
/__tests__/code/nesting.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const compile = require('../compile.js');
3 |
4 | it('throws on invalid nesting', () => {
5 | const input = `
6 | import style9 from 'style9';
7 | const styles = style9.create({
8 | default: {
9 | foo: {
10 | opacity: 1
11 | }
12 | }
13 | });
14 | `;
15 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
16 | "unknown: Invalid key foo. Object keys must be at-rules or pseudo selectors
17 | 3 | const styles = style9.create({
18 | 4 | default: {
19 | > 5 | foo: {
20 | | ^^^
21 | 6 | opacity: 1
22 | 7 | }
23 | 8 | }"
24 | `);
25 | });
26 |
27 | it('throws on invalid nesting with string literal key', () => {
28 | const input = `
29 | import style9 from 'style9';
30 | const styles = style9.create({
31 | default: {
32 | 'foo': {
33 | opacity: 1
34 | }
35 | }
36 | });
37 | `;
38 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
39 | "unknown: Invalid key foo. Object keys must be at-rules or pseudo selectors
40 | 3 | const styles = style9.create({
41 | 4 | default: {
42 | > 5 | 'foo': {
43 | | ^^^^^
44 | 6 | opacity: 1
45 | 7 | }
46 | 8 | }"
47 | `);
48 | });
49 |
50 | it('throws on invalid nesting with dynamic key', () => {
51 | const input = `
52 | import style9 from 'style9';
53 | const foo = 'bar';
54 | const styles = style9.create({
55 | default: {
56 | [foo]: {
57 | opacity: 1
58 | }
59 | }
60 | });
61 | `;
62 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
63 | "unknown: Invalid key bar. Object keys must be at-rules or pseudo selectors
64 | 4 | const styles = style9.create({
65 | 5 | default: {
66 | > 6 | [foo]: {
67 | | ^^^
68 | 7 | opacity: 1
69 | 8 | }
70 | 9 | }"
71 | `);
72 | });
73 |
74 | it('throws when failing to evaluate key', () => {
75 | const input = `
76 | import style9 from 'style9';
77 | const styles = style9.create({
78 | default: {
79 | [foo]: {
80 | opacity: 1
81 | }
82 | }
83 | });
84 | `;
85 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
86 | "unknown: Could not evaluate value
87 | 3 | const styles = style9.create({
88 | 4 | default: {
89 | > 5 | [foo]: {
90 | | ^^^
91 | 6 | opacity: 1
92 | 7 | }
93 | 8 | }"
94 | `);
95 | });
96 |
97 | it('throws on spread object', () => {
98 | const input = `
99 | import style9 from 'style9';
100 | const foo = {};
101 | const styles = style9.create({
102 | ...foo
103 | });
104 | `;
105 | expect(() => compile(input)).toThrowErrorMatchingInlineSnapshot(`
106 | "unknown: Could not evaluate value
107 | 3 | const foo = {};
108 | 4 | const styles = style9.create({
109 | > 5 | ...foo
110 | | ^^^^^^
111 | 6 | });
112 | 7 | "
113 | `);
114 | });
115 |
--------------------------------------------------------------------------------
/__tests__/compile.js:
--------------------------------------------------------------------------------
1 | const babel = require('@babel/core');
2 | const plugin = require('../babel.js');
3 |
4 | function compile(input, opts = {}) {
5 | const {
6 | code,
7 | ast,
8 | metadata: { style9: styles }
9 | } = babel.transformSync(input, {
10 | plugins: [[plugin, opts]],
11 | highlightCode: false
12 | });
13 |
14 | return { code, ast, styles };
15 | }
16 |
17 | module.exports = compile;
18 |
--------------------------------------------------------------------------------
/__tests__/css-sorter.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const processCSS = require('../src/process-css.js');
3 |
4 | const CASES = [
5 | {
6 | name: 'basic',
7 | input: '.a:hover{opacity:1}' + '.b{opacity:1}',
8 | expected: '.b{opacity:1}' + '.a:hover{opacity:1}'
9 | },
10 | {
11 | name: 'pseudo order',
12 | input:
13 | '.i:active{opacity:1}' +
14 | '.k:disabled{opacity:1}' +
15 | '.f:even-child{opacity:1}' +
16 | '.c:first-child{opacity:1}' +
17 | '.b:focus-within{opacity:1}' +
18 | '.h:focus{opacity:1}' +
19 | '.g:hover{opacity:1}' +
20 | '.d:last-child{opacity:1}' +
21 | '.a:link{opacity:1}' +
22 | '.z:unknown{opacity:1}' +
23 | '.e:odd-child{opacity:1}' +
24 | '.j:visited{opacity:1}',
25 | expected:
26 | '.z:unknown{opacity:1}' +
27 | '.a:link{opacity:1}' +
28 | '.b:focus-within{opacity:1}' +
29 | '.c:first-child{opacity:1}' +
30 | '.d:last-child{opacity:1}' +
31 | '.e:odd-child{opacity:1}' +
32 | '.f:even-child{opacity:1}' +
33 | '.g:hover{opacity:1}' +
34 | '.h:focus{opacity:1}' +
35 | '.i:active{opacity:1}' +
36 | '.j:visited{opacity:1}' +
37 | '.k:disabled{opacity:1}'
38 | },
39 | {
40 | name: 'first pseudo',
41 | input: '.b:active:hover{opacity:1}' + '.a:hover{opacity:1}',
42 | expected: '.a:hover{opacity:1}' + '.b:active:hover{opacity:1}'
43 | },
44 | {
45 | name: 'mobile first',
46 | input:
47 | '@media (min-width: 200px){.b{opacity:1}}' +
48 | '@media (min-width: 100px){.a{opacity:1}}' +
49 | '.c{opacity:1}',
50 | expected:
51 | '.c{opacity:1}' +
52 | '@media (min-width: 100px){.a{opacity:1}}' +
53 | '@media (min-width: 200px){.b{opacity:1}}'
54 | },
55 | {
56 | name: 'pseudo order before media query',
57 | input:
58 | '@media (max-width: 200px){.b:active{opacity:1}}' +
59 | '@media (max-width: 100px){.a:hover{opacity:1}}',
60 | expected:
61 | '@media (max-width: 100px){.a:hover{opacity:1}}' +
62 | '@media (max-width: 200px){.b:active{opacity:1}}'
63 | },
64 | {
65 | name: 'nested media query',
66 | input:
67 | '@media (max-width: 100px){@media (min-height: 100px){.a{opacity:1}}}' +
68 | '@media (max-width: 200px){@media (min-height: 200px){.b{opacity:1}}}',
69 | expected:
70 | '@media (max-width: 200px){@media (min-height: 200px){.b{opacity:1}}}' +
71 | '@media (max-width: 100px){@media (min-height: 100px){.a{opacity:1}}}'
72 | },
73 | {
74 | name: 'ignore @supports',
75 | input: '@supports (display: block){.b{opacity:1}}' + '.a{opacity:1}',
76 | expected: '@supports (display: block){.b{opacity:1}}' + '.a{opacity:1}'
77 | },
78 | {
79 | name: 'splits multiple properties',
80 | input: '.a{color:red;opacity:1}',
81 | expected: '.a{color:red}' + '.a{opacity:1}'
82 | },
83 | {
84 | name: 'preseveres same pseudo order',
85 | input: '.a:hover{opacity:1}' + '.b:hover{opacity:0}',
86 | expected: '.a:hover{opacity:1}' + '.b:hover{opacity:0}'
87 | },
88 | {
89 | name: 'ignores pseudo element when sorting',
90 | input: '.a::before:hover{opacity:1}' + '.b:focus{opacity:0}',
91 | expected: '.a::before:hover{opacity:1}' + '.b:focus{opacity:0}'
92 | },
93 | {
94 | name: 'sorts longhands after shorthands',
95 | input:
96 | '.a{padding-top:2px}' +
97 | '.b{padding:1px}' +
98 | '.c{border-top-width:2px}' +
99 | '.d{border-top:1px}' +
100 | '.e{border:2px solid red}',
101 | expected:
102 | '.b{padding:1px}' +
103 | '.e{border:2px solid red}' +
104 | '.a{padding-top:2px}' +
105 | '.d{border-top:1px}' +
106 | '.c{border-top-width:2px}'
107 | },
108 | {
109 | name: 'ignore atrule',
110 | input: '@-ms-viewport {width:device-width}' + '.a{opacity:1}',
111 | expected: '@-ms-viewport {width:device-width}' + '.a{opacity:1}'
112 | },
113 | {
114 | name: 'pseudo order in media queries',
115 | input:
116 | '@media (min-width: 200px){.b:disabled{opacity:1}}' +
117 | '@media (min-width: 100px){.a:disabled{opacity:1}}',
118 | expected:
119 | '@media (min-width: 100px){.a:disabled{opacity:1}}' +
120 | '@media (min-width: 200px){.b:disabled{opacity:1}}'
121 | }
122 | ];
123 |
124 | const IGNORE = [
125 | {
126 | name: 'selector list',
127 | input: '.foo, .bar{color:red} .bar, .foo{color:blue}'
128 | },
129 | {
130 | name: 'non-class selectors',
131 | input: '[disabled]{color:red} .foo[disabled]{color:red}'
132 | },
133 | {
134 | name: 'multiple selectors',
135 | input: '.foo.bar{color:red} .foo.bar.baz{color:red}'
136 | }
137 | ];
138 |
139 | for (const { name, input, expected } of CASES) {
140 | it(name, () => {
141 | expect(processCSS(input).css).toEqual(expected);
142 | });
143 | }
144 |
145 | for (const { name, input } of IGNORE) {
146 | it(name, () => {
147 | expect(processCSS(input).css).toEqual(input);
148 | });
149 | }
150 |
151 | it('supports setting from parameter', () => {
152 | const from = 'testfile.css';
153 | expect(processCSS('', { from }).result.opts.from).toEqual(from);
154 | });
155 |
--------------------------------------------------------------------------------
/__tests__/incremental-classnames.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const createGenerator = require('../src/utils/incremental-classnames');
3 |
4 | it('generates unique classnames', () => {
5 | const { getIncrementalClass } = createGenerator();
6 | const input = new Array(5000).fill().map((_, index) => String(index));
7 | const output = input.map(getIncrementalClass);
8 | const duplicates = output.filter((cls, i) => output.indexOf(cls) !== i);
9 | expect(duplicates).toEqual([]);
10 | expect(output.join('')).toEqual(expect.not.stringContaining('undefined'));
11 | const startsWithNumber = output.filter(cls => /^[0-9]/.test(cls));
12 | expect(startsWithNumber).toEqual([]);
13 | expect(output).toMatchSnapshot();
14 | const ALL_CHARS = (
15 | 'abcdefghijklmnopqrstuvwxyz' +
16 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
17 | '0123456789'
18 | ).split('');
19 | const notIncluded = ALL_CHARS.filter(c => !output.join('').includes(c));
20 | expect(notIncluded).toEqual([]);
21 | });
22 |
--------------------------------------------------------------------------------
/__tests__/resolver.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const style9 = require('../index.js').default;
3 |
4 | it('combines different properties', () => {
5 | const input = {
6 | a: {
7 | foo: 'foo'
8 | },
9 | b: {
10 | bar: 'bar'
11 | }
12 | };
13 | expect(style9(input.a, input.b)).toBe('foo bar');
14 | });
15 |
16 | it('merges from right to left', () => {
17 | const input = {
18 | a: {
19 | foo: 'foo'
20 | },
21 | b: {
22 | foo: 'bar'
23 | }
24 | };
25 | expect(style9(input.a, input.b)).toBe('bar');
26 | });
27 |
28 | it('ignores falsy values', () => {
29 | const input = {
30 | a: {
31 | foo: 'foo'
32 | }
33 | };
34 | expect(style9(input.a, false, undefined, null)).toBe('foo');
35 | });
36 |
37 | it('handles nested objects', () => {
38 | const input = {
39 | a: {
40 | foo: 'foo',
41 | first: {
42 | foo: 'baz'
43 | }
44 | },
45 | b: {
46 | foo: 'bar'
47 | }
48 | };
49 | expect(style9(input.a, input.b)).toBe('bar baz');
50 | });
51 |
52 | it('merges nested objects', () => {
53 | const input = {
54 | a: {
55 | foo: 'foo',
56 | first: {
57 | foo: 'baz'
58 | }
59 | },
60 | b: {
61 | foo: 'bar',
62 | first: {
63 | foo: 'biz'
64 | }
65 | }
66 | };
67 | expect(style9(input.a, input.b)).toBe('bar biz');
68 | });
69 |
70 | it('handles deeply nested objects', () => {
71 | const input = {
72 | a: {
73 | foo: 'foo',
74 | first: {
75 | foo: 'baz',
76 | second: {
77 | foo: 'bop'
78 | }
79 | }
80 | },
81 | b: {
82 | foo: 'bar'
83 | }
84 | };
85 | expect(style9(input.a, input.b)).toBe('bar baz bop');
86 | });
87 |
88 | it('merges deeply nested objects', () => {
89 | const input = {
90 | a: {
91 | foo: 'foo',
92 | first: {
93 | foo: 'baz',
94 | second: {
95 | foo: 'bop'
96 | }
97 | }
98 | },
99 | b: {
100 | foo: 'bar',
101 | first: {
102 | foo: 'biz',
103 | second: {
104 | foo: 'bip'
105 | }
106 | }
107 | }
108 | };
109 | expect(style9(input.a, input.b)).toBe('bar biz bip');
110 | });
111 |
112 | it('merges several deeply nested objects', () => {
113 | const input = {
114 | a: {
115 | foo: 'foo',
116 | first: {
117 | foo: 'baz',
118 | second: {
119 | foo: 'bop'
120 | }
121 | }
122 | },
123 | b: {
124 | foo: 'bar',
125 | first: {
126 | foo: 'biz',
127 | second: {
128 | foo: 'bip'
129 | }
130 | }
131 | },
132 | c: {
133 | foo: 'bup',
134 | first: {
135 | foo: 'bap'
136 | }
137 | }
138 | };
139 | expect(style9(input.a, input.b, input.c)).toBe('bup bap bip');
140 | });
141 |
142 | it('does not modify objects', () => {
143 | const input = {
144 | a: {
145 | foo: 'foo',
146 | first: {
147 | foo: 'baz',
148 | second: {
149 | foo: 'bop'
150 | }
151 | }
152 | },
153 | b: {
154 | foo: 'bar',
155 | first: {
156 | foo: 'biz',
157 | second: {
158 | foo: 'bip'
159 | }
160 | }
161 | }
162 | };
163 | const clone = JSON.parse(JSON.stringify(input));
164 | style9(input.a, input.b);
165 | expect(input).toEqual(clone);
166 | });
167 |
168 | it('returns empty string when called without arguments', () => {
169 | expect(style9()).toBe('');
170 | });
171 |
172 | it('create should throw', () => {
173 | expect(() => style9.create({})).toThrow(
174 | new Error('style9.create calls should be compiled away')
175 | );
176 | });
177 |
178 | it('keyframes should throw', () => {
179 | expect(() => style9.keyframes({})).toThrow(
180 | new Error('style9.keyframes calls should be compiled away')
181 | );
182 | });
183 |
--------------------------------------------------------------------------------
/__tests__/styles/comma-separated-properties.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const compile = require('../compile.js');
3 |
4 | it('handles properties wich can be defined as lists correctly', () => {
5 | const input = `
6 | import style9 from 'style9';
7 | const styles = style9.create({
8 | default: {
9 | transitionProperty: ['opacity', 'transform'],
10 | transitionDuration: ['200ms', '300ms', '400ms'],
11 | transitionDelay: ['100ms', '200ms', '300ms'],
12 | transitionTimingFunction: ['ease-in', 'ease-out', 'ease-in-out'],
13 | strokeDasharray: [10, 100, 200],
14 | scrollSnapType: ['none', 'mandatory'],
15 | scrollSnapAlign: ['start', 'end']
16 | }
17 | });
18 | styles('default');
19 | `;
20 | const { styles } = compile(input);
21 |
22 | expect(styles).toBe(
23 | '.ivPgPH{transition-property:opacity,transform}' +
24 | '.cfCwqg{transition-duration:200ms,300ms,400ms}' +
25 | '.dEsdmn{transition-delay:100ms,200ms,300ms}' +
26 | '.genghA{transition-timing-function:ease-in,ease-out,ease-in-out}' +
27 | '.kViNob{stroke-dasharray:10 100 200}' +
28 | '.cwFPDc{scroll-snap-type:none mandatory}' +
29 | '.hboCrl{scroll-snap-align:start end}'
30 | );
31 | });
32 |
--------------------------------------------------------------------------------
/__tests__/styles/custom-properties.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const compile = require('../compile.js');
3 |
4 | it('does not convert number', () => {
5 | const input = `
6 | import style9 from 'style9';
7 | const styles = style9.create({
8 | default: {
9 | '--opacity': 1
10 | }
11 | });
12 | styles('default');
13 | `;
14 | const { styles } = compile(input);
15 |
16 | expect(styles).toBe('.jVMKrZ{--opacity:1}');
17 | });
18 |
19 | it('does not change capitalization', () => {
20 | const input = `
21 | import style9 from 'style9';
22 | const styles = style9.create({
23 | default: {
24 | '--backgroundColor': 'red'
25 | }
26 | });
27 | styles('default');
28 | `;
29 | const { styles } = compile(input);
30 |
31 | expect(styles).toBe('.hJKoGo{--backgroundColor:red}');
32 | });
33 |
--------------------------------------------------------------------------------
/__tests__/styles/incremental-classnames.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const compile = require('../compile.js');
3 |
4 | it('uses incremental classname for styles', () => {
5 | const input = `
6 | import style9 from 'style9';
7 | const styles = style9.create({
8 | default: {
9 | paddingLeft: 2,
10 | paddingTop: 1
11 | },
12 | other: {
13 | paddingRight: 3
14 | }
15 | });
16 | styles('default', 'other');
17 | `;
18 | const { styles } = compile(input, {
19 | incrementalClassnames: true
20 | });
21 |
22 | expect(styles).toBe(
23 | '.a{padding-left:2px}.b{padding-top:1px}.c{padding-right:3px}'
24 | );
25 | });
26 |
--------------------------------------------------------------------------------
/__tests__/styles/keyframes.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const compile = require('../compile.js');
3 |
4 | it('generates keyframes', () => {
5 | const input = `
6 | import style9 from 'style9';
7 | style9.keyframes({
8 | '0%': {
9 | color: 'blue'
10 | },
11 | '100%': {
12 | color: 'red'
13 | }
14 | });
15 | `;
16 | const { styles } = compile(input);
17 |
18 | expect(styles).toBe('@keyframes gzOBtW{0%{color:blue}100%{color:red}}');
19 | });
20 |
21 | it('removes empty frame', () => {
22 | const input = `
23 | import style9 from 'style9';
24 | style9.keyframes({
25 | '0%': {
26 | color: 'blue'
27 | },
28 | '100%': {
29 | }
30 | });
31 | `;
32 | const { styles } = compile(input);
33 |
34 | expect(styles).toBe('@keyframes duuCUn{0%{color:blue}}');
35 | });
36 |
37 | it('converts from', () => {
38 | const input = `
39 | import style9 from 'style9';
40 | style9.keyframes({
41 | from: {
42 | color: 'blue'
43 | }
44 | });
45 | `;
46 | const { styles } = compile(input);
47 |
48 | expect(styles).toBe('@keyframes duuCUn{0%{color:blue}}');
49 | });
50 |
51 | it('converts to', () => {
52 | const input = `
53 | import style9 from 'style9';
54 | style9.keyframes({
55 | to: {
56 | color: 'blue'
57 | }
58 | });
59 | `;
60 | const { styles } = compile(input);
61 |
62 | expect(styles).toBe('@keyframes lkltCV{100%{color:blue}}');
63 | });
64 |
65 | it('expands shorthand', () => {
66 | const input = `
67 | import style9 from 'style9';
68 | style9.keyframes({
69 | '0%': {
70 | padding: '1rem',
71 | }
72 | });
73 | `;
74 | const { styles } = compile(input);
75 |
76 | expect(styles).toBe(
77 | '@keyframes dyyvIk{0%{' +
78 | 'padding-top:1rem;' +
79 | 'padding-right:1rem;' +
80 | 'padding-bottom:1rem;' +
81 | 'padding-left:1rem' +
82 | '}}'
83 | );
84 | });
85 |
86 | it('keeps longhand', () => {
87 | const input = `
88 | import style9 from 'style9';
89 | style9.keyframes({
90 | '0%': {
91 | padding: '1rem',
92 | paddingLeft: '2rem'
93 | }
94 | });
95 | `;
96 | const { styles } = compile(input);
97 |
98 | expect(styles).toBe(
99 | '@keyframes eTcyEi{0%{' +
100 | 'padding-top:1rem;' +
101 | 'padding-right:1rem;' +
102 | 'padding-bottom:1rem;' +
103 | 'padding-left:2rem' +
104 | '}}'
105 | );
106 | });
107 |
--------------------------------------------------------------------------------
/__tests__/styles/multiple-imports.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const compile = require('../compile.js');
3 |
4 | it('supports multiple imports', () => {
5 | const input = `
6 | import style0 from 'style9';
7 | import style1 from 'style9';
8 | const styles0 = style0.create({
9 | first: {
10 | color: 'blue'
11 | }
12 | });
13 | styles0('first');
14 | const styles1 = style1.create({
15 | second: {
16 | color: 'red'
17 | }
18 | });
19 | styles1('second');
20 | `;
21 | const { styles } = compile(input);
22 |
23 | expect(styles).toBe(`.hxxstI{color:blue}.RCRUH{color:red}`);
24 | });
25 |
--------------------------------------------------------------------------------
/__tests__/styles/nesting.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const compile = require('../compile.js');
3 |
4 | it('supports nesting', () => {
5 | const input = `
6 | import style9 from 'style9';
7 | const styles = style9.create({
8 | default: {
9 | '::before': {
10 | opacity: 1
11 | }
12 | }
13 | });
14 | styles('default');
15 | `;
16 | const { styles } = compile(input);
17 |
18 | expect(styles).toBe('.dLppjJ::before{opacity:1}');
19 | });
20 |
21 | it('supports at rules', () => {
22 | const input = `
23 | import style9 from 'style9';
24 | const styles = style9.create({
25 | default: {
26 | '@media (max-width: 1000px)': {
27 | opacity: 1
28 | },
29 | '@supports (color: blue)': {
30 | opacity: 'blue'
31 | }
32 | }
33 | });
34 | styles('default');
35 | `;
36 | const { styles } = compile(input);
37 |
38 | expect(styles).toBe(
39 | '@media (max-width: 1000px){.Bbwnu{opacity:1}}' +
40 | '@supports (color: blue){.ilahpL{opacity:blue}}'
41 | );
42 | });
43 |
44 | it('supports deep nesting', () => {
45 | const input = `
46 | import style9 from 'style9';
47 | const styles = style9.create({
48 | default: {
49 | '@media (max-width: 1000px)': {
50 | '@media (max-width: 200px)': {
51 | ':hover': {
52 | '::before': {
53 | opacity: 1
54 | }
55 | }
56 | }
57 | }
58 | }
59 | });
60 | styles('default');
61 | `;
62 | const { styles } = compile(input);
63 |
64 | expect(styles).toBe(
65 | '@media (max-width: 1000px){@media (max-width: 200px){.ClOOj:hover::before{opacity:1}}}'
66 | );
67 | });
68 |
69 | it('generates correct class names', () => {
70 | const input = `
71 | import style9 from 'style9';
72 | const styles = style9.create({
73 | default: {
74 | '::before': {
75 | opacity: 1
76 | }
77 | },
78 | hidden: {
79 | '::before': {
80 | opacity: 0
81 | }
82 | }
83 | });
84 | styles('default', 'hidden');
85 | `;
86 | const { styles } = compile(input);
87 |
88 | expect(styles).toBe('.dLppjJ::before{opacity:1}.diXuqL::before{opacity:0}');
89 | });
90 |
91 | it('translates old pseudo element', () => {
92 | const input = `
93 | import style9 from 'style9';
94 | const styles = style9.create({
95 | default: {
96 | ':before': { opacity: 1 },
97 | ':after': { opacity: 1 },
98 | ':first-letter': { opacity: 1 },
99 | ':first-line': { opacity: 1 }
100 | }
101 | });
102 | styles.default
103 | `;
104 | const { styles } = compile(input);
105 |
106 | expect(styles).toBe(
107 | '.dLppjJ::before{opacity:1}' +
108 | '.kMNmYO::after{opacity:1}' +
109 | '.ezsObI::first-letter{opacity:1}' +
110 | '.iaGYxt::first-line{opacity:1}'
111 | );
112 | });
113 |
--------------------------------------------------------------------------------
/__tests__/styles/transition-property.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const compile = require('../compile.js');
3 |
4 | it('converts transitionProperty to kebab-case', () => {
5 | const input = `
6 | import style9 from 'style9';
7 | const styles = style9.create({
8 | default: {
9 | transitionProperty: 'backgroundColor',
10 | }
11 | });
12 | styles('default');
13 | `;
14 | const { styles } = compile(input);
15 |
16 | expect(styles).toBe('.diErbW{transition-property:background-color}');
17 | });
18 |
19 | it('converts transitionProperty list to kebab-case', () => {
20 | const input = `
21 | import style9 from 'style9';
22 | const styles = style9.create({
23 | default: {
24 | transitionProperty: ['backgroundColor', 'borderColor', 'boxShadow'],
25 | }
26 | });
27 | styles('default');
28 | `;
29 | const { styles } = compile(input);
30 |
31 | expect(styles).toBe(
32 | '.gKERdg{transition-property:background-color,border-color,box-shadow}'
33 | );
34 | });
35 |
--------------------------------------------------------------------------------
/__tests__/styles/values.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | const compile = require('../compile.js');
3 |
4 | it('converts paddingLeft to pixels', () => {
5 | const input = `
6 | import style9 from 'style9';
7 | const styles = style9.create({
8 | default: {
9 | paddingLeft: 2
10 | }
11 | });
12 | styles('default');
13 | `;
14 | const { styles } = compile(input);
15 |
16 | expect(styles).toBe(`.hhTkCv{padding-left:2px}`);
17 | });
18 |
19 | it('does not convert opacity to pixels', () => {
20 | const input = `
21 | import style9 from 'style9';
22 | const styles = style9.create({
23 | default: {
24 | opacity: 1
25 | }
26 | });
27 | styles('default');
28 | `;
29 | const { styles } = compile(input);
30 |
31 | expect(styles).toBe(`.gOeSjL{opacity:1}`);
32 | });
33 |
34 | it('expands shorthand', () => {
35 | const input = `
36 | import style9 from 'style9';
37 | const styles = style9.create({
38 | default: {
39 | padding: '1rem'
40 | }
41 | });
42 | styles('default');
43 | `;
44 | const { styles } = compile(input);
45 |
46 | expect(styles).toBe(
47 | '.jWWtke{padding-top:1rem}' +
48 | '.ftIldC{padding-right:1rem}' +
49 | '.bnHxUw{padding-bottom:1rem}' +
50 | '.iDuqPI{padding-left:1rem}'
51 | );
52 | });
53 |
54 | it('does not override longhand', () => {
55 | const input = `
56 | import style9 from 'style9';
57 | const styles = style9.create({
58 | default: {
59 | paddingTop: '.5rem',
60 | padding: '1rem',
61 | paddingLeft: '2rem'
62 | }
63 | });
64 | styles('default');
65 | `;
66 | const { styles } = compile(input);
67 |
68 | expect(styles).toBe(
69 | '.lcGuBB{padding-top:.5rem}' +
70 | '.ftIldC{padding-right:1rem}' +
71 | '.bnHxUw{padding-bottom:1rem}' +
72 | '.iigETV{padding-left:2rem}'
73 | );
74 | });
75 |
76 | it('converts fontSize pixels', () => {
77 | const input = `
78 | import style9 from 'style9';
79 | const styles = style9.create({
80 | default: {
81 | fontSize: 14
82 | }
83 | });
84 | styles('default');
85 | `;
86 | const { styles } = compile(input);
87 |
88 | expect(styles).toBe(`.kKRHCo{font-size:0.875rem}`);
89 | });
90 |
91 | it('accepts an array', () => {
92 | const input = `
93 | import style9 from 'style9';
94 | const styles = style9.create({
95 | default: {
96 | textDecorationLine: ['underline', 'overline']
97 | }
98 | });
99 | styles('default');
100 | `;
101 | const { styles } = compile(input);
102 |
103 | expect(styles).toBe(`.jMUvdQ{text-decoration-line:underline overline}`);
104 | });
105 |
106 | it('supports constants', () => {
107 | const input = `
108 | import style9 from 'style9';
109 | const BLUE = 'blue';
110 | const styles = style9.create({
111 | default: {
112 | color: BLUE
113 | }
114 | });
115 | styles('default');
116 | `;
117 | const { styles } = compile(input);
118 |
119 | expect(styles).toBe(`.hxxstI{color:blue}`);
120 | });
121 |
122 | it('removes unused styles', () => {
123 | const input = `
124 | import style9 from 'style9';
125 | const styles = style9.create({
126 | default: {
127 | color: 'blue'
128 | }
129 | });
130 | `;
131 | const { styles } = compile(input);
132 |
133 | expect(styles).toBe(``);
134 | });
135 |
136 | it('keeps styles used in styles()', () => {
137 | const input = `
138 | import style9 from 'style9';
139 | const styles = style9.create({
140 | default: {
141 | color: 'blue'
142 | },
143 | red: {
144 | color: 'red'
145 | }
146 | });
147 | styles('default');
148 | `;
149 | const { styles } = compile(input);
150 |
151 | expect(styles).toBe(`.hxxstI{color:blue}`);
152 | });
153 |
154 | it('keeps styles used as object', () => {
155 | const input = `
156 | import style9 from 'style9';
157 | const styles = style9.create({
158 | default: {
159 | color: 'blue'
160 | },
161 | red: {
162 | color: 'red'
163 | }
164 | });
165 | styles.default;
166 | `;
167 | const { styles } = compile(input);
168 |
169 | expect(styles).toBe(`.hxxstI{color:blue}`);
170 | });
171 |
172 | it('supports static bracket access', () => {
173 | const input = `
174 | import style9 from 'style9';
175 | const styles = style9.create({
176 | default: {
177 | color: 'blue'
178 | },
179 | red: {
180 | color: 'red'
181 | }
182 | });
183 | styles['default']
184 | `;
185 | const { styles } = compile(input);
186 |
187 | expect(styles).toBe(`.hxxstI{color:blue}`);
188 | });
189 |
190 | it('supports dynamic bracket access', () => {
191 | const input = `
192 | import style9 from 'style9';
193 | const styles = style9.create({
194 | blue: {
195 | color: 'blue'
196 | },
197 | red: {
198 | color: 'red'
199 | }
200 | });
201 | styles[blue]
202 | `;
203 | const { styles } = compile(input);
204 |
205 | expect(styles).toBe(`.hxxstI{color:blue}.RCRUH{color:red}`);
206 | });
207 |
208 | it('supports arrow function', () => {
209 | const input = `
210 | import style9 from 'style9';
211 | const styles = style9.create({
212 | default: {
213 | color: 'blue'
214 | }
215 | });
216 | const get = state => styles(state && 'default');
217 | `;
218 | const { styles } = compile(input);
219 |
220 | expect(styles).toBe(`.hxxstI{color:blue}`);
221 | });
222 |
223 | it('outputs no styles without declaration', () => {
224 | const input = `
225 | import style9 from 'style9';
226 | style9.create({
227 | default: {
228 | color: 'blue'
229 | }
230 | });
231 | `;
232 | const { styles } = compile(input);
233 |
234 | expect(styles).toBe('');
235 | });
236 |
237 | it('supports spread assignment', () => {
238 | const input = `
239 | import style9 from 'style9';
240 | const { ...styles } = style9.create({
241 | blue: {
242 | color: 'blue'
243 | }
244 | });
245 | `;
246 | const { styles } = compile(input);
247 |
248 | expect(styles).toBe(`.hxxstI{color:blue}`);
249 | });
250 |
251 | it('removes unused destructured keys', () => {
252 | const input = `
253 | import style9 from 'style9';
254 | const { blue } = style9.create({
255 | blue: {
256 | color: 'blue'
257 | },
258 | red: {
259 | color: 'red'
260 | }
261 | });
262 | `;
263 | const { styles } = compile(input);
264 | expect(styles).toBe('.hxxstI{color:blue}');
265 | });
266 |
267 | it('supports spread use', () => {
268 | const input = `
269 | import style9 from 'style9';
270 | const styles = style9.create({
271 | blue: {
272 | color: 'blue'
273 | }
274 | });
275 | console.log({ ...styles });
276 | `;
277 | const { styles } = compile(input);
278 |
279 | expect(styles).toBe(`.hxxstI{color:blue}`);
280 | });
281 |
282 | it('supports member expression access', () => {
283 | const input = `
284 | import style9 from 'style9';
285 | const blue = style9.create({
286 | blue: {
287 | color: 'blue'
288 | },
289 | red: {
290 | color: 'red'
291 | }
292 | }).blue;
293 | console.log(blue)
294 | `;
295 | const { styles } = compile(input);
296 |
297 | expect(styles).toBe(`.hxxstI{color:blue}`);
298 | });
299 |
300 | it('does not output CSS when style9() is called', () => {
301 | const input = `
302 | import style9 from 'style9';
303 | style9();
304 | `;
305 | const { styles } = compile(input);
306 |
307 | expect(styles).toBe('');
308 | });
309 |
--------------------------------------------------------------------------------
/babel.js:
--------------------------------------------------------------------------------
1 | const NAME = require('./package.json').name;
2 | const processReferences = require('./src/process-references.js');
3 |
4 | module.exports = function style9BabelPlugin() {
5 | return {
6 | name: NAME,
7 | visitor: {
8 | ImportDefaultSpecifier(path, state) {
9 | if (path.parent.source.value !== NAME) return;
10 |
11 | const importName = path.node.local.name;
12 | const bindings = path.scope.bindings[importName].referencePaths;
13 |
14 | const css = processReferences(bindings, state.opts).join('');
15 | if (!state.file.metadata.style9) {
16 | state.file.metadata.style9 = '';
17 | }
18 | state.file.metadata.style9 += css;
19 | }
20 | }
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/docs/Background.md:
--------------------------------------------------------------------------------
1 | # Background
2 |
3 | style9 was created to be an open-source implementation of the CSS-in-JS library used to develop the new version of Facebook, [stylex][stylex]. For that reason, the design principles are heavily influenced by stylex.
4 |
5 | ## Principles
6 |
7 | ### Optimized output
8 |
9 | Large JavaScript and CSS files are bad for performance, and embedding CSS in JavaScript is even worse. By only allowing values that can be inferred at compile time, combined with a carefully designed API, the CSS can be extracted to it's own file at compile time. Additionally, any styles that aren't used can be pruned from the output. And because most values are used many times the classes are outputted as atomic CSS, which avoids the issue of ever-growing files.
10 |
11 | ### No global styles, no cascade, no selectors
12 |
13 | No issues with specificity, no searching for definitions. Styles can be co-located with the code and referenced directly.
14 |
15 | ### Type safety
16 |
17 | Full type safety, courtesy of TypeScript. Values, properties and variables can be autocompleted and errors caught, all using toolchains already used.
18 |
19 | ### Low-level, framework agnostic, small API
20 |
21 | style9 deals with defining styles and generating class names, nothing else. This makes the API small and makes style9 easy to learn. It also makes it framework agnostic. It also makes it possible to build solutions on top, see [Ecosystem](Ecosystem.md).
22 |
23 | ## Tradeoffs
24 |
25 | ### No dynamic values
26 |
27 | There are two types of values that can be considered dynamic: the first is about choosing among pre-defined styles based on a runtime value. These are fully supported, for example:
28 |
29 | ```typescript
30 | import style9 from 'style9';
31 |
32 | const styles = style9.create({
33 | blue: {
34 | color: 'blue'
35 | },
36 | red: {
37 | color: 'red'
38 | }
39 | });
40 |
41 | export const getClass(color: keyof typeof styles) => style9(styles[color]);
42 | ```
43 |
44 | The other is using a value that's only available at the runtime, a common example being setting a user-defined image as a background. These are not available while compiling, and are therefore not supported. However, these values are one-off and limited in use, and does not suffer from being set inline. Or, if reuse is required, they can be set as CSS Custom Properties:
45 |
46 | ```typescript
47 | import style9 from 'style9';
48 |
49 | const styles = style9.create({
50 | avatar: {
51 | backgroundColor: 'var(--my-personal-color)'
52 | }
53 | });
54 |
55 | document.body.style = `--my-personal-color: blue;`;
56 | ```
57 |
58 | ### No shorthands
59 |
60 | Because the generated CSS uses atomic classes, there is no single class name that can be toggled for each style. Instead, multiple classes are toggled, with the ones that are overridden removed. This means that it must be possible to know which properties set the same styles. Shorthand CSS properties, such as `background`, make this impossible since they set multiple values.
61 |
62 | [stylex]: https://engineering.fb.com/2020/05/08/web/facebook-redesign/
63 |
--------------------------------------------------------------------------------
/docs/Bundler-plugins.md:
--------------------------------------------------------------------------------
1 | # Bundler plugins
2 |
3 | ### Universal options
4 |
5 | - `minifyProperties` - minify the property names of style objects. Unless you pass custom objects to `style9()` not generated by `style9.create()`, this is safe to enable and will lead to smaller JavaScript output but mangled property names. Consider enabling it in production. Default: `false`
6 | - `incrementalClassnames` - use incremental names for classes(`.a`, `.b`) instead of a hash of the style value. This means that class names are not stable across builds, but leads to smaller JavaScript output. Default: `false`
7 |
8 | ## Webpack
9 |
10 | ```javascript
11 | const Style9Plugin = require('style9/webpack');
12 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
13 |
14 | module.exports = {
15 | // Collect all styles in a single file - required
16 | optimization: {
17 | splitChunks: {
18 | cacheGroups: {
19 | styles: {
20 | name: 'styles',
21 | type: 'css/mini-extract',
22 | // For webpack@4 remove type and uncomment the line below
23 | // test: /\.css$/,
24 | chunks: 'all',
25 | enforce: true,
26 | }
27 | }
28 | }
29 | },
30 | module: {
31 | rules: [
32 | {
33 | test: /\.(tsx|ts|js|mjs|jsx)$/,
34 | use: Style9Plugin.loader,
35 | options: {
36 | parserOptions?: BabelParserOpts;
37 | minifyProperties?: boolean;
38 | incrementalClassnames?: boolean;
39 | }
40 | },
41 | {
42 | test: /\.css$/i,
43 | use: [MiniCssExtractPlugin.loader, 'css-loader']
44 | }
45 | ]
46 | },
47 | plugins: [
48 | new Style9Plugin(),
49 | new MiniCssExtractPlugin()
50 | ]
51 | };
52 | ```
53 |
54 | ## Rollup
55 |
56 | ```javascript
57 | import style9 from 'style9/rollup';
58 |
59 | export default {
60 | // ...
61 | plugins: [
62 | style9({
63 | include?: (string | RegExp)[] | string | RegExp;
64 | exclude?: (string | RegExp)[] | string | RegExp;
65 | // fileName XOR name is required
66 | fileName: string; // will be emitted as fileName
67 | name: string; // will be emitted according to output.assetFileNames format
68 | parserOptions?: BabelParserOpts;
69 | minifyProperties?: boolean;
70 | incrementalClassnames?: boolean;
71 | })
72 | ]
73 | };
74 | ```
75 |
76 | ## Next.js
77 |
78 | ```javascript
79 | const withTM = require('next-transpile-modules')(['style9']);
80 | // If you are using the latest version Next.js:
81 | const withStyle9 = require('style9/next');
82 | // If you are using Next.js below 12.0.5:
83 | const withStyle9 = require('style9/next-legacy');
84 |
85 | module.exports = withStyle9({
86 | parserOptions?: BabelParserOpts;
87 | minifyProperties?: boolean;
88 | incrementalClassnames?: boolean;
89 | })(withTM());
90 | ```
91 |
92 | ## Gatsby
93 |
94 | ```javascript
95 | module.exports = {
96 | plugins: [
97 | {
98 | resolve: 'style9/gatsby',
99 | options: {
100 | parserOptions?: BabelParserOpts;
101 | minifyProperties?: boolean;
102 | incrementalClassnames?: boolean;
103 | }
104 | }
105 | ]
106 | }
107 | ```
108 |
109 | ## Vite
110 |
111 | ```javascript
112 | import { defineConfig } from 'vite';
113 | import style9 from 'style9/vite';
114 | export default defineConfig({
115 | plugins: [
116 | // ...other plugins.
117 | style9()
118 | ]
119 | });
120 | ```
121 |
122 | ## Babel
123 |
124 | When using the babel plugin you'll have to pass the generated CSS to the post-processor yourself. If compiling multiple files this should be done to the concatenated output of all babel transforms.
125 |
126 | ```javascript
127 | const babel = require('@babel/core');
128 | const processCSS = require('style9/src/process-css');
129 |
130 | const output = babel.transformFile('./file.js', {
131 | plugins: [['style9/babel', {
132 | minifyProperties?: boolean;
133 | incrementalClassnames?: boolean;
134 | }]]
135 | });
136 | const { css } = processCSS(output.metadata.style9 || '', options?: PostCSSOptions);
137 | ```
138 |
--------------------------------------------------------------------------------
/docs/Ecosystem.md:
--------------------------------------------------------------------------------
1 | # Ecosystem
2 |
3 | ## [style9-components.macro](https://github.com/johanholmerin/style9-components.macro)
4 |
5 | styled-components API for React with support for prop-based styling
6 |
7 | ```javascript
8 | import styled from 'style9-components.macro';
9 |
10 | // Create new component
11 | const Component = styled.div({
12 | color: props => props.color,
13 | backgroundColor: 'red'
14 | });
15 |
16 | // Extend existing component
17 | const WrappedComponent = styled(Component)({
18 | backgroundColor: 'blue'
19 | });
20 |
21 | // Support for overriding the element
22 |
Weclome Style9.
11 |In some case we using dynamic import to load us resource.
12 |Vite plugin will process css with right order.
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "style9-vite-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev":"vite", 7 | "build":"vite build" 8 | }, 9 | "dependencies": { 10 | "style9": "link:../.." 11 | }, 12 | "devDependencies": { 13 | "vite":"^4.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/vite/src/dynamic.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | import style9 from 'style9'; 3 | 4 | const styles = style9.create({ 5 | size: { 6 | fontSize: '20px' 7 | }, 8 | color: { 9 | color: 'blue' 10 | } 11 | }); 12 | 13 | document 14 | .querySelectorAll('p') 15 | .forEach(node => (node.className = styles('color', 'size'))); 16 | -------------------------------------------------------------------------------- /examples/vite/src/dynamic2.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | import style9 from 'style9'; 3 | 4 | const styles = style9.create({ 5 | size: { 6 | fontSize: '30px' 7 | }, 8 | color: { 9 | color: 'red' 10 | } 11 | }); 12 | 13 | document 14 | .querySelectorAll('p') 15 | .forEach(node => (node.className = styles('color', 'size'))); 16 | -------------------------------------------------------------------------------- /examples/vite/src/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | import style9 from 'style9'; 3 | 4 | const styles = style9.create({ 5 | blue: { 6 | color: 'blue' 7 | } 8 | }); 9 | 10 | document.body.className = styles('blue'); 11 | 12 | if (Math.random() > 0.5) { 13 | import('./dynamic').then(() => import('./dynamic2')); 14 | } else { 15 | import('./dynamic2').then(() => import('./dynamic')); 16 | } 17 | -------------------------------------------------------------------------------- /examples/vite/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import style9 from 'style9/vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [style9()] 6 | }); 7 | -------------------------------------------------------------------------------- /examples/webpack4/README.md: -------------------------------------------------------------------------------- 1 | # style9-webpack4-example 2 | 3 | Example using [style9](https://github.com/johanholmerin/style9) with Webpack 4. 4 | See [webpack.config.js](webpack.config.js) for config and 5 | [src/main.js](src/main.js) for usage. 6 | 7 | ## Installation 8 | 9 | ```sh 10 | yarn 11 | ``` 12 | 13 | ## Build 14 | 15 | ```sh 16 | yarn build 17 | ``` 18 | -------------------------------------------------------------------------------- /examples/webpack4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "style9-webpack4-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "webpack --mode=production" 7 | }, 8 | "dependencies": { 9 | "style9": "link:../.." 10 | }, 11 | "devDependencies": { 12 | "css-loader": "^5.1.1", 13 | "mini-css-extract-plugin": "^1.3.9", 14 | "webpack": "^4.46", 15 | "webpack-cli": "^4.5.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/webpack4/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |