├── .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 |
⚠️ PostCSS Color Custom Properties was moved to @csstools/postcss-plugins. ⚠️
2 | Read the announcement
3 | 4 | # PostCSS Custom Properties [PostCSS][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 | [!['Can I use' table](https://caniuse.bitsofco.de/image/css-variables.png)](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 | --------------------------------------------------------------------------------