├── .editorconfig
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .rollup.js
├── .tape.js
├── CHANGELOG.md
├── CONTRIBUTING.md
├── INSTALL.md
├── LICENSE.md
├── README.md
├── package.json
├── src
├── index.d.ts
├── index.js
└── lib
│ ├── get-custom-properties-from-imports.js
│ ├── get-custom-properties-from-root.js
│ ├── is-ignored.js
│ ├── transform-properties.js
│ ├── transform-value-ast.js
│ └── write-custom-properties-to-exports.js
└── test
├── basic.css
├── basic.expect.css
├── basic.import-is-empty.expect.css
├── basic.import-override.expect.css
├── basic.import.expect.css
├── basic.preserve.expect.css
├── export-properties.css
├── export-properties.js
├── export-properties.json
├── export-properties.mjs
├── export-properties.scss
├── import-properties-2.css
├── import-properties-2.js
├── import-properties.css
├── import-properties.js
├── import-properties.json
└── import-properties.pcss
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = tab
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
13 | [*.{json,md,yml}]
14 | indent_size = 2
15 | indent_style = space
16 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | on:
3 | push:
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | node: [12, 16]
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: actions/setup-node@v2
14 | with:
15 | node-version: ${{ matrix.node }}
16 |
17 | - run: yarn install --ignore-scripts
18 | - run: yarn run test
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | yarn.lock
4 | *.log*
5 | *.result.css
6 | .*
7 | !.editorconfig
8 | !.github
9 | !.gitignore
10 | !.rollup.js
11 | !.tape.js
12 | !.travis.yml
13 | /index.*
14 |
--------------------------------------------------------------------------------
/.rollup.js:
--------------------------------------------------------------------------------
1 | import babel from '@rollup/plugin-babel';
2 | import { copy } from '@web/rollup-plugin-copy'
3 |
4 | export default {
5 | input: 'src/index.js',
6 | output: [
7 | { file: 'index.cjs', format: 'cjs', exports: 'default' },
8 | { file: 'index.mjs', format: 'esm' }
9 | ],
10 | plugins: [
11 | babel({
12 | babelHelpers: 'bundled',
13 | plugins: [
14 | '@babel/plugin-syntax-dynamic-import'
15 | ],
16 | presets: [
17 | ['@babel/preset-env', {
18 | corejs: 3,
19 | loose: true,
20 | modules: false,
21 | targets: { node: 12 },
22 | useBuiltIns: 'entry'
23 | }]
24 | ]
25 | }),
26 | copy({
27 | rootDir: './src',
28 | patterns: 'index.d.ts'
29 | }),
30 | ]
31 | };
32 |
--------------------------------------------------------------------------------
/.tape.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'basic': {
3 | message: 'supports basic usage'
4 | },
5 | 'basic:preserve': {
6 | message: 'supports { preserve: false } usage',
7 | options: {
8 | preserve: false
9 | }
10 | },
11 | 'basic:import': {
12 | message: 'supports { importFrom: { customProperties: { ... } } } usage',
13 | options: {
14 | importFrom: {
15 | customProperties: {
16 | '--color': 'rgb(255, 0, 0)',
17 | '--color-2': 'yellow',
18 | '--ref-color': 'var(--color)',
19 | '--margin': '0 10px 20px 30px',
20 | '--z-index': 10
21 | }
22 | }
23 | }
24 | },
25 | 'basic:import-fn': {
26 | message: 'supports { importFrom() } usage',
27 | options: {
28 | importFrom() {
29 | return {
30 | customProperties: {
31 | '--color': 'rgb(255, 0, 0)',
32 | '--color-2': 'yellow',
33 | '--ref-color': 'var(--color)',
34 | '--margin': '0 10px 20px 30px',
35 | '--z-index': 10
36 | }
37 | };
38 | }
39 | },
40 | expect: 'basic.import.expect.css',
41 | result: 'basic.import.result.css'
42 | },
43 | 'basic:import-fn-promise': {
44 | message: 'supports { async importFrom() } usage',
45 | options: {
46 | importFrom() {
47 | return new Promise(resolve => {
48 | resolve({
49 | customProperties: {
50 | '--color': 'rgb(255, 0, 0)',
51 | '--color-2': 'yellow',
52 | '--ref-color': 'var(--color)',
53 | '--z-index': 10
54 | }
55 | })
56 | });
57 | }
58 | },
59 | expect: 'basic.import.expect.css',
60 | result: 'basic.import.result.css'
61 | },
62 | 'basic:import-json': {
63 | message: 'supports { importFrom: "test/import-properties.json" } usage',
64 | options: {
65 | importFrom: 'test/import-properties.json'
66 | },
67 | expect: 'basic.import.expect.css',
68 | result: 'basic.import.result.css'
69 | },
70 | 'basic:import-js': {
71 | message: 'supports { importFrom: "test/import-properties{-2}?.js" } usage',
72 | options: {
73 | importFrom: [
74 | 'test/import-properties.js',
75 | 'test/import-properties-2.js'
76 | ]
77 | },
78 | expect: 'basic.import.expect.css',
79 | result: 'basic.import.result.css'
80 | },
81 | 'basic:import-css': {
82 | message: 'supports { importFrom: "test/import-properties{-2}?.css" } usage',
83 | options: {
84 | importFrom: [
85 | 'test/import-properties.css',
86 | 'test/import-properties-2.css'
87 | ]
88 | },
89 | expect: 'basic.import.expect.css',
90 | result: 'basic.import.result.css'
91 | },
92 | 'basic:import-css-js': {
93 | message: 'supports { importFrom: "test/import-properties{-2}?.{css|js}" } usage',
94 | options: {
95 | importFrom: [
96 | 'test/import-properties.js',
97 | 'test/import-properties-2.css'
98 | ]
99 | },
100 | expect: 'basic.import.expect.css',
101 | result: 'basic.import.result.css'
102 | },
103 | 'basic:import-css-pcss': {
104 | message: 'supports { importFrom: "test/import-properties.{p}?css" } usage',
105 | options: {
106 | importFrom: [
107 | 'test/import-properties.pcss',
108 | 'test/import-properties-2.css'
109 | ]
110 | },
111 | expect: 'basic.import.expect.css',
112 | result: 'basic.import.result.css'
113 | },
114 | 'basic:import-css-from': {
115 | message: 'supports { importFrom: { from: "test/import-properties.css" } } usage',
116 | options: {
117 | importFrom: [
118 | { from: 'test/import-properties.css' },
119 | { from: 'test/import-properties-2.css' }
120 | ]
121 | },
122 | expect: 'basic.import.expect.css',
123 | result: 'basic.import.result.css'
124 | },
125 | 'basic:import-css-from-type': {
126 | message: 'supports { importFrom: [ { from: "test/import-properties.css", type: "css" } ] } usage',
127 | options: {
128 | importFrom: [
129 | { from: 'test/import-properties.css', type: 'css' },
130 | { from: 'test/import-properties-2.css', type: 'css' }
131 | ]
132 | },
133 | expect: 'basic.import.expect.css',
134 | result: 'basic.import.result.css'
135 | },
136 | 'basic:import-override': {
137 | message: 'importFrom with { preserve: false } should override root properties',
138 | options: {
139 | preserve: false,
140 | importFrom: {
141 | customProperties: {
142 | '--color': 'rgb(0, 0, 0)',
143 | '--color-2': 'yellow',
144 | '--ref-color': 'var(--color)',
145 | '--margin': '0 10px 20px 30px',
146 | '--shadow-color': 'rgb(0,0,0)',
147 | '--z-index': 10
148 | }
149 | }
150 | },
151 | expect: 'basic.import-override.expect.css',
152 | result: 'basic.import-override.result.css'
153 | },
154 | 'basic:export': {
155 | message: 'supports { exportTo: { customProperties: { ... } } } usage',
156 | options: {
157 | exportTo: (global.__exportPropertiesObject = global.__exportPropertiesObject || {
158 | customProperties: null
159 | })
160 | },
161 | expect: 'basic.expect.css',
162 | result: 'basic.result.css',
163 | after() {
164 | if (__exportPropertiesObject.customProperties['--color'] !== 'rgb(255, 0, 0)') {
165 | throw new Error('The exportTo function failed');
166 | }
167 | }
168 | },
169 | 'basic:export-fn': {
170 | message: 'supports { exportTo() } usage',
171 | options: {
172 | exportTo(customProperties) {
173 | if (customProperties['--color'] !== 'rgb(255, 0, 0)') {
174 | throw new Error('The exportTo function failed');
175 | }
176 | }
177 | },
178 | expect: 'basic.expect.css',
179 | result: 'basic.result.css'
180 | },
181 | 'basic:export-fn-promise': {
182 | message: 'supports { async exportTo() } usage',
183 | options: {
184 | exportTo(customProperties) {
185 | return new Promise((resolve, reject) => {
186 | if (customProperties['--color'] !== 'rgb(255, 0, 0)') {
187 | reject('The exportTo function failed');
188 | } else {
189 | resolve();
190 | }
191 | });
192 | }
193 | },
194 | expect: 'basic.expect.css',
195 | result: 'basic.result.css'
196 | },
197 | 'basic:export-scss': {
198 | message: 'supports { exportTo: "test/export-properties.scss" } usage',
199 | options: {
200 | exportTo: 'test/export-properties.scss'
201 | },
202 | expect: 'basic.expect.css',
203 | result: 'basic.result.css',
204 | before() {
205 | global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.scss', 'utf8');
206 | },
207 | after() {
208 | if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.scss', 'utf8')) {
209 | throw new Error('The original file did not match the freshly exported copy');
210 | }
211 | }
212 | },
213 | 'basic:export-json': {
214 | message: 'supports { exportTo: "test/export-properties.json" } usage',
215 | options: {
216 | exportTo: 'test/export-properties.json'
217 | },
218 | expect: 'basic.expect.css',
219 | result: 'basic.result.css',
220 | before() {
221 | global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.json', 'utf8');
222 | },
223 | after() {
224 | if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.json', 'utf8')) {
225 | throw new Error('The original file did not match the freshly exported copy');
226 | }
227 | }
228 | },
229 | 'basic:export-js': {
230 | message: 'supports { exportTo: "test/export-properties.js" } usage',
231 | options: {
232 | exportTo: 'test/export-properties.js'
233 | },
234 | expect: 'basic.expect.css',
235 | result: 'basic.result.css',
236 | before() {
237 | global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.js', 'utf8');
238 | },
239 | after() {
240 | if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.js', 'utf8')) {
241 | throw new Error('The original file did not match the freshly exported copy');
242 | }
243 | }
244 | },
245 | 'basic:export-mjs': {
246 | message: 'supports { exportTo: "test/export-properties.mjs" } usage',
247 | options: {
248 | exportTo: 'test/export-properties.mjs'
249 | },
250 | expect: 'basic.expect.css',
251 | result: 'basic.result.css',
252 | before() {
253 | global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.mjs', 'utf8');
254 | },
255 | after() {
256 | if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.mjs', 'utf8')) {
257 | throw new Error('The original file did not match the freshly exported copy');
258 | }
259 | }
260 | },
261 | 'basic:export-css': {
262 | message: 'supports { exportTo: "test/export-properties.css" } usage',
263 | options: {
264 | exportTo: 'test/export-properties.css'
265 | },
266 | expect: 'basic.expect.css',
267 | result: 'basic.result.css',
268 | before() {
269 | global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.css', 'utf8');
270 | },
271 | after() {
272 | if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.css', 'utf8')) {
273 | throw new Error('The original file did not match the freshly exported copy');
274 | }
275 | }
276 | },
277 | 'basic:export-css-to': {
278 | message: 'supports { exportTo: { to: "test/export-properties.css" } } usage',
279 | options: {
280 | exportTo: { to: 'test/export-properties.css' }
281 | },
282 | expect: 'basic.expect.css',
283 | result: 'basic.result.css',
284 | before() {
285 | global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.css', 'utf8');
286 | },
287 | after() {
288 | if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.css', 'utf8')) {
289 | throw new Error('The original file did not match the freshly exported copy');
290 | }
291 | }
292 | },
293 | 'basic:export-css-to-type': {
294 | message: 'supports { exportTo: { to: "test/export-properties.css", type: "css" } } usage',
295 | options: {
296 | exportTo: { to: 'test/export-properties.css', type: 'css' }
297 | },
298 | expect: 'basic.expect.css',
299 | result: 'basic.result.css',
300 | before() {
301 | global.__exportPropertiesString = require('fs').readFileSync('test/export-properties.css', 'utf8');
302 | },
303 | after() {
304 | if (global.__exportPropertiesString !== require('fs').readFileSync('test/export-properties.css', 'utf8')) {
305 | throw new Error('The original file did not match the freshly exported copy');
306 | }
307 | }
308 | },
309 | 'basic:import-is-empty': {
310 | message: 'supports { importFrom: {} } usage',
311 | options: {
312 | importFrom: {}
313 | }
314 | }
315 | };
316 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changes to PostCSS Custom Properties
2 |
3 | ### 12.0.0 (September 17, 2021)
4 |
5 | - Updated: Support for PostCS 8+ (major).
6 | - Updated: Support for Node 12+ (major).
7 |
8 | ### 11.0.0 (January 12, 2021)
9 |
10 | - Added: Support for PostCSS v8.
11 |
12 | ### 10.0.0 (September 18, 2020)
13 |
14 | - Fixed: `url-regex` vulnerability ([#228](https://github.com/postcss/postcss-custom-properties/pull/228))
15 | - Breaking Change: Node v10+ now required
16 |
17 | ### 9.2.0 (September 18, 2020)
18 |
19 | - Added: Export variables to SCSS file ([#212](https://github.com/postcss/postcss-custom-properties/pull/212))
20 | - Added: Support for ".pcss" file resolution in `importFrom` ([#211](https://github.com/postcss/postcss-custom-properties/pull/211))
21 | - Fixed: Allow combined selectors ([#199](https://github.com/postcss/postcss-custom-properties/pull/199))
22 | - Fixed: Bug with spaces and commas in value ([#222](https://github.com/postcss/postcss-custom-properties/pull/222))
23 | - Fixed: `importFrom` priority ([#222](https://github.com/postcss/postcss-custom-properties/pull/222))
24 |
25 | ### 9.1.1 (February 20, 2020)
26 |
27 | - Fixed: Preserve spaces in multi-part values ([#203](https://github.com/postcss/postcss-custom-properties/pull/203))
28 |
29 | ### 9.1.0 (July 15, 2019)
30 |
31 | - Added: Support for preserving trailing comments within a declaration.
32 |
33 | ### 9.0.2 (July 15, 2019)
34 |
35 | - Updated: `postcss-values-parser` to 3.0.5 (patch)
36 |
37 | ### 9.0.1 (June 20, 2019)
38 |
39 | - Updated: `postcss-values-parser` to 3.0.4 (major)
40 | - Updated: Node 8+ compatibility (major)
41 |
42 | > This release is identical to v9.0.0, only `npm publish` failed to publish v9.0.0 and threw the following error:
43 | > ```
44 | > You cannot publish over the previously published versions: 9.0.0.
45 | > ```
46 | > I did not want this issue to distract me, and so I thoughtfully and impatiently published v9.0.0 as v9.0.1.
47 |
48 | ### 8.0.11 (June 20, 2019)
49 |
50 | - Added: Synchronous transforms when async is unnecessary (thank @eteeselink)
51 | - Fixed: Unexpected mutations to imported Custom Properties (thank @EECOLOR)
52 | - Fixed: Transforms throwing over unknown Custom Properties
53 |
54 | ### 8.0.10 (April 1, 2019)
55 |
56 | - Added: Support for ignoring lines and or blocks using
57 | `postcss-custom-properties` comments.
58 | - Updated: `postcss` to 7.0.14 (patch)
59 | - Updated: `postcss-values-parser` to 2.0.1 (patch)
60 |
61 | ### 8.0.9 (November 5, 2018)
62 |
63 | - Fixed: Issue with duplicate custom property usage within a declaration
64 |
65 | ### 8.0.8 (October 2, 2018)
66 |
67 | - Fixed: Issue with nested fallbacks
68 |
69 | ### 8.0.7 (October 2, 2018)
70 |
71 | - Fixed: Issue with parsing custom properties that are not strings
72 | - Updated: `postcss` to 7.0.5 (patch)
73 |
74 | ### 8.0.6 (September 21, 2018)
75 |
76 | - Fixed: Issue with regular `:root` and `html` properties not getting polyfilled
77 | - Updated: `postcss` to 7.0.3 (patch)
78 |
79 | ### 8.0.5 (September 21, 2018)
80 |
81 | - Fixed: Issue with multiple `importFrom` not getting combined
82 |
83 | ### 8.0.4 (September 18, 2018)
84 |
85 | - Fixed: Do not break on an empty `importFrom` object
86 |
87 | ### 8.0.3 (September 18, 2018)
88 |
89 | - Updated: PostCSS Values Parser 2
90 |
91 | ### 8.0.2 (September 17, 2018)
92 |
93 | - Fixed: Spacing is preserved before replaced variables.
94 |
95 | ### 8.0.1 (September 17, 2018)
96 |
97 | - Fixed: Workaround issue in `postcss-values-parser` incorrectly cloning nodes.
98 |
99 | ### 8.0.0 (September 16, 2018)
100 |
101 | - Added: New `exportTo` function to specify where to export custom properties to.
102 | - Added: New `importFrom` option to specify where to import custom properties from.
103 | - Added: Support for variables written within `html`
104 | - Added: Support for PostCSS v7.
105 | - Added: Support for Node v6+.
106 | - Removed: `strict` option, as using the fallback value isn’t necessarily more valid.
107 | - Removed: `preserve: "computed"` option, as there seems to be little use in preserving custom property declarations while removing all usages of them.
108 | - Removed: `warnings` and `noValueNotifications` options, as this should be the job of a linter tool.
109 | - Removed: `variables` option, which is now replaced by `importFrom`
110 | - Removed: `appendVariables` option, which is now replaced by `exportTo`
111 | - Fixed: Custom Properties in `:root` are not also transformed.
112 | - Fixed: Declarations that do not change are not duplicated during preserve.
113 |
114 | ### 7.0.0 (February 16, 2018)
115 |
116 | - Changed: `preserve` option defaults as `true` to reflect the browser climate
117 | - Changed: `warnings` option defaults to `false` to reflect the browser climate
118 |
119 | ### 6.3.1 (February 16, 2018)
120 |
121 | - Reverted: `preserve` and `warnings` option to be added in major release
122 |
123 | ### 6.3.0 (February 15, 2018)
124 |
125 | - Fixed: `var()` captures strictly `var()` functions and not `xvar()`, etc
126 | - Fixed: `var()` better captures whitespace within the function
127 | - Fixed: comments within declarations using `var()` are now preserved
128 | - Changed: `preserve` option defaults as `true` to reflect the browser climate
129 | - Changed: `warnings` option defaults to `false` to reflect the browser climate
130 | - Updated documentation
131 |
132 | ### 6.2.0 (October 6, 2017)
133 |
134 | - Added: `noValueNotifications` option (#71)
135 | - Fixed: Typo in `prefixedVariables` variable name (#77)
136 |
137 | ### 6.1.0 (June 28, 2017)
138 |
139 | - Added: Let "warnings" option silence all warnings
140 | ([#67](https://github.com/postcss/postcss-custom-properties/pull/67))
141 | - Dependencies update (postcss, balanced-match)
142 |
143 | ### 6.0.1 (May 15, 2017)
144 |
145 | - Fixed: incorrect export ([#69](https://github.com/postcss/postcss-custom-properties/issues/69))
146 |
147 | ### 6.0.0 (May 12, 2017)
148 |
149 | - Added: compatibility with postcss v6.x
150 |
151 | ### 5.0.2 (February 1, 2017)
152 |
153 | - Minor dependency update
154 | ([#57](https://github.com/postcss/postcss-custom-properties/pull/57))
155 |
156 | ### 5.0.1 (April 22, 2016)
157 |
158 | - Fixed: trailing space after custom property name causes duplicate empty
159 | property
160 | ([#43](https://github.com/postcss/postcss-custom-properties/pull/43))
161 |
162 | ### 5.0.0 (August 25, 2015)
163 |
164 | - Removed: compatibility with postcss v4.x
165 | - Added: compatibility with postcss v5.x
166 |
167 | ### 4.2.0 (July 21, 2015)
168 |
169 | - Added: `warnings` option allows you to disable warnings.
170 | ([cssnext#186](https://github.com/cssnext/cssnext/issues/186))
171 |
172 | ### 4.1.0 (July 14, 2015)
173 |
174 | - Added: plugin now returns itself in order to expose a `setVariables` function
175 | that allow you to programmatically change the variables.
176 | ([#35](https://github.com/postcss/postcss-custom-properties/pull/35))
177 |
178 | ### 4.0.0 (June 17, 2015)
179 |
180 | - Changed: messages and exceptions are now sent using postcss message API.
181 |
182 | ### 3.3.0 (April 8, 2015)
183 |
184 | - Added: `preserve` now support `"computed"` so only preserve resolved custom properties (see new option below)
185 | - Added: `appendVariables` allows you (when `preserve` is truthy) to append your variables as custom properties
186 | - Added: `strict: false` allows your to avoid too many fallbacks added in your CSS.
187 |
188 | ### 3.2.0 (03 31, 2015)
189 |
190 | - Added: JS defined variables are now resolved too ([#22](https://github.com/postcss/postcss-custom-properties/issues/22))
191 |
192 | ### 3.1.0 (03 16, 2015)
193 |
194 | - Added: variables defined in JS are now automatically prefixed with `--`
195 | ([0691784](https://github.com/postcss/postcss-custom-properties/commit/0691784ed2218d7e6b16da8c4df03e2ca0c4798c))
196 |
197 | ### 3.0.1 (February 6, 2015)
198 |
199 | - Fixed: logs now have filename back ([#19](https://github.com/postcss/postcss-custom-properties/issues/19))
200 |
201 | ### 3.0.0 (January 20, 2015)
202 |
203 | - Changed: upgrade to postcss 4 ([#18](https://github.com/postcss/postcss-custom-properties/pull/18))
204 | - Removed: some code that seems to be useless ([16ff3c2](https://github.com/postcss/postcss-custom-properties/commit/16ff3c22fe0563a1283411d7866791966fff4c58))
205 |
206 | ### 2.1.1 (December 2, 2014)
207 |
208 | - Fixed: issue when multiples undefined custom properties are referenced ([#16](https://github.com/postcss/postcss-custom-properties/issues/16))
209 |
210 | ### 2.1.0 (November 25, 2014)
211 |
212 | - Added: enhanced exceptions & messages
213 |
214 | ### 2.0.0 (November 12, 2014)
215 |
216 | - Changed: upgrade to postcss 3
217 |
218 | ### 1.0.2 (November 4, 2014)
219 |
220 | - Fixed: more clear message for warning about custom prop used in non top-level :root
221 |
222 | ### 1.0.1 (November 3, 2014)
223 |
224 | - Fixed: warning about custom prop used in non :root
225 |
226 | ### 1.0.0 (November 2, 2014)
227 |
228 | - Added: warning when a custom prop is used in another place than :root
229 | - Added: handle !important
230 |
231 | ### 0.4.0 (September 30, 2014)
232 |
233 | - Added: JS-defined properties override CSS-defined
234 |
235 | ### 0.3.1 (August 27, 2014)
236 |
237 | - Added: nested custom properties usages are now correctly resolved
238 | - Changed: undefined var doesn't throw error anymore (just a console warning) & are kept as is in the output
239 |
240 | ### 0.3.0 (August 26, 2014)
241 |
242 | - Changed: fallback now are always added by default ([see why](http://www.w3.org/TR/css-variables/#invalid-variables))
243 | - Changed: `map` option renamed to `variables`
244 |
245 | ### 0.2.0 (August 22, 2014)
246 |
247 | - Added: `map` option
248 | - Changed: GNU style error message
249 |
250 | ### 0.1.0 (August 1, 2014)
251 |
252 | ✨ First release based on [rework-vars](https://github.com/reworkcss/rework-vars) v3.1.1
253 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to PostCSS Custom Properties
2 |
3 | You want to help? You rock! Now, take a moment to be sure your contributions
4 | make sense to everyone else.
5 |
6 | ## Reporting Issues
7 |
8 | Found a problem? Want a new feature?
9 |
10 | - See if your issue or idea has [already been reported].
11 | - Provide a [reduced test case] or a [live example].
12 |
13 | Remember, a bug is a _demonstrable problem_ caused by _our_ code.
14 |
15 | ## Submitting Pull Requests
16 |
17 | Pull requests are the greatest contributions, so be sure they are focused in
18 | scope and avoid unrelated commits.
19 |
20 | 1. To begin; [fork this project], clone your fork, and add our upstream.
21 | ```bash
22 | # Clone your fork of the repo into the current directory
23 | git clone git@github.com:YOUR_USER/postcss-custom-properties.git
24 |
25 | # Navigate to the newly cloned directory
26 | cd postcss-custom-properties
27 |
28 | # Assign the original repo to a remote called "upstream"
29 | git remote add upstream git@github.com:postcss/postcss-custom-properties.git
30 |
31 | # Install the tools necessary for testing
32 | npm install
33 | ```
34 |
35 | 2. Create a branch for your feature or fix:
36 | ```bash
37 | # Move into a new branch for your feature
38 | git checkout -b feature/thing
39 | ```
40 | ```bash
41 | # Move into a new branch for your fix
42 | git checkout -b fix/something
43 | ```
44 |
45 | 3. If your code follows our practices, then push your feature branch:
46 | ```bash
47 | # Test current code
48 | npm test
49 | ```
50 | ```bash
51 | # Push the branch for your new feature
52 | git push origin feature/thing
53 | ```
54 | ```bash
55 | # Or, push the branch for your update
56 | git push origin update/something
57 | ```
58 |
59 | That’s it! Now [open a pull request] with a clear title and description.
60 |
61 | [already been reported]: issues
62 | [fork this project]: fork
63 | [live example]: https://codepen.io/pen
64 | [open a pull request]: https://help.github.com/articles/using-pull-requests/
65 | [reduced test case]: https://css-tricks.com/reduced-test-cases/
66 |
--------------------------------------------------------------------------------
/INSTALL.md:
--------------------------------------------------------------------------------
1 | # Installing PostCSS Custom Properties
2 |
3 | [PostCSS Custom Properties] runs in all Node environments, with special instructions for:
4 |
5 | | [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) |
6 | | --- | --- | --- | --- | --- | --- |
7 |
8 | ## Node
9 |
10 | Add [PostCSS Custom Properties] to your project:
11 |
12 | ```bash
13 | npm install postcss-custom-properties --save-dev
14 | ```
15 |
16 | Use [PostCSS Custom Properties] to process your CSS:
17 |
18 | ```js
19 | const postcssCustomProperties = require('postcss-custom-properties');
20 |
21 | postcssCustomProperties.process(YOUR_CSS /*, processOptions, pluginOptions */);
22 | ```
23 |
24 | Or use it as a [PostCSS] plugin:
25 |
26 | ```js
27 | const postcss = require('postcss');
28 | const postcssCustomProperties = require('postcss-custom-properties');
29 |
30 | postcss([
31 | postcssCustomProperties(/* pluginOptions */)
32 | ]).process(YOUR_CSS /*, processOptions */);
33 | ```
34 |
35 | ## PostCSS CLI
36 |
37 | Add [PostCSS CLI] to your project:
38 |
39 | ```bash
40 | npm install postcss-cli --save-dev
41 | ```
42 |
43 | Use [PostCSS Custom Properties] in your `postcss.config.js` configuration file:
44 |
45 | ```js
46 | const postcssCustomProperties = require('postcss-custom-properties');
47 |
48 | module.exports = {
49 | plugins: [
50 | postcssCustomProperties(/* pluginOptions */)
51 | ]
52 | }
53 | ```
54 |
55 | ## Webpack
56 |
57 | Add [PostCSS Loader] to your project:
58 |
59 | ```bash
60 | npm install postcss-loader --save-dev
61 | ```
62 |
63 | Use [PostCSS Custom Properties] in your Webpack configuration:
64 |
65 | ```js
66 | const postcssCustomProperties = require('postcss-custom-properties');
67 |
68 | module.exports = {
69 | module: {
70 | rules: [
71 | {
72 | test: /\.css$/,
73 | use: [
74 | 'style-loader',
75 | { loader: 'css-loader', options: { importLoaders: 1 } },
76 | { loader: 'postcss-loader', options: {
77 | postcssOptions: {
78 | plugins: [postcssCustomProperties(/* pluginOptions */)],
79 | }
80 | } }
81 | ]
82 | }
83 | ]
84 | }
85 | }
86 | ```
87 |
88 | ## Create React App
89 |
90 | Add [React App Rewired] and [React App Rewire PostCSS] to your project:
91 |
92 | ```bash
93 | npm install react-app-rewired react-app-rewire-postcss --save-dev
94 | ```
95 |
96 | Use [React App Rewire PostCSS] and [PostCSS Custom Properties] in your
97 | `config-overrides.js` file:
98 |
99 | ```js
100 | const reactAppRewirePostcss = require('react-app-rewire-postcss');
101 | const postcssCustomProperties = require('postcss-custom-properties');
102 |
103 | module.exports = config => reactAppRewirePostcss(config, {
104 | plugins: () => [
105 | postcssCustomProperties(/* pluginOptions */)
106 | ]
107 | });
108 | ```
109 |
110 | ## Gulp
111 |
112 | Add [Gulp PostCSS] to your project:
113 |
114 | ```bash
115 | npm install gulp-postcss --save-dev
116 | ```
117 |
118 | Use [PostCSS Custom Properties] in your Gulpfile:
119 |
120 | ```js
121 | const postcss = require('gulp-postcss');
122 | const postcssCustomProperties = require('postcss-custom-properties');
123 |
124 | gulp.task('css', () => gulp.src('./src/*.css').pipe(
125 | postcss([
126 | postcssCustomProperties(/* pluginOptions */)
127 | ])
128 | ).pipe(
129 | gulp.dest('.')
130 | ));
131 | ```
132 |
133 | ## Grunt
134 |
135 | Add [Grunt PostCSS] to your project:
136 |
137 | ```bash
138 | npm install grunt-postcss --save-dev
139 | ```
140 |
141 | Use [PostCSS Custom Properties] in your Gruntfile:
142 |
143 | ```js
144 | const postcssCustomProperties = require('postcss-custom-properties');
145 |
146 | grunt.loadNpmTasks('grunt-postcss');
147 |
148 | grunt.initConfig({
149 | postcss: {
150 | options: {
151 | processors: [
152 | postcssCustomProperties(/* pluginOptions */)
153 | ]
154 | },
155 | dist: {
156 | src: '*.css'
157 | }
158 | }
159 | });
160 | ```
161 |
162 | [Gulp PostCSS]: https://github.com/postcss/gulp-postcss
163 | [Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
164 | [PostCSS]: https://github.com/postcss/postcss
165 | [PostCSS CLI]: https://github.com/postcss/postcss-cli
166 | [PostCSS Loader]: https://github.com/postcss/postcss-loader
167 | [PostCSS Custom Properties]: https://github.com/postcss/postcss-custom-properties
168 | [React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss
169 | [React App Rewired]: https://github.com/timarney/react-app-rewired
170 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Copyright © PostCSS
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
3 |
4 | # PostCSS Custom Properties [
][postcss]
5 |
6 | [![NPM Version][npm-img]][npm-url]
7 | [![CSS Standard Status][css-img]][css-url]
8 | [![Build Status][cli-img]][cli-url]
9 | [![Support Chat][git-img]][git-url]
10 |
11 | [PostCSS Custom Properties] lets you use Custom Properties in CSS, following
12 | the [CSS Custom Properties] specification.
13 |
14 | [](https://caniuse.com/#feat=css-variables)
15 |
16 | ```pcss
17 | :root {
18 | --color: red;
19 | }
20 |
21 | h1 {
22 | color: var(--color);
23 | }
24 |
25 | /* becomes */
26 |
27 | :root {
28 | --color: red;
29 | }
30 |
31 | h1 {
32 | color: red;
33 | color: var(--color);
34 | }
35 | ```
36 |
37 | **Note:** This plugin only processes variables that are defined in the `:root` selector.
38 |
39 | ## Usage
40 |
41 | Add [PostCSS Custom Properties] to your project:
42 |
43 | ```bash
44 | npm install postcss-custom-properties --save-dev
45 | ```
46 |
47 | Use [PostCSS Custom Properties] to process your CSS:
48 |
49 | ```js
50 | const postcssCustomProperties = require('postcss-custom-properties');
51 |
52 | postcssCustomProperties.process(YOUR_CSS /*, processOptions, pluginOptions */);
53 | ```
54 |
55 | Or use it as a [PostCSS] plugin:
56 |
57 | ```js
58 | const postcss = require('postcss');
59 | const postcssCustomProperties = require('postcss-custom-properties');
60 |
61 | postcss([
62 | postcssCustomProperties(/* pluginOptions */)
63 | ]).process(YOUR_CSS /*, processOptions */);
64 | ```
65 |
66 | [PostCSS Custom Properties] runs in all Node environments, with special instructions for:
67 |
68 | | [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) |
69 | | --- | --- | --- | --- | --- | --- |
70 |
71 | ## Options
72 |
73 | ### preserve
74 |
75 | The `preserve` option determines whether Custom Properties and properties using
76 | custom properties should be preserved in their original form. By default, both
77 | of these are preserved.
78 |
79 | ```js
80 | postcssCustomProperties({
81 | preserve: false
82 | });
83 | ```
84 |
85 | ```pcss
86 | :root {
87 | --color: red;
88 | }
89 |
90 | h1 {
91 | color: var(--color);
92 | }
93 |
94 | /* becomes */
95 |
96 | h1 {
97 | color: red;
98 | }
99 | ```
100 |
101 | ### importFrom
102 |
103 | The `importFrom` option specifies sources where Custom Properties can be imported
104 | from, which might be CSS, JS, and JSON files, functions, and directly passed
105 | objects.
106 |
107 | ```js
108 | postcssCustomProperties({
109 | importFrom: 'path/to/file.css' // => :root { --color: red }
110 | });
111 | ```
112 |
113 | ```pcss
114 | h1 {
115 | color: var(--color);
116 | }
117 |
118 | /* becomes */
119 |
120 | h1 {
121 | color: red;
122 | }
123 | ```
124 |
125 | Multiple sources can be passed into this option, and they will be parsed in the
126 | order they are received. JavaScript files, JSON files, functions, and objects
127 | will need to namespace Custom Properties using the `customProperties` or
128 | `custom-properties` key.
129 |
130 | ```js
131 | postcssCustomProperties({
132 | importFrom: [
133 | 'path/to/file.css', // :root { --color: red; }
134 | 'and/then/this.js', // module.exports = { customProperties: { '--color': 'red' } }
135 | 'and/then/that.json', // { "custom-properties": { "--color": "red" } }
136 | {
137 | customProperties: { '--color': 'red' }
138 | },
139 | () => {
140 | const customProperties = { '--color': 'red' };
141 |
142 | return { customProperties };
143 | }
144 | ]
145 | });
146 | ```
147 |
148 | See example imports written in [CSS](test/import-properties.css),
149 | [JS](test/import-properties.js), and [JSON](test/import-properties.json).
150 |
151 | ### exportTo
152 |
153 | The `exportTo` option specifies destinations where Custom Properties can be exported
154 | to, which might be CSS, JS, and JSON files, functions, and directly passed
155 | objects.
156 |
157 | ```js
158 | postcssCustomProperties({
159 | exportTo: 'path/to/file.css' // :root { --color: red; }
160 | });
161 | ```
162 |
163 | Multiple destinations can be passed into this option, and they will be parsed
164 | in the order they are received. JavaScript files, JSON files, and objects will
165 | need to namespace Custom Properties using the `customProperties` or
166 | `custom-properties` key.
167 |
168 | ```js
169 | const cachedObject = { customProperties: {} };
170 |
171 | postcssCustomProperties({
172 | exportTo: [
173 | 'path/to/file.css', // :root { --color: red; }
174 | 'and/then/this.js', // module.exports = { customProperties: { '--color': 'red' } }
175 | 'and/then/this.mjs', // export const customProperties = { '--color': 'red' } }
176 | 'and/then/that.json', // { "custom-properties": { "--color": "red" } }
177 | 'and/then/that.scss', // $color: red;
178 | cachedObject,
179 | customProperties => {
180 | customProperties // { '--color': 'red' }
181 | }
182 | ]
183 | });
184 | ```
185 |
186 | See example exports written to [CSS](test/export-properties.css),
187 | [JS](test/export-properties.js), [MJS](test/export-properties.mjs),
188 | [JSON](test/export-properties.json) and [SCSS](test/export-properties.scss).
189 |
190 | [cli-img]: https://img.shields.io/travis/postcss/postcss-custom-properties/master.svg
191 | [cli-url]: https://travis-ci.org/postcss/postcss-custom-properties
192 | [css-img]: https://github.com/postcss/postcss-custom-properties/workflows/test/badge.svg
193 | [css-url]: https://github.com/postcss/postcss-custom-properties/actions/workflows/test.yml?query=workflow/test
194 | [git-img]: https://img.shields.io/badge/support-chat-blue.svg
195 | [git-url]: https://gitter.im/postcss/postcss
196 | [npm-img]: https://img.shields.io/npm/v/postcss-custom-properties.svg
197 | [npm-url]: https://www.npmjs.com/package/postcss-custom-properties
198 |
199 | [CSS Custom Properties]: https://www.w3.org/TR/css-variables-1/
200 | [PostCSS]: https://github.com/postcss/postcss
201 | [PostCSS Custom Properties]: https://github.com/postcss/postcss-custom-properties
202 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "postcss-custom-properties",
3 | "version": "12.0.0",
4 | "description": "Use Custom Properties Queries in CSS",
5 | "author": "Jonathan Neal ",
6 | "contributors": [
7 | "Maxime Thirouin"
8 | ],
9 | "license": "MIT",
10 | "repository": "postcss/postcss-custom-properties",
11 | "homepage": "https://github.com/postcss/postcss-custom-properties#readme",
12 | "bugs": "https://github.com/postcss/postcss-custom-properties/issues",
13 | "main": "index.cjs",
14 | "module": "index.mjs",
15 | "types": "./index.d.ts",
16 | "files": [
17 | "index.d.ts",
18 | "index.cjs",
19 | "index.mjs"
20 | ],
21 | "exports": {
22 | ".": {
23 | "import": "./index.mjs",
24 | "require": "./index.cjs",
25 | "types": "./index.d.ts"
26 | }
27 | },
28 | "scripts": {
29 | "prepublishOnly": "npm test",
30 | "pretest:tape": "rollup -c .rollup.js --silent",
31 | "test": "npm run test:js && npm run test:tape",
32 | "test:js": "eslint src/{*,**/*}.js --cache --ignore-path .gitignore --quiet",
33 | "test:tape": "postcss-tape"
34 | },
35 | "engines": {
36 | "node": ">=12"
37 | },
38 | "dependencies": {
39 | "postcss-value-parser": "^4.2.0"
40 | },
41 | "peerDependencies": {
42 | "postcss": "^8.3"
43 | },
44 | "devDependencies": {
45 | "@babel/core": "7.15.5",
46 | "@babel/plugin-syntax-dynamic-import": "7.8.3",
47 | "@babel/preset-env": "7.15.6",
48 | "@rollup/plugin-babel": "5.3.0",
49 | "@web/rollup-plugin-copy": "^0.3.0",
50 | "eslint": "7.32.0",
51 | "postcss": "8.3.6",
52 | "postcss-tape": "6.0.1",
53 | "pre-commit": "1.2.2",
54 | "rollup": "2.57.0"
55 | },
56 | "eslintConfig": {
57 | "env": {
58 | "browser": true,
59 | "es6": true,
60 | "node": true
61 | },
62 | "extends": "eslint:recommended",
63 | "parserOptions": {
64 | "ecmaVersion": 2020,
65 | "sourceType": "module"
66 | },
67 | "root": true
68 | },
69 | "keywords": [
70 | "postcss",
71 | "css",
72 | "postcss-plugin",
73 | "custom",
74 | "properties",
75 | "declarations",
76 | "variables",
77 | "vars",
78 | "w3c",
79 | "csswg",
80 | "specification"
81 | ]
82 | }
83 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | import type * as PostCSS from 'postcss'
2 |
3 | type ImportExportObject = { customProperties: {} }
4 | type ImportExportFunction = (customProperties?: {}) => ImportExportObject
5 | type ImportExportFilepath = string
6 |
7 | type ImportExport = ImportExportFilepath | ImportExportObject | ImportExportFunction | (ImportExportFilepath | ImportExportObject | ImportExportFunction)[]
8 |
9 | export interface PluginOptions {
10 | /** Determines whether Custom Properties and properties using custom properties should be preserved in their original form. */
11 | preserve?: boolean
12 |
13 | /** Specifies sources where Custom Properties can be imported from, which might be CSS, JS, and JSON files, functions, and directly passed objects. */
14 | importFrom?: ImportExport
15 |
16 | /** Specifies destinations where Custom Properties can be exported to, which might be CSS, JS, and JSON files, functions, and directly passed objects. */
17 | exportTo?: ImportExport
18 | }
19 |
20 | export interface Plugin {
21 | (options?: PluginOptions): {
22 | postcssPlugin: 'postcss-custom-properties',
23 | prepare({ root }: { root: any }): (
24 | | {
25 | Declaration: (decl: any) => void;
26 | Once?: undefined;
27 | }
28 | | {
29 | Once: (root: any) => Promise;
30 | Declaration: (decl: any) => void;
31 | }
32 | )
33 | },
34 | postcss: true
35 | }
36 |
37 | declare const plugin: Plugin
38 |
39 | export default plugin
40 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import getCustomPropertiesFromRoot from './lib/get-custom-properties-from-root';
2 | import getCustomPropertiesFromImports from './lib/get-custom-properties-from-imports';
3 | import transformProperties from './lib/transform-properties';
4 | import writeCustomPropertiesToExports from './lib/write-custom-properties-to-exports';
5 |
6 | const creator = opts => {
7 | // whether to preserve custom selectors and rules using them
8 | const preserve = 'preserve' in Object(opts) ? Boolean(opts.preserve) : true;
9 |
10 | // sources to import custom selectors from
11 | const importFrom = [].concat(Object(opts).importFrom || []);
12 |
13 | // destinations to export custom selectors to
14 | const exportTo = [].concat(Object(opts).exportTo || []);
15 |
16 | // promise any custom selectors are imported
17 | const customPropertiesPromise = getCustomPropertiesFromImports(importFrom);
18 |
19 | let customProperties;
20 |
21 | // whether to return synchronous function if no asynchronous operations are requested
22 | const canReturnSyncFunction = importFrom.length === 0 && exportTo.length === 0;
23 |
24 | return {
25 | postcssPlugin: 'postcss-custom-properties',
26 | prepare ({ root }) {
27 | if (canReturnSyncFunction) {
28 | customProperties = getCustomPropertiesFromRoot(root, { preserve });
29 |
30 | return {
31 | Declaration: (decl) => transformProperties(decl, customProperties, { preserve })
32 | }
33 | } else {
34 | return {
35 | Once: async root => {
36 | customProperties = Object.assign(
37 | {},
38 | getCustomPropertiesFromRoot(root, { preserve }),
39 | await customPropertiesPromise,
40 | );
41 |
42 | await writeCustomPropertiesToExports(customProperties, exportTo);
43 | },
44 | Declaration: (decl) => transformProperties(decl, customProperties, { preserve })
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
51 | creator.postcss = true
52 |
53 | export default creator
54 |
--------------------------------------------------------------------------------
/src/lib/get-custom-properties-from-imports.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { parse } from 'postcss';
4 | import valuesParser from 'postcss-value-parser';
5 | import getCustomPropertiesFromRoot from './get-custom-properties-from-root';
6 |
7 | /* Get Custom Properties from CSS File
8 | /* ========================================================================== */
9 |
10 | async function getCustomPropertiesFromCSSFile(from) {
11 | const css = await readFile(from);
12 | const root = parse(css, { from });
13 |
14 | return getCustomPropertiesFromRoot(root, { preserve: true });
15 | }
16 |
17 | /* Get Custom Properties from Object
18 | /* ========================================================================== */
19 |
20 | function getCustomPropertiesFromObject(object) {
21 | const customProperties = Object.assign(
22 | {},
23 | Object(object).customProperties,
24 | Object(object)['custom-properties']
25 | );
26 |
27 | for (const key in customProperties) {
28 | customProperties[key] = valuesParser(String(customProperties[key]));
29 | }
30 |
31 | return customProperties;
32 | }
33 |
34 | /* Get Custom Properties from JSON file
35 | /* ========================================================================== */
36 |
37 | async function getCustomPropertiesFromJSONFile(from) {
38 | const object = await readJSON(from);
39 |
40 | return getCustomPropertiesFromObject(object);
41 | }
42 |
43 | /* Get Custom Properties from JS file
44 | /* ========================================================================== */
45 |
46 | async function getCustomPropertiesFromJSFile(from) {
47 | const object = await import(from);
48 |
49 | return getCustomPropertiesFromObject(object);
50 | }
51 |
52 | /* Get Custom Properties from Imports
53 | /* ========================================================================== */
54 |
55 | export default function getCustomPropertiesFromImports(sources) {
56 | return sources.map(source => {
57 | if (source instanceof Promise) {
58 | return source;
59 | } else if (source instanceof Function) {
60 | return source();
61 | }
62 |
63 | // read the source as an object
64 | const opts = source === Object(source) ? source : { from: String(source) };
65 |
66 | // skip objects with Custom Properties
67 | if (opts.customProperties || opts['custom-properties']) {
68 | return opts
69 | }
70 |
71 | // source pathname
72 | const from = path.resolve(String(opts.from || ''));
73 |
74 | // type of file being read from
75 | const type = (opts.type || path.extname(from).slice(1)).toLowerCase();
76 |
77 | return { type, from };
78 | }).reduce(async (customProperties, source) => {
79 | const { type, from } = await source;
80 |
81 | if (type === 'css' || type === 'pcss') {
82 | return Object.assign(await customProperties, await getCustomPropertiesFromCSSFile(from));
83 | }
84 |
85 | if (type === 'js') {
86 | return Object.assign(await customProperties, await getCustomPropertiesFromJSFile(from));
87 | }
88 |
89 | if (type === 'json') {
90 | return Object.assign(await customProperties, await getCustomPropertiesFromJSONFile(from));
91 | }
92 |
93 | return Object.assign(await customProperties, await getCustomPropertiesFromObject(await source));
94 | }, {});
95 | }
96 |
97 | /* Helper utilities
98 | /* ========================================================================== */
99 |
100 | const readFile = from => new Promise((resolve, reject) => {
101 | fs.readFile(from, 'utf8', (error, result) => {
102 | if (error) {
103 | reject(error);
104 | } else {
105 | resolve(result);
106 | }
107 | });
108 | });
109 |
110 | const readJSON = async from => JSON.parse(await readFile(from));
111 |
--------------------------------------------------------------------------------
/src/lib/get-custom-properties-from-root.js:
--------------------------------------------------------------------------------
1 | import valuesParser from 'postcss-value-parser';
2 | import { isBlockIgnored } from './is-ignored';
3 |
4 | // return custom selectors from the css root, conditionally removing them
5 | export default function getCustomPropertiesFromRoot(root, opts) {
6 | // initialize custom selectors
7 | const customPropertiesFromHtmlElement = {};
8 | const customPropertiesFromRootPseudo = {};
9 |
10 | // for each html or :root rule
11 | root.nodes.slice().forEach(rule => {
12 | const customPropertiesObject = isHtmlRule(rule)
13 | ? customPropertiesFromHtmlElement
14 | : isRootRule(rule)
15 | ? customPropertiesFromRootPseudo
16 | : null;
17 |
18 | // for each custom property
19 | if (customPropertiesObject) {
20 | rule.nodes.slice().forEach(decl => {
21 | if (isCustomDecl(decl) && !isBlockIgnored(decl)) {
22 | const { prop } = decl;
23 |
24 | // write the parsed value to the custom property
25 | customPropertiesObject[prop] = valuesParser(decl.value);
26 |
27 | // conditionally remove the custom property declaration
28 | if (!opts.preserve) {
29 | decl.remove();
30 | }
31 | }
32 | });
33 |
34 | // conditionally remove the empty html or :root rule
35 | if (!opts.preserve && isEmptyParent(rule) && !isBlockIgnored(rule)) {
36 | rule.remove();
37 | }
38 | }
39 | });
40 |
41 | // return all custom properties, preferring :root properties over html properties
42 | return { ...customPropertiesFromHtmlElement, ...customPropertiesFromRootPseudo };
43 | }
44 |
45 | // match html and :root rules
46 | const htmlSelectorRegExp = /^html$/i;
47 | const rootSelectorRegExp = /^:root$/i;
48 | const customPropertyRegExp = /^--[A-z][\w-]*$/;
49 |
50 | // whether the node is an html or :root rule
51 | const isHtmlRule = node => node.type === 'rule' && node.selector.split(',').some(item => htmlSelectorRegExp.test(item)) && Object(node.nodes).length;
52 | const isRootRule = node => node.type === 'rule' && node.selector.split(',').some(item => rootSelectorRegExp.test(item)) && Object(node.nodes).length;
53 |
54 | // whether the node is an custom property
55 | const isCustomDecl = node => node.type === 'decl' && customPropertyRegExp.test(node.prop);
56 |
57 | // whether the node is a parent without children
58 | const isEmptyParent = node => Object(node.nodes).length === 0;
59 |
--------------------------------------------------------------------------------
/src/lib/is-ignored.js:
--------------------------------------------------------------------------------
1 | function isBlockIgnored(ruleOrDeclaration) {
2 | var rule = ruleOrDeclaration.selector ?
3 | ruleOrDeclaration : ruleOrDeclaration.parent;
4 |
5 | return /(!\s*)?postcss-custom-properties:\s*off\b/i.test(rule.toString())
6 | }
7 |
8 | function isRuleIgnored(rule) {
9 | var previous = rule.prev();
10 |
11 | return Boolean(isBlockIgnored(rule) ||
12 | previous &&
13 | previous.type === 'comment' &&
14 | /(!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i.test(previous.text));
15 | }
16 |
17 | export {
18 | isBlockIgnored,
19 | isRuleIgnored
20 | }
21 |
--------------------------------------------------------------------------------
/src/lib/transform-properties.js:
--------------------------------------------------------------------------------
1 | import valuesParser from 'postcss-value-parser';
2 | import transformValueAST from './transform-value-ast';
3 | import { isRuleIgnored } from './is-ignored';
4 |
5 | // transform custom pseudo selectors with custom selectors
6 | export default (decl, customProperties, opts) => {
7 | if (isTransformableDecl(decl) && !isRuleIgnored(decl)) {
8 | const originalValue = decl.value;
9 | const valueAST = valuesParser(originalValue);
10 | let value = transformValueAST(valueAST, customProperties);
11 |
12 | // protect against circular references
13 | const valueSet = new Set();
14 |
15 | while (customPropertiesRegExp.test(value) && !valueSet.has(value)) {
16 | valueSet.add(value);
17 | const parsedValueAST = valuesParser(value);
18 | value = transformValueAST(parsedValueAST, customProperties);
19 | }
20 |
21 | // conditionally transform values that have changed
22 | if (value !== originalValue) {
23 | if (opts.preserve) {
24 | const beforeDecl = decl.cloneBefore({ value });
25 |
26 | if (hasTrailingComment(beforeDecl)) {
27 | beforeDecl.raws.value.value = beforeDecl.value.replace(trailingCommentRegExp, '$1');
28 | beforeDecl.raws.value.raw = beforeDecl.raws.value.value + beforeDecl.raws.value.raw.replace(trailingCommentRegExp, '$2');
29 | }
30 | } else {
31 | decl.value = value;
32 |
33 | if (hasTrailingComment(decl)) {
34 | decl.raws.value.value = decl.value.replace(trailingCommentRegExp, '$1');
35 | decl.raws.value.raw = decl.raws.value.value + decl.raws.value.raw.replace(trailingCommentRegExp, '$2');
36 | }
37 | }
38 | }
39 | }
40 | };
41 |
42 | // match custom properties
43 | const customPropertyRegExp = /^--[A-z][\w-]*$/;
44 |
45 | // match custom property inclusions
46 | const customPropertiesRegExp = /(^|[^\w-])var\([\W\w]+\)/;
47 |
48 | // whether the declaration should be potentially transformed
49 | const isTransformableDecl = decl => !customPropertyRegExp.test(decl.prop) && customPropertiesRegExp.test(decl.value);
50 |
51 | // whether the declaration has a trailing comment
52 | const hasTrailingComment = decl => 'value' in Object(Object(decl.raws).value) && 'raw' in decl.raws.value && trailingCommentRegExp.test(decl.raws.value.raw);
53 | const trailingCommentRegExp = /^([\W\w]+)(\s*\/\*[\W\w]+?\*\/)$/;
54 |
--------------------------------------------------------------------------------
/src/lib/transform-value-ast.js:
--------------------------------------------------------------------------------
1 | export default function transformValueAST(root, customProperties) {
2 | if (root.nodes && root.nodes.length) {
3 | root.nodes.slice().forEach((child) => {
4 | if (isVarFunction(child)) {
5 | const [propertyNode, ...fallbacks] = child.nodes.filter((node) => node.type !== 'div');
6 | const { value: name } = propertyNode;
7 | const index = root.nodes.indexOf(child);
8 |
9 | if (name in Object(customProperties)) {
10 | // Direct match of a custom property to a parsed value
11 | const nodes = customProperties[name].nodes;
12 |
13 | // Re-transform nested properties without given one to avoid circular from keeping this forever
14 | retransformValueAST({ nodes }, customProperties, name);
15 |
16 | if (index > -1) {
17 | root.nodes.splice(index, 1, ...nodes);
18 | }
19 | } else if (fallbacks.length) {
20 | // No match, but fallback available
21 | if (index > -1) {
22 | root.nodes.splice(index, 1, ...fallbacks);
23 | }
24 |
25 | transformValueAST(root, customProperties);
26 | }
27 | } else {
28 | // Transform child nodes of current child
29 | transformValueAST(child, customProperties);
30 | }
31 | });
32 | }
33 |
34 | return root.toString();
35 | }
36 |
37 | // retransform the current ast without a custom property (to prevent recursion)
38 | function retransformValueAST(root, customProperties, withoutProperty) {
39 | const nextCustomProperties = Object.assign({}, customProperties);
40 |
41 | delete nextCustomProperties[withoutProperty];
42 |
43 | return transformValueAST(root, nextCustomProperties);
44 | }
45 |
46 | // match var() functions
47 | const varRegExp = /^var$/i;
48 |
49 | // whether the node is a var() function
50 | const isVarFunction = node => node.type === 'function' && varRegExp.test(node.value) && Object(node.nodes).length > 0;
51 |
--------------------------------------------------------------------------------
/src/lib/write-custom-properties-to-exports.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 |
4 | /* Write Custom Properties to CSS File
5 | /* ========================================================================== */
6 |
7 | async function writeCustomPropertiesToCssFile(to, customProperties) {
8 | const cssContent = Object.keys(customProperties).reduce((cssLines, name) => {
9 | cssLines.push(`\t${name}: ${customProperties[name]};`);
10 |
11 | return cssLines;
12 | }, []).join('\n');
13 | const css = `:root {\n${cssContent}\n}\n`;
14 |
15 | await writeFile(to, css);
16 | }
17 |
18 | /* Write Custom Properties to SCSS File
19 | /* ========================================================================== */
20 |
21 | async function writeCustomPropertiesToScssFile(to, customProperties) {
22 | const scssContent = Object.keys(customProperties).reduce((scssLines, name) => {
23 | const scssName = name.replace('--', '$');
24 | scssLines.push(`${scssName}: ${customProperties[name]};`);
25 |
26 | return scssLines;
27 | }, []).join('\n');
28 | const scss = `${scssContent}\n`;
29 |
30 | await writeFile(to, scss);
31 | }
32 |
33 | /* Write Custom Properties to JSON file
34 | /* ========================================================================== */
35 |
36 | async function writeCustomPropertiesToJsonFile(to, customProperties) {
37 | const jsonContent = JSON.stringify({
38 | 'custom-properties': customProperties
39 | }, null, ' ');
40 | const json = `${jsonContent}\n`;
41 |
42 | await writeFile(to, json);
43 | }
44 |
45 | /* Write Custom Properties to Common JS file
46 | /* ========================================================================== */
47 |
48 | async function writeCustomPropertiesToCjsFile(to, customProperties) {
49 | const jsContents = Object.keys(customProperties).reduce((jsLines, name) => {
50 | jsLines.push(`\t\t'${escapeForJS(name)}': '${escapeForJS(customProperties[name])}'`);
51 |
52 | return jsLines;
53 | }, []).join(',\n');
54 | const js = `module.exports = {\n\tcustomProperties: {\n${jsContents}\n\t}\n};\n`;
55 |
56 | await writeFile(to, js);
57 | }
58 |
59 | /* Write Custom Properties to Module JS file
60 | /* ========================================================================== */
61 |
62 | async function writeCustomPropertiesToMjsFile(to, customProperties) {
63 | const mjsContents = Object.keys(customProperties).reduce((mjsLines, name) => {
64 | mjsLines.push(`\t'${escapeForJS(name)}': '${escapeForJS(customProperties[name])}'`);
65 |
66 | return mjsLines;
67 | }, []).join(',\n');
68 | const mjs = `export const customProperties = {\n${mjsContents}\n};\n`;
69 |
70 | await writeFile(to, mjs);
71 | }
72 |
73 | /* Write Custom Properties to Exports
74 | /* ========================================================================== */
75 |
76 | export default function writeCustomPropertiesToExports(customProperties, destinations) {
77 | return Promise.all(destinations.map(async destination => {
78 | if (destination instanceof Function) {
79 | await destination(defaultCustomPropertiesToJSON(customProperties));
80 | } else {
81 | // read the destination as an object
82 | const opts = destination === Object(destination) ? destination : { to: String(destination) };
83 |
84 | // transformer for Custom Properties into a JSON-compatible object
85 | const toJSON = opts.toJSON || defaultCustomPropertiesToJSON;
86 |
87 | if ('customProperties' in opts) {
88 | // write directly to an object as customProperties
89 | opts.customProperties = toJSON(customProperties);
90 | } else if ('custom-properties' in opts) {
91 | // write directly to an object as custom-properties
92 | opts['custom-properties'] = toJSON(customProperties);
93 | } else {
94 | // destination pathname
95 | const to = String(opts.to || '');
96 |
97 | // type of file being written to
98 | const type = (opts.type || path.extname(opts.to).slice(1)).toLowerCase();
99 |
100 | // transformed Custom Properties
101 | const customPropertiesJSON = toJSON(customProperties);
102 |
103 | if (type === 'css') {
104 | await writeCustomPropertiesToCssFile(to, customPropertiesJSON);
105 | }
106 |
107 | if (type === 'scss') {
108 | await writeCustomPropertiesToScssFile(to, customPropertiesJSON);
109 | }
110 |
111 | if (type === 'js') {
112 | await writeCustomPropertiesToCjsFile(to, customPropertiesJSON);
113 | }
114 |
115 | if (type === 'json') {
116 | await writeCustomPropertiesToJsonFile(to, customPropertiesJSON);
117 | }
118 |
119 | if (type === 'mjs') {
120 | await writeCustomPropertiesToMjsFile(to, customPropertiesJSON);
121 | }
122 | }
123 | }
124 | }));
125 | }
126 |
127 | /* Helper utilities
128 | /* ========================================================================== */
129 |
130 | const defaultCustomPropertiesToJSON = customProperties => {
131 | return Object.keys(customProperties).reduce((customPropertiesJSON, key) => {
132 | const valueNodes = customProperties[key];
133 | customPropertiesJSON[key] = valueNodes.toString();
134 |
135 | return customPropertiesJSON;
136 | }, {});
137 | };
138 |
139 | const writeFile = (to, text) => new Promise((resolve, reject) => {
140 | fs.writeFile(to, text, error => {
141 | if (error) {
142 | reject(error);
143 | } else {
144 | resolve();
145 | }
146 | });
147 | });
148 |
149 | const escapeForJS = string => string.replace(/\\([\s\S])|(')/g, '\\$1$2').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
150 |
--------------------------------------------------------------------------------
/test/basic.css:
--------------------------------------------------------------------------------
1 | html {
2 | --ref-color: skip;
3 | }
4 |
5 | :root {
6 | --color: rgb(255, 0, 0);
7 | --color-h: 0;
8 | --color-s: 100%;
9 | --color-l: 50%;
10 | --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l));
11 | --ref-color: var(--color);
12 | --circular: var(--circular-2);
13 | --circular-2: var(--circular);
14 | --margin: 0 10px 20px 30px;
15 | --shadow-color: rgb(255,0,0);
16 | --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15));
17 | --font-family: "Open Sans", sans-serif;
18 | color: var(--color);
19 | }
20 |
21 | :root,
22 | [data-theme=light] {
23 | --theme-color: #053;
24 | }
25 |
26 | .ignore-line {
27 | /* postcss-custom-properties: ignore next */
28 | color: var(--color);
29 | background-color: var(--color-2, blue);
30 | }
31 |
32 | .ignore-block {
33 | /* postcss-custom-properties: off */
34 | color: var(--color-2, blue);
35 | box-shadow: inset 0 -3px 0 var(--color);
36 | background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%);
37 | }
38 |
39 | .test {
40 | --skip: gray;
41 | color: var(--override, var(--color));
42 | }
43 |
44 | .test--color_spacing {
45 | box-shadow: inset 0 -3px 0 var(--color);
46 | }
47 |
48 | .test--preserve_whitespaces {
49 | margin: var(--margin);
50 | }
51 |
52 | .test--complex_values {
53 | box-shadow: var(--shadow);
54 | }
55 |
56 | .test--comma_separated_values {
57 | font-family: var(--font-family);
58 | }
59 |
60 | .test--fallback {
61 | color: var(--color-2, blue);
62 | }
63 |
64 | .test--color_w_var {
65 | color: var(--ref-color);
66 | }
67 |
68 | .test--color_w_vars {
69 | color: var(--color-hsl);
70 | }
71 |
72 | .test--circular_var {
73 | color: var(--circular);
74 | }
75 |
76 | .test--z-index {
77 | z-index: var(--z-index);
78 | }
79 |
80 | .test--nested-fallback {
81 | z-index: var(--xxx, var(--yyy, 1));
82 | }
83 |
84 | .text--calc {
85 | width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px));
86 | }
87 |
88 | .test--linear-gradient {
89 | background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%);
90 | }
91 |
92 | .test--loose-formatting {
93 | color: var(
94 | --color,
95 | blue
96 | )/*rtl:red*/;
97 | }
98 |
99 | .test--combined-selector {
100 | color: var(--theme-color);
101 | }
102 |
--------------------------------------------------------------------------------
/test/basic.expect.css:
--------------------------------------------------------------------------------
1 | html {
2 | --ref-color: skip;
3 | }
4 |
5 | :root {
6 | --color: rgb(255, 0, 0);
7 | --color-h: 0;
8 | --color-s: 100%;
9 | --color-l: 50%;
10 | --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l));
11 | --ref-color: var(--color);
12 | --circular: var(--circular-2);
13 | --circular-2: var(--circular);
14 | --margin: 0 10px 20px 30px;
15 | --shadow-color: rgb(255,0,0);
16 | --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15));
17 | --font-family: "Open Sans", sans-serif;
18 | color: rgb(255, 0, 0);
19 | color: var(--color);
20 | }
21 |
22 | :root,
23 | [data-theme=light] {
24 | --theme-color: #053;
25 | }
26 |
27 | .ignore-line {
28 | /* postcss-custom-properties: ignore next */
29 | color: var(--color);
30 | background-color: blue;
31 | background-color: var(--color-2, blue);
32 | }
33 |
34 | .ignore-block {
35 | /* postcss-custom-properties: off */
36 | color: var(--color-2, blue);
37 | box-shadow: inset 0 -3px 0 var(--color);
38 | background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%);
39 | }
40 |
41 | .test {
42 | --skip: gray;
43 | color: rgb(255, 0, 0);
44 | color: var(--override, var(--color));
45 | }
46 |
47 | .test--color_spacing {
48 | box-shadow: inset 0 -3px 0 rgb(255, 0, 0);
49 | box-shadow: inset 0 -3px 0 var(--color);
50 | }
51 |
52 | .test--preserve_whitespaces {
53 | margin: 0 10px 20px 30px;
54 | margin: var(--margin);
55 | }
56 |
57 | .test--complex_values {
58 | box-shadow: 0 6px 14px 0 color(rgb(255,0,0) a(.15));
59 | box-shadow: var(--shadow);
60 | }
61 |
62 | .test--comma_separated_values {
63 | font-family: "Open Sans", sans-serif;
64 | font-family: var(--font-family);
65 | }
66 |
67 | .test--fallback {
68 | color: blue;
69 | color: var(--color-2, blue);
70 | }
71 |
72 | .test--color_w_var {
73 | color: rgb(255, 0, 0);
74 | color: var(--ref-color);
75 | }
76 |
77 | .test--color_w_vars {
78 | color: hsl(0, 100%, 50%);
79 | color: var(--color-hsl);
80 | }
81 |
82 | .test--circular_var {
83 | color: var(--circular);
84 | }
85 |
86 | .test--z-index {
87 | z-index: var(--z-index);
88 | }
89 |
90 | .test--nested-fallback {
91 | z-index: 1;
92 | z-index: var(--xxx, var(--yyy, 1));
93 | }
94 |
95 | .text--calc {
96 | width: calc((100% - 1px) + 10px);
97 | width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px));
98 | }
99 |
100 | .test--linear-gradient {
101 | background-image: linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(255, 0, 0) 100%);
102 | background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%);
103 | }
104 |
105 | .test--loose-formatting {
106 | color: rgb(255, 0, 0)/*rtl:red*/;
107 | color: var(
108 | --color,
109 | blue
110 | )/*rtl:red*/;
111 | }
112 |
113 | .test--combined-selector {
114 | color: #053;
115 | color: var(--theme-color);
116 | }
117 |
--------------------------------------------------------------------------------
/test/basic.import-is-empty.expect.css:
--------------------------------------------------------------------------------
1 | html {
2 | --ref-color: skip;
3 | }
4 |
5 | :root {
6 | --color: rgb(255, 0, 0);
7 | --color-h: 0;
8 | --color-s: 100%;
9 | --color-l: 50%;
10 | --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l));
11 | --ref-color: var(--color);
12 | --circular: var(--circular-2);
13 | --circular-2: var(--circular);
14 | --margin: 0 10px 20px 30px;
15 | --shadow-color: rgb(255,0,0);
16 | --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15));
17 | --font-family: "Open Sans", sans-serif;
18 | color: rgb(255, 0, 0);
19 | color: var(--color);
20 | }
21 |
22 | :root,
23 | [data-theme=light] {
24 | --theme-color: #053;
25 | }
26 |
27 | .ignore-line {
28 | /* postcss-custom-properties: ignore next */
29 | color: var(--color);
30 | background-color: blue;
31 | background-color: var(--color-2, blue);
32 | }
33 |
34 | .ignore-block {
35 | /* postcss-custom-properties: off */
36 | color: var(--color-2, blue);
37 | box-shadow: inset 0 -3px 0 var(--color);
38 | background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%);
39 | }
40 |
41 | .test {
42 | --skip: gray;
43 | color: rgb(255, 0, 0);
44 | color: var(--override, var(--color));
45 | }
46 |
47 | .test--color_spacing {
48 | box-shadow: inset 0 -3px 0 rgb(255, 0, 0);
49 | box-shadow: inset 0 -3px 0 var(--color);
50 | }
51 |
52 | .test--preserve_whitespaces {
53 | margin: 0 10px 20px 30px;
54 | margin: var(--margin);
55 | }
56 |
57 | .test--complex_values {
58 | box-shadow: 0 6px 14px 0 color(rgb(255,0,0) a(.15));
59 | box-shadow: var(--shadow);
60 | }
61 |
62 | .test--comma_separated_values {
63 | font-family: "Open Sans", sans-serif;
64 | font-family: var(--font-family);
65 | }
66 |
67 | .test--fallback {
68 | color: blue;
69 | color: var(--color-2, blue);
70 | }
71 |
72 | .test--color_w_var {
73 | color: rgb(255, 0, 0);
74 | color: var(--ref-color);
75 | }
76 |
77 | .test--color_w_vars {
78 | color: hsl(0, 100%, 50%);
79 | color: var(--color-hsl);
80 | }
81 |
82 | .test--circular_var {
83 | color: var(--circular);
84 | }
85 |
86 | .test--z-index {
87 | z-index: var(--z-index);
88 | }
89 |
90 | .test--nested-fallback {
91 | z-index: 1;
92 | z-index: var(--xxx, var(--yyy, 1));
93 | }
94 |
95 | .text--calc {
96 | width: calc((100% - 1px) + 10px);
97 | width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px));
98 | }
99 |
100 | .test--linear-gradient {
101 | background-image: linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(255, 0, 0) 100%);
102 | background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%);
103 | }
104 |
105 | .test--loose-formatting {
106 | color: rgb(255, 0, 0)/*rtl:red*/;
107 | color: var(
108 | --color,
109 | blue
110 | )/*rtl:red*/;
111 | }
112 |
113 | .test--combined-selector {
114 | color: #053;
115 | color: var(--theme-color);
116 | }
117 |
--------------------------------------------------------------------------------
/test/basic.import-override.expect.css:
--------------------------------------------------------------------------------
1 | :root {
2 | color: rgb(0, 0, 0);
3 | }
4 |
5 | .ignore-line {
6 | /* postcss-custom-properties: ignore next */
7 | color: var(--color);
8 | background-color: yellow;
9 | }
10 |
11 | .ignore-block {
12 | /* postcss-custom-properties: off */
13 | color: var(--color-2, blue);
14 | box-shadow: inset 0 -3px 0 var(--color);
15 | background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%);
16 | }
17 |
18 | .test {
19 | --skip: gray;
20 | color: rgb(0, 0, 0);
21 | }
22 |
23 | .test--color_spacing {
24 | box-shadow: inset 0 -3px 0 rgb(0, 0, 0);
25 | }
26 |
27 | .test--preserve_whitespaces {
28 | margin: 0 10px 20px 30px;
29 | }
30 |
31 | .test--complex_values {
32 | box-shadow: 0 6px 14px 0 color(rgb(0,0,0) a(.15));
33 | }
34 |
35 | .test--comma_separated_values {
36 | font-family: "Open Sans", sans-serif;
37 | }
38 |
39 | .test--fallback {
40 | color: yellow;
41 | }
42 |
43 | .test--color_w_var {
44 | color: rgb(0, 0, 0);
45 | }
46 |
47 | .test--color_w_vars {
48 | color: hsl(0, 100%, 50%);
49 | }
50 |
51 | .test--circular_var {
52 | color: var(--circular);
53 | }
54 |
55 | .test--z-index {
56 | z-index: 10;
57 | }
58 |
59 | .test--nested-fallback {
60 | z-index: 1;
61 | }
62 |
63 | .text--calc {
64 | width: calc((100% - 1px) + 10px);
65 | }
66 |
67 | .test--linear-gradient {
68 | background-image: linear-gradient(to right, rgb(0, 0, 0) 0%, rgb(0, 0, 0) 100%);
69 | }
70 |
71 | .test--loose-formatting {
72 | color: rgb(0, 0, 0)/*rtl:red*/;
73 | }
74 |
75 | .test--combined-selector {
76 | color: #053;
77 | }
78 |
--------------------------------------------------------------------------------
/test/basic.import.expect.css:
--------------------------------------------------------------------------------
1 | html {
2 | --ref-color: skip;
3 | }
4 |
5 | :root {
6 | --color: rgb(255, 0, 0);
7 | --color-h: 0;
8 | --color-s: 100%;
9 | --color-l: 50%;
10 | --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l));
11 | --ref-color: var(--color);
12 | --circular: var(--circular-2);
13 | --circular-2: var(--circular);
14 | --margin: 0 10px 20px 30px;
15 | --shadow-color: rgb(255,0,0);
16 | --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15));
17 | --font-family: "Open Sans", sans-serif;
18 | color: rgb(255, 0, 0);
19 | color: var(--color);
20 | }
21 |
22 | :root,
23 | [data-theme=light] {
24 | --theme-color: #053;
25 | }
26 |
27 | .ignore-line {
28 | /* postcss-custom-properties: ignore next */
29 | color: var(--color);
30 | background-color: yellow;
31 | background-color: var(--color-2, blue);
32 | }
33 |
34 | .ignore-block {
35 | /* postcss-custom-properties: off */
36 | color: var(--color-2, blue);
37 | box-shadow: inset 0 -3px 0 var(--color);
38 | background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%);
39 | }
40 |
41 | .test {
42 | --skip: gray;
43 | color: rgb(255, 0, 0);
44 | color: var(--override, var(--color));
45 | }
46 |
47 | .test--color_spacing {
48 | box-shadow: inset 0 -3px 0 rgb(255, 0, 0);
49 | box-shadow: inset 0 -3px 0 var(--color);
50 | }
51 |
52 | .test--preserve_whitespaces {
53 | margin: 0 10px 20px 30px;
54 | margin: var(--margin);
55 | }
56 |
57 | .test--complex_values {
58 | box-shadow: 0 6px 14px 0 color(rgb(255,0,0) a(.15));
59 | box-shadow: var(--shadow);
60 | }
61 |
62 | .test--comma_separated_values {
63 | font-family: "Open Sans", sans-serif;
64 | font-family: var(--font-family);
65 | }
66 |
67 | .test--fallback {
68 | color: yellow;
69 | color: var(--color-2, blue);
70 | }
71 |
72 | .test--color_w_var {
73 | color: rgb(255, 0, 0);
74 | color: var(--ref-color);
75 | }
76 |
77 | .test--color_w_vars {
78 | color: hsl(0, 100%, 50%);
79 | color: var(--color-hsl);
80 | }
81 |
82 | .test--circular_var {
83 | color: var(--circular);
84 | }
85 |
86 | .test--z-index {
87 | z-index: 10;
88 | z-index: var(--z-index);
89 | }
90 |
91 | .test--nested-fallback {
92 | z-index: 1;
93 | z-index: var(--xxx, var(--yyy, 1));
94 | }
95 |
96 | .text--calc {
97 | width: calc((100% - 1px) + 10px);
98 | width: calc((100% - var(--xxx, 1px)) + var(--yyy, 10px));
99 | }
100 |
101 | .test--linear-gradient {
102 | background-image: linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(255, 0, 0) 100%);
103 | background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%);
104 | }
105 |
106 | .test--loose-formatting {
107 | color: rgb(255, 0, 0)/*rtl:red*/;
108 | color: var(
109 | --color,
110 | blue
111 | )/*rtl:red*/;
112 | }
113 |
114 | .test--combined-selector {
115 | color: #053;
116 | color: var(--theme-color);
117 | }
118 |
--------------------------------------------------------------------------------
/test/basic.preserve.expect.css:
--------------------------------------------------------------------------------
1 | :root {
2 | color: rgb(255, 0, 0);
3 | }
4 |
5 | .ignore-line {
6 | /* postcss-custom-properties: ignore next */
7 | color: var(--color);
8 | background-color: blue;
9 | }
10 |
11 | .ignore-block {
12 | /* postcss-custom-properties: off */
13 | color: var(--color-2, blue);
14 | box-shadow: inset 0 -3px 0 var(--color);
15 | background-image: linear-gradient(to right, var(--color, transparent) 0%, var(--color, transparent) 100%);
16 | }
17 |
18 | .test {
19 | --skip: gray;
20 | color: rgb(255, 0, 0);
21 | }
22 |
23 | .test--color_spacing {
24 | box-shadow: inset 0 -3px 0 rgb(255, 0, 0);
25 | }
26 |
27 | .test--preserve_whitespaces {
28 | margin: 0 10px 20px 30px;
29 | }
30 |
31 | .test--complex_values {
32 | box-shadow: 0 6px 14px 0 color(rgb(255,0,0) a(.15));
33 | }
34 |
35 | .test--comma_separated_values {
36 | font-family: "Open Sans", sans-serif;
37 | }
38 |
39 | .test--fallback {
40 | color: blue;
41 | }
42 |
43 | .test--color_w_var {
44 | color: rgb(255, 0, 0);
45 | }
46 |
47 | .test--color_w_vars {
48 | color: hsl(0, 100%, 50%);
49 | }
50 |
51 | .test--circular_var {
52 | color: var(--circular);
53 | }
54 |
55 | .test--z-index {
56 | z-index: var(--z-index);
57 | }
58 |
59 | .test--nested-fallback {
60 | z-index: 1;
61 | }
62 |
63 | .text--calc {
64 | width: calc((100% - 1px) + 10px);
65 | }
66 |
67 | .test--linear-gradient {
68 | background-image: linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(255, 0, 0) 100%);
69 | }
70 |
71 | .test--loose-formatting {
72 | color: rgb(255, 0, 0)/*rtl:red*/;
73 | }
74 |
75 | .test--combined-selector {
76 | color: #053;
77 | }
78 |
--------------------------------------------------------------------------------
/test/export-properties.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --ref-color: var(--color);
3 | --color: rgb(255, 0, 0);
4 | --color-h: 0;
5 | --color-s: 100%;
6 | --color-l: 50%;
7 | --color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l));
8 | --circular: var(--circular-2);
9 | --circular-2: var(--circular);
10 | --margin: 0 10px 20px 30px;
11 | --shadow-color: rgb(255,0,0);
12 | --shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15));
13 | --font-family: "Open Sans", sans-serif;
14 | --theme-color: #053;
15 | }
16 |
--------------------------------------------------------------------------------
/test/export-properties.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | customProperties: {
3 | '--ref-color': 'var(--color)',
4 | '--color': 'rgb(255, 0, 0)',
5 | '--color-h': '0',
6 | '--color-s': '100%',
7 | '--color-l': '50%',
8 | '--color-hsl': 'hsl(var(--color-h), var(--color-s), var(--color-l))',
9 | '--circular': 'var(--circular-2)',
10 | '--circular-2': 'var(--circular)',
11 | '--margin': '0 10px 20px 30px',
12 | '--shadow-color': 'rgb(255,0,0)',
13 | '--shadow': '0 6px 14px 0 color(var(--shadow-color) a(.15))',
14 | '--font-family': '"Open Sans", sans-serif',
15 | '--theme-color': '#053'
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/test/export-properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "custom-properties": {
3 | "--ref-color": "var(--color)",
4 | "--color": "rgb(255, 0, 0)",
5 | "--color-h": "0",
6 | "--color-s": "100%",
7 | "--color-l": "50%",
8 | "--color-hsl": "hsl(var(--color-h), var(--color-s), var(--color-l))",
9 | "--circular": "var(--circular-2)",
10 | "--circular-2": "var(--circular)",
11 | "--margin": "0 10px 20px 30px",
12 | "--shadow-color": "rgb(255,0,0)",
13 | "--shadow": "0 6px 14px 0 color(var(--shadow-color) a(.15))",
14 | "--font-family": "\"Open Sans\", sans-serif",
15 | "--theme-color": "#053"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/test/export-properties.mjs:
--------------------------------------------------------------------------------
1 | export const customProperties = {
2 | '--ref-color': 'var(--color)',
3 | '--color': 'rgb(255, 0, 0)',
4 | '--color-h': '0',
5 | '--color-s': '100%',
6 | '--color-l': '50%',
7 | '--color-hsl': 'hsl(var(--color-h), var(--color-s), var(--color-l))',
8 | '--circular': 'var(--circular-2)',
9 | '--circular-2': 'var(--circular)',
10 | '--margin': '0 10px 20px 30px',
11 | '--shadow-color': 'rgb(255,0,0)',
12 | '--shadow': '0 6px 14px 0 color(var(--shadow-color) a(.15))',
13 | '--font-family': '"Open Sans", sans-serif',
14 | '--theme-color': '#053'
15 | };
16 |
--------------------------------------------------------------------------------
/test/export-properties.scss:
--------------------------------------------------------------------------------
1 | $ref-color: var(--color);
2 | $color: rgb(255, 0, 0);
3 | $color-h: 0;
4 | $color-s: 100%;
5 | $color-l: 50%;
6 | $color-hsl: hsl(var(--color-h), var(--color-s), var(--color-l));
7 | $circular: var(--circular-2);
8 | $circular-2: var(--circular);
9 | $margin: 0 10px 20px 30px;
10 | $shadow-color: rgb(255,0,0);
11 | $shadow: 0 6px 14px 0 color(var(--shadow-color) a(.15));
12 | $font-family: "Open Sans", sans-serif;
13 | $theme-color: #053;
14 |
--------------------------------------------------------------------------------
/test/import-properties-2.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --color: rgb(255, 0, 0);
3 | --color-2: yellow;
4 | }
5 |
--------------------------------------------------------------------------------
/test/import-properties-2.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | customProperties: {
3 | '--color': 'rgb(255, 0, 0)',
4 | '--color-2': 'yellow'
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/test/import-properties.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --ref-color: var(--color);
3 | --z-index: 10;
4 | }
5 |
--------------------------------------------------------------------------------
/test/import-properties.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | customProperties: {
3 | '--ref-color': 'var(--color)',
4 | '--z-index': 10
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/test/import-properties.json:
--------------------------------------------------------------------------------
1 | {
2 | "custom-properties": {
3 | "--color": "rgb(255, 0, 0)",
4 | "--color-2": "yellow",
5 | "--ref-color": "var(--color)",
6 | "--z-index": 10
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/import-properties.pcss:
--------------------------------------------------------------------------------
1 | :root {
2 | --ref-color: var(--color);
3 | --z-index: 10;
4 | }
5 |
--------------------------------------------------------------------------------