From d134c16f0a5fab67b777981e579f07b557c15601 Mon Sep 17 00:00:00 2001 From: Peter Galiba Date: Mon, 9 Dec 2019 10:43:35 +0000 Subject: [PATCH 01/21] Fix algorithm to find balanced var() pairs --- lib/resolve-value.js | 43 ++++++++++++++++++- test/fixtures/fallback-with-parenthesis.css | 6 +++ .../fallback-with-parenthesis.expected.css | 3 ++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/fallback-with-parenthesis.css create mode 100644 test/fixtures/fallback-with-parenthesis.expected.css diff --git a/lib/resolve-value.js b/lib/resolve-value.js index 1954758..b6c59da 100644 --- a/lib/resolve-value.js +++ b/lib/resolve-value.js @@ -14,6 +14,45 @@ function toString(value) { return String(value); } +// Check for balanced `var(` and `)` pairs inside `value`, and return the 3 fragments: +// `body` (inside), `pre` (before), `post` (after) of the found wrapper +function balancedVar(value) { + var match = balanced('(', ')', value) + if (match) { + // Check if it was prepended with var + if (/(?:^|\s)var$/.test(match.pre)) { + // Remove the var from the end of pre + return { + pre: match.pre.slice(0, -3), + body: match.body, + post: match.post + } + } else { + // Check inside body + var bodyMatch = balancedVar(match.body) + if (bodyMatch) { + // Reconstruct pre and post + return { + pre: match.pre + '(' + bodyMatch.pre, + body: bodyMatch.body, + post: bodyMatch.post + ')' + match.post + } + } else { + // Check inside post + var postMatch = balancedVar(match.post) + if (postMatch) { + // Reconstruct pre + return { + pre: match.pre + '(' + match.body + ')' + postMatch.pre, + body: postMatch.body, + post: postMatch.post + } + } + } + } + } +} + // Pass in a value string to parse/resolve and a map of available values // and we can figure out the final value // @@ -34,7 +73,7 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal // Create a temporary variable, storing resultantValue variable value var remainingVariableValue = resultantValue; // Use balanced lib to find var() declarations and store variable names - while ((matchingVarDecl = balanced('var(', ')', remainingVariableValue))) { + while ((matchingVarDecl = balancedVar(remainingVariableValue))) { // Split at the comma to find variable name and fallback value // There may be other commas in the values so this isn't necessarily just 2 pieces var variableFallbackSplitPieces = matchingVarDecl.body.split(','); @@ -61,7 +100,7 @@ var resolveValue = function(decl, map, /*optional*/ignorePseudoScope, /*internal // var() = var( [, ]? ) // matches `name[, fallback]`, captures "name" and "fallback" // See: http://dev.w3.org/csswg/css-variables/#funcdef-var - while ((matchingVarDecl = balanced('var(', ')', resultantValue))) { + while ((matchingVarDecl = balancedVar(resultantValue))) { var matchingVarDeclMapItem = undefined; // Split at the comma to find variable name and fallback value diff --git a/test/fixtures/fallback-with-parenthesis.css b/test/fixtures/fallback-with-parenthesis.css new file mode 100644 index 0000000..661408d --- /dev/null +++ b/test/fixtures/fallback-with-parenthesis.css @@ -0,0 +1,6 @@ +:root { + --box-shadow: 0px 2px 2px 0px #fff; +} +.box { + box-shadow: var(--box-shadow, 0px 2px 8px 0px rgba(0, 0, 0, 0.5)); +} \ No newline at end of file diff --git a/test/fixtures/fallback-with-parenthesis.expected.css b/test/fixtures/fallback-with-parenthesis.expected.css new file mode 100644 index 0000000..75680f0 --- /dev/null +++ b/test/fixtures/fallback-with-parenthesis.expected.css @@ -0,0 +1,3 @@ +.box { + box-shadow: 0px 2px 2px 0px #fff; +} \ No newline at end of file From 44107a2247f13d56723ff1d88611560d74c32b72 Mon Sep 17 00:00:00 2001 From: Peter Galiba Date: Mon, 9 Dec 2019 10:53:41 +0000 Subject: [PATCH 02/21] Add missing test for fallback --- test/test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test.js b/test/test.js index b0867b0..1f91bfd 100644 --- a/test/test.js +++ b/test/test.js @@ -89,6 +89,7 @@ describe('postcss-css-variables', function() { test('should work with variables declared in root', 'root-variable'); + test('should work with variables with parenthesis in fallback', 'fallback-with-parenthesis'); test('should work with locally scoped variable in a non-root rule', 'local-variable-non-root'); From bbbd64d3915498744b3a972f03d4889600395cb5 Mon Sep 17 00:00:00 2001 From: Peter Galiba Date: Mon, 9 Dec 2019 11:14:39 +0000 Subject: [PATCH 03/21] Less restricitive check for what can be before var( --- lib/resolve-value.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/resolve-value.js b/lib/resolve-value.js index b6c59da..60b6789 100644 --- a/lib/resolve-value.js +++ b/lib/resolve-value.js @@ -20,7 +20,7 @@ function balancedVar(value) { var match = balanced('(', ')', value) if (match) { // Check if it was prepended with var - if (/(?:^|\s)var$/.test(match.pre)) { + if (/(?:^|[^\w-])var$/.test(match.pre)) { // Remove the var from the end of pre return { pre: match.pre.slice(0, -3), From 9e7176a13e39e6132e8d87dadcf3d6c90daff4ee Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 24 Apr 2020 17:36:22 -0500 Subject: [PATCH 04/21] Prepare changelog with #112 --- CHANGELOG.md | 114 +++++++++++++++++++++++---------------------------- 1 file changed, 52 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 481bd2c..6adfd46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,139 +1,129 @@ +# v0.15.0 - 2020-4-24 + +- Fix algorithm to find balanced `var()` pairs and nested parenthesis + - Thank you to [@Poetro](https://github.com/juliovedovatto) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/112) + # v0.14.0 - 2019-11-24 - - Fix regex in `resolve-value.js` to allow nested CSS functions - - Thank you to [@juliovedovatto](https://github.com/juliovedovatto) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/97) +- Fix regex in `resolve-value.js` to allow nested CSS functions + - Thank you to [@juliovedovatto](https://github.com/juliovedovatto) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/97) # v0.13.0 - 2019-6-17 - - Add `options.preserveAtRulesOrder` so media queries are outputted in the order they are defined (as expected) - - Thank you to [@erikthalen](https://github.com/erikthalen) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/92) via https://github.com/MadLittleMods/postcss-css-variables/pull/101 - - Remove `calc` from readme table of contents for non-existent section - - Thank you to [@AlexandreArpin](https://github.com/AlexandreArpin) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/99) +- Add `options.preserveAtRulesOrder` so media queries are outputted in the order they are defined (as expected) + - Thank you to [@erikthalen](https://github.com/erikthalen) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/92) via https://github.com/MadLittleMods/postcss-css-variables/pull/101 +- Remove `calc` from readme table of contents for non-existent section + - Thank you to [@AlexandreArpin](https://github.com/AlexandreArpin) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/99) # v0.12.0 - 2019-2-21 - - Accept whitespace in `var( --var )` expression - - Thank you to [@benwest](https://github.com/benwest) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/93) - +- Accept whitespace in `var( --var )` expression + - Thank you to [@benwest](https://github.com/benwest) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/93) # v0.11.0 - 2018-10-9 - - Fix JS-defined variables using `isImportant`, https://github.com/MadLittleMods/postcss-css-variables/pull/87 - +- Fix JS-defined variables using `isImportant`, https://github.com/MadLittleMods/postcss-css-variables/pull/87 # v0.10.0 - 2018-9-25 - - Cast `opts.variables` variable values to string - - Thank you to [@shonie](https://github.com/shonie) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/84) - +- Cast `opts.variables` variable values to string + - Thank you to [@shonie](https://github.com/shonie) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/84) # v0.9.0 - 2018-6-26 - - Adds `opts.preserveInjectedVariables`, which when set to `false`, removes the `:root { ... }` custom property declarations added via `opts.variables` - - Thank you to [@akdetrick](https://github.com/akdetrick) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/74) - +- Adds `opts.preserveInjectedVariables`, which when set to `false`, removes the `:root { ... }` custom property declarations added via `opts.variables` + - Thank you to [@akdetrick](https://github.com/akdetrick) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/74) # v0.8.1 - 2018-3-21 - - Log `undefined` variables (available in `result.warnings()`) - - Thank you to [@pixeldrew](https://github.com/pixeldrew) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/69) - +- Log `undefined` variables (available in `result.warnings()`) + - Thank you to [@pixeldrew](https://github.com/pixeldrew) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/69) # v0.8.0 - 2017-8-8 - - Remove PostCSS `moveTo`/`append` deprecation warnings, [#50](https://github.com/MadLittleMods/postcss-css-variables/issues/50) - - Thank you to [@modosc](https://github.com/modosc) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/56) - +- Remove PostCSS `moveTo`/`append` deprecation warnings, [#50](https://github.com/MadLittleMods/postcss-css-variables/issues/50) + - Thank you to [@modosc](https://github.com/modosc) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/56) # v0.7.0 - 2017-3-12 - - Resolve `var` usage in fallbacks, [#37](https://github.com/MadLittleMods/postcss-css-variables/issues/37) - - Thank you to [@asvny](https://github.com/asvny) and [@marklu](https://github.com/marklu) for the contribution, [#39](https://github.com/MadLittleMods/postcss-css-variables/issues/39) -> [#49](https://github.com/MadLittleMods/postcss-css-variables/pull/49) - +- Resolve `var` usage in fallbacks, [#37](https://github.com/MadLittleMods/postcss-css-variables/issues/37) + - Thank you to [@asvny](https://github.com/asvny) and [@marklu](https://github.com/marklu) for the contribution, [#39](https://github.com/MadLittleMods/postcss-css-variables/issues/39) -> [#49](https://github.com/MadLittleMods/postcss-css-variables/pull/49) # v0.6.0 - 2016-9-23 - - Update/refactor readme - - Thank you to [@isiahmeadows](github.com/isiahmeadows) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/36) - - Use string value for `undefined` variables to play nice with other plugins downstream - - Thank you to [@vincentorback](github.com/vincentorback) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/44) - +- Update/refactor readme + - Thank you to [@isiahmeadows](github.com/isiahmeadows) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/36) +- Use string value for `undefined` variables to play nice with other plugins downstream + - Thank you to [@vincentorback](github.com/vincentorback) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/44) # v0.5.2 - 2016-8-24 - - Fix [#42](https://github.com/MadLittleMods/postcss-css-variables/issues/42) where `opts.preserve` was not working inside at-rules - - Thank you to [@muftiev](github.com/muftiev) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/43) - +- Fix [#42](https://github.com/MadLittleMods/postcss-css-variables/issues/42) where `opts.preserve` was not working inside at-rules + - Thank you to [@muftiev](github.com/muftiev) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/43) # v0.5.1 - 2015-10-24 - - Fix [postcss/postcss#611](https://github.com/postcss/postcss/issues/611) where we were trying to remove the root node on clean up - - Improved test setup +- Fix [postcss/postcss#611](https://github.com/postcss/postcss/issues/611) where we were trying to remove the root node on clean up +- Improved test setup # v0.5.0 - 2015-9-12 - - Upgrade to PostCSS v5. Fix [#20](https://github.com/MadLittleMods/postcss-css-variables/issues/20) +- Upgrade to PostCSS v5. Fix [#20](https://github.com/MadLittleMods/postcss-css-variables/issues/20) # v0.4.0 - 2015-7-2 - - Fix [#15](https://github.com/MadLittleMods/postcss-css-variables/issues/15) - - Remove slowness from cloning the `root` with `node.clone().removeAll()`. Now using `./lib/shallow-clone-node.js` to avoid cloning children which will get removed right after. - - Thank you to [@ddprrt](https://github.com/ddprrt) for bringing up the slowness issue in this article, [PostCSS misconceptions](https://medium.com/@ddprrt/postcss-misconceptions-faf5dc5038df). - - +- Fix [#15](https://github.com/MadLittleMods/postcss-css-variables/issues/15) - Remove slowness from cloning the `root` with `node.clone().removeAll()`. Now using `./lib/shallow-clone-node.js` to avoid cloning children which will get removed right after. - Thank you to [@ddprrt](https://github.com/ddprrt) for bringing up the slowness issue in this article, [PostCSS misconceptions](https://medium.com/@ddprrt/postcss-misconceptions-faf5dc5038df). # v0.3.9 - 2015-6-29 - - Remove `opts` global leak. Fix [#13](https://github.com/MadLittleMods/postcss-css-variables/issues/13) - +- Remove `opts` global leak. Fix [#13](https://github.com/MadLittleMods/postcss-css-variables/issues/13) # v0.3.8 - 2015-5-28 - - Add support for pseudo selectors `:hover` `:before` +- Add support for pseudo selectors `:hover` `:before` # v0.3.7 - 2015-5-27 - - Fix [#7](https://github.com/MadLittleMods/postcss-css-variables/issues/7): Support for child combinator - - Added tests for child-combinator/direct-descendant coverage +- Fix [#7](https://github.com/MadLittleMods/postcss-css-variables/issues/7): Support for child combinator +- Added tests for child-combinator/direct-descendant coverage # v0.3.6 - 2015-5-21 - - Fix [#6](https://github.com/MadLittleMods/postcss-css-variables/issues/6). Variable usage in comma separated selector to use proper scope +- Fix [#6](https://github.com/MadLittleMods/postcss-css-variables/issues/6). Variable usage in comma separated selector to use proper scope # v0.3.5 - 2015-5-12 - - Big refactor of code to reduce cyclomatic complexity. Still needs work though. - - Fix variable referencing another variable resolution when being changed by at-rule in non-root rule +- Big refactor of code to reduce cyclomatic complexity. Still needs work though. +- Fix variable referencing another variable resolution when being changed by at-rule in non-root rule # v0.3.4 - 2015-5-12 - - Fix variable referencing another variable resolution when being changed by at-rule +- Fix variable referencing another variable resolution when being changed by at-rule # v0.3.3 - 2015-5-11 - - Add support for last piece of combinator chain in selector resolution matching. - - `.foo + .bar` can match variables declared in `.bar` +- Add support for last piece of combinator chain in selector resolution matching. - `.foo + .bar` can match variables declared in `.bar` # v0.3.1 - 2015-5-5 - - Large overhaul of code to make it more robust on proper scope resolution. - - Fix [#2]](https://github.com/MadLittleMods/postcss-css-variables/issues/2) +- Large overhaul of code to make it more robust on proper scope resolution. +- Fix [#2]](https://github.com/MadLittleMods/postcss-css-variables/issues/2) # v0.2.3 - 2015-5-4 - - Add support for CSS4 descendant selector `>>` syntax +- Add support for CSS4 descendant selector `>>` syntax # v0.2.2 - 2015-5-1 - - Automatically prefix any variables defined in `options.variables` with `--` (according to CSS custom property syntax). +- Automatically prefix any variables defined in `options.variables` with `--` (according to CSS custom property syntax). # v0.2.1 - 2015-4-30 - - Added support for descendant selector nesting instead of just physical space nesting - - Fixed issue with comma separated rules. It was throwing a undefined is not a function error - - Moved to external scope check `isUnderScope` instead of integrated into `resolveValue` - - Added test for empty `var()` call. See [test/fixtures/empty-var-func.css](https://github.com/MadLittleMods/postcss-css-variables/blob/master/test/fixtures/empty-var-func.css) +- Added support for descendant selector nesting instead of just physical space nesting +- Fixed issue with comma separated rules. It was throwing a undefined is not a function error +- Moved to external scope check `isUnderScope` instead of integrated into `resolveValue` +- Added test for empty `var()` call. See [test/fixtures/empty-var-func.css](https://github.com/MadLittleMods/postcss-css-variables/blob/master/test/fixtures/empty-var-func.css) # v0.1.0 - 2015-4-29 - - First release +- First release From 3a86fa8ddf7311c68b8eabdcb5b9f997a0f35f47 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 24 Apr 2020 17:37:50 -0500 Subject: [PATCH 05/21] 0.15.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b92a5e7..ac46647 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-css-variables", - "version": "0.14.0", + "version": "0.15.0", "description": "PostCSS plugin to transform CSS Custom Properties(CSS variables) syntax into a static representation", "keywords": [ "postcss", From 7876dd0d5c44955a1dcdef8516ee49ac595908f6 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 24 Apr 2020 17:38:24 -0500 Subject: [PATCH 06/21] Fix #112 author link --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6adfd46..e05606d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # v0.15.0 - 2020-4-24 - Fix algorithm to find balanced `var()` pairs and nested parenthesis - - Thank you to [@Poetro](https://github.com/juliovedovatto) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/112) + - Thank you to [@Poetro](https://github.com/Poetro) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/112) # v0.14.0 - 2019-11-24 From f92ee48e4709563f1684b83f310134bf60ea3325 Mon Sep 17 00:00:00 2001 From: Marco Ernst Date: Sat, 25 Apr 2020 00:52:18 +0200 Subject: [PATCH 07/21] Add conditional preservation behavior (#116) * Add conditional preservation behavior * Add conditional preservation behavior --- README.md | 1 + index.js | 10 +++++++-- lib/resolve-decl.js | 10 +++++++-- .../preserve-variables-conditionally.css | 21 ++++++++++++++++++ ...serve-variables-conditionally.expected.css | 22 +++++++++++++++++++ test/test.js | 13 +++++++++++ 6 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/preserve-variables-conditionally.css create mode 100644 test/fixtures/preserve-variables-conditionally.expected.css diff --git a/README.md b/README.md index eebf01c..ad1dc2c 100644 --- a/README.md +++ b/README.md @@ -355,6 +355,7 @@ Possible values: - `false`: Removes `--var` declarations and replaces `var()` with their resolved/computed values. - `true`: Keeps `var()` declarations in the output and has the computed value as a fallback declaration. Also keeps computed `--var` declarations. - `'computed'`: Keeps computed `--var` declarations in the output. Handy to make them available to your JavaScript. + - `(declaration) => boolean|'computed'` : Handles preservation behavior based on the respective declaration. ### `variables` (default: `{}`) diff --git a/index.js b/index.js index abcfe8d..70c8282 100644 --- a/index.js +++ b/index.js @@ -183,12 +183,18 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) { }); }); + let preserveDecl; + if (typeof opts.preserve === "function") { + preserveDecl = opts.preserve(decl); + } else { + preserveDecl = opts.preserve; + } // Remove the variable declaration because they are pretty much useless after we resolve them - if(!opts.preserve) { + if(!preserveDecl) { decl.remove(); } // Or we can also just show the computed value used for that variable - else if(opts.preserve === 'computed') { + else if(preserveDecl === 'computed') { decl.value = valueResults.value; } // Otherwise just keep them as var declarations diff --git a/lib/resolve-decl.js b/lib/resolve-decl.js index 214f570..e469849 100644 --- a/lib/resolve-decl.js +++ b/lib/resolve-decl.js @@ -72,7 +72,7 @@ function eachMapItemDependencyOfDecl(variablesUsedList, map, decl, cb) { // Resolve the decl with the computed value // Also add in any media queries that change the value as necessary function resolveDecl(decl, map, /*optional*/shouldPreserve, /*optional*/preserveAtRulesOrder, /*optional*/logResolveValueResult) { - shouldPreserve = shouldPreserve || false; + shouldPreserve = (typeof shouldPreserve === "function" ? shouldPreserve(decl) : shouldPreserve) || false; preserveAtRulesOrder = preserveAtRulesOrder || false; // Make it chainable @@ -101,7 +101,13 @@ function resolveDecl(decl, map, /*optional*/shouldPreserve, /*optional*/preserve // Add the declaration to our new rule ruleClone.append(declClone); - if(shouldPreserve === true) { + let preserveVariable; + if(typeof shouldPreserve === "function") { + preserveVariable = shouldPreserve(decl); + } else { + preserveVariable = shouldPreserve; + } + if(preserveVariable === true) { declClone.cloneAfter(); } diff --git a/test/fixtures/preserve-variables-conditionally.css b/test/fixtures/preserve-variables-conditionally.css new file mode 100644 index 0000000..2e2f203 --- /dev/null +++ b/test/fixtures/preserve-variables-conditionally.css @@ -0,0 +1,21 @@ +:root { + --color-one: #0000ff; + --color-two: #00ff00; + --color-three: var(--color-two); +} + +.before { + prop: before; + color: var(--color-one); +} + +.after { + color: var(--color-two); + prop: after; +} + +.before-and-after { + prop: before; + color: var(--missing, #ff0000); + otherprop: after; +} \ No newline at end of file diff --git a/test/fixtures/preserve-variables-conditionally.expected.css b/test/fixtures/preserve-variables-conditionally.expected.css new file mode 100644 index 0000000..c9108ff --- /dev/null +++ b/test/fixtures/preserve-variables-conditionally.expected.css @@ -0,0 +1,22 @@ +:root { + --color-two: #00ff00; + --color-three: var(--color-two); +} + +.before { + prop: before; + color: #0000ff; +} + +.after { + color: #00ff00; + color: var(--color-two); + prop: after; +} + +.before-and-after { + prop: before; + color: #ff0000; + color: var(--missing, #ff0000); + otherprop: after; +} \ No newline at end of file diff --git a/test/test.js b/test/test.js index 1f91bfd..caafea2 100644 --- a/test/test.js +++ b/test/test.js @@ -228,6 +228,19 @@ describe('postcss-css-variables', function() { 'preserve-computed', { preserve: 'computed' } ); + + test( + 'preserves variables when `preserve` function applies', + 'preserve-variables-conditionally', + { + preserve: function (declaration) { + return !( + declaration.value.includes("--color-one") + || declaration.prop.includes("--color-one") + ) + } + } + ); }); From 9459f2a436fabe4f7ce6ee03b95396722bdb0139 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 24 Apr 2020 17:59:57 -0500 Subject: [PATCH 08/21] Make preserve callback documentation and tests more clear --- README.md | 259 ++++--- .../preserve-variables-conditionally.css | 22 +- ...serve-variables-conditionally.expected.css | 24 +- test/test.js | 645 ++++++++++-------- 4 files changed, 496 insertions(+), 454 deletions(-) diff --git a/README.md b/README.md index ad1dc2c..ea6978a 100644 --- a/README.md +++ b/README.md @@ -12,70 +12,56 @@ npm install postcss-css-variables --save-dev ### Table of Contents - - [Code Playground](#code-playground) - - [Usage](#usage) - - [Syntax](#syntax) - - [Defining Custom Properties with `--*`](#defining-custom-properties-with---) - - [Using Variables/Custom Properties with `var()`](#using-variables-custom-properties-with-var) - - [Features](#features) - - [At-rules like `@media`, `@support`, etc.](#at-rules-like-media-support-etc) - - [Pseudo-classes and Elements](#pseudo-classes-and-elements) - - [Nested Rules](#nested-rules) - - [Why?](#why) - - [Interoperability](#interoperability) - - [Differences from `postcss-custom-properties`](#differences-from-postcss-custom-properties) - - [Caveats](#caveats) - - [Options](#options) - - [Quick Reference/Notes](#quick-referencenotes) - - [Testing](#testing) - - [Changelog](https://github.com/MadLittleMods/postcss-css-variables/blob/master/CHANGELOG.md) - +- [Code Playground](#code-playground) +- [Usage](#usage) +- [Syntax](#syntax) - [Defining Custom Properties with `--*`](#defining-custom-properties-with---) - [Using Variables/Custom Properties with `var()`](#using-variables-custom-properties-with-var) +- [Features](#features) - [At-rules like `@media`, `@support`, etc.](#at-rules-like-media-support-etc) - [Pseudo-classes and Elements](#pseudo-classes-and-elements) - [Nested Rules](#nested-rules) +- [Why?](#why) - [Interoperability](#interoperability) - [Differences from `postcss-custom-properties`](#differences-from-postcss-custom-properties) +- [Caveats](#caveats) +- [Options](#options) +- [Quick Reference/Notes](#quick-referencenotes) +- [Testing](#testing) +- [Changelog](https://github.com/MadLittleMods/postcss-css-variables/blob/master/CHANGELOG.md) # [Code Playground](https://madlittlemods.github.io/postcss-css-variables/playground/) [Try it in the playground](https://madlittlemods.github.io/postcss-css-variables/playground/) and see what you think! Just add some CSS and see to see the final transformed/compiled CSS. You can try anything here in the playground, too. - # Usage -[*For more general PostCSS usage, look here.*](https://github.com/postcss/postcss#usage) +[_For more general PostCSS usage, look here._](https://github.com/postcss/postcss#usage) ```js -var postcss = require('postcss'); -var cssvariables = require('postcss-css-variables'); +var postcss = require("postcss"); +var cssvariables = require("postcss-css-variables"); -var fs = require('fs'); +var fs = require("fs"); -var mycss = fs.readFileSync('input.css', 'utf8'); +var mycss = fs.readFileSync("input.css", "utf8"); // Process your CSS with postcss-css-variables -var output = postcss([ - cssvariables(/*options*/) - ]) - .process(mycss) - .css; +var output = postcss([cssvariables(/*options*/)]).process(mycss).css; console.log(output); ``` - # Syntax ### Defining Custom Properties with `--*` A custom property is any property whose name starts with two dashes `--`. A property must be in a rule. -*Note: `:root` is nothing more than the selector for the root DOM node. Any other selector like `.class`, `#id`, or even `#foo ~ .bar > span.baz` works.* +_Note: `:root` is nothing more than the selector for the root DOM node. Any other selector like `.class`, `#id`, or even `#foo ~ .bar > span.baz` works._ ```css :root { - --foo-width: 100px; - --foo-bg-color: rgba(255, 0, 0, 0.85); + --foo-width: 100px; + --foo-bg-color: rgba(255, 0, 0, 0.85); } .foo { - --foo-width: 100px; - --foo-bg-color: rgba(255, 0, 0, 0.85); + --foo-width: 100px; + --foo-bg-color: rgba(255, 0, 0, 0.85); } ``` @@ -83,41 +69,39 @@ Custom properties can be declared multiple times, but like variable scope in oth ```css :root { - --some-color: red; + --some-color: red; } .foo { - /* red */ - color: var(--some-color); + /* red */ + color: var(--some-color); } - .bar { - --some-color: blue; - /* blue */ - color: var(--some-color); + --some-color: blue; + /* blue */ + color: var(--some-color); } .bar:hover { - --some-color: green; - /* Automatically gets a `color: green;` declaration because we `--some-color` used within scope elsewhere */ + --some-color: green; + /* Automatically gets a `color: green;` declaration because we `--some-color` used within scope elsewhere */ } ``` -*[W3C Draft: CSS Custom Properties for Cascading Variables, section 2](http://dev.w3.org/csswg/css-variables/#defining-variables)* +_[W3C Draft: CSS Custom Properties for Cascading Variables, section 2](http://dev.w3.org/csswg/css-variables/#defining-variables)_ ### Using Variables/Custom Properties with `var()` ```css .foo { - width: var(--foo-width); - /* You can even provide a fallback */ - background: var(--foo-bg-color, #ff0000); + width: var(--foo-width); + /* You can even provide a fallback */ + background: var(--foo-bg-color, #ff0000); } ``` -*[W3C Draft: CSS Custom Properties for Cascading Variables, section 3](http://dev.w3.org/csswg/css-variables/#using-variables)* - +_[W3C Draft: CSS Custom Properties for Cascading Variables, section 3](http://dev.w3.org/csswg/css-variables/#using-variables)_ # Features @@ -127,17 +111,17 @@ It's perfectly okay to declare CSS variables inside media queries and the like. ```css :root { - --width: 100px; + --width: 100px; } @media (max-width: 1000px) { - :root { - --width: 200px; - } + :root { + --width: 200px; + } } .box { - width: var(--width); + width: var(--width); } ``` @@ -145,13 +129,13 @@ Will be transformed to: ```css .box { - width: 100px; + width: 100px; } @media (max-width: 1000px) { - .box { - width: 200px; - } + .box { + width: 200px; + } } ``` @@ -161,12 +145,12 @@ Psuedo-classes are also dealt with correctly, because it's easy to statically de ```css .foo { - --foo-color: red; - color: var(--foo-color); + --foo-color: red; + color: var(--foo-color); } .foo:hover { - --foo-color: green; + --foo-color: green; } ``` @@ -174,11 +158,11 @@ Will be transformed to: ```css .foo { - color: red; + color: red; } .foo:hover { - color: green; + color: green; } ``` @@ -186,24 +170,21 @@ Will be transformed to: This pairs very well with [`postcss-nested`](https://github.com/postcss/postcss-nested) or [`postcss-nesting`](https://github.com/jonathantneal/postcss-nesting), adding support for nested rules. For either, you must put the plugin before `postcss-css-variables` in the plugin stack so that the `&` references are expanded first (`postcss-css-variables` doesn't understand them). For example, with `postcss-nested`, your PostCSS setup would look like this: - ```js -var postcss = require('postcss'); -var cssvariables = require('postcss-css-variables'); -var nested = require('postcss-nested'); +var postcss = require("postcss"); +var cssvariables = require("postcss-css-variables"); +var nested = require("postcss-nested"); -var fs = require('fs'); +var fs = require("fs"); -var mycss = fs.readFileSync('input.css', 'utf8'); +var mycss = fs.readFileSync("input.css", "utf8"); var output = postcss([ - // Flatten/unnest rules - nested, - // Then process any CSS variables - cssvariables(/*options*/) - ]) - .process(mycss) - .css; + // Flatten/unnest rules + nested, + // Then process any CSS variables + cssvariables(/*options*/) +]).process(mycss).css; console.log(output); ``` @@ -212,12 +193,12 @@ For a simple example with nesting: ```css .box-foo { - --some-width: 150px; - width: var(--some-width); + --some-width: 150px; + width: var(--some-width); - .box-bar { - width: var(--some-width); - } + .box-bar { + width: var(--some-width); + } } ``` @@ -225,11 +206,11 @@ With also `postcss-nesting`, this will be transformed to: ```css .box-foo { - width: 150px; + width: 150px; } .box-foo .box-bar { - width: 150px; + width: 150px; } ``` @@ -237,21 +218,21 @@ For a more complex example with a media query: ```css :root { - --some-width: 150px; + --some-width: 150px; } .box-foo { - width: var(--some-width); + width: var(--some-width); - .box-bar { - width: var(--some-width); - } + .box-bar { + width: var(--some-width); + } } @media (max-width: 800px) { - .box-foo { - --some-width: 300px; - } + .box-foo { + --some-width: 300px; + } } ``` @@ -259,26 +240,24 @@ Will be transformed to: ```css .box-foo { - width: 150px; + width: 150px; } .box-foo .box-bar { - width: 150px; + width: 150px; } @media (max-width: 800px) { - .box-foo { - width: 300px; - } + .box-foo { + width: 300px; + } - .box-foo .box-bar { - width: 300px; - } + .box-foo .box-bar { + width: 300px; + } } ``` - - # Why? This plugin was spawned out of a [discussion on the `cssnext` repo](https://github.com/cssnext/cssnext/issues/99) and a personal need. @@ -295,14 +274,13 @@ In `postcss-css-variables`, this is not the case and they may be declared inside Here's a quick overview of the differences: - - CSS variables may be declared in any selector like `.foo` or `.foo .bar:hover`, and is not limited to just `:root` - - CSS variables may be declared in `@media`, `@support`, and other at-rules. - - CSS variables may be declared in `:hover` and other psuedo-classes, which get expanded properly. - - Variables in nested rules can be deduced with the help of [`postcss-nested`](https://github.com/postcss/postcss-nested) or [`postcss-nesting`](https://github.com/jonathantneal/postcss-nesting). +- CSS variables may be declared in any selector like `.foo` or `.foo .bar:hover`, and is not limited to just `:root` +- CSS variables may be declared in `@media`, `@support`, and other at-rules. +- CSS variables may be declared in `:hover` and other psuedo-classes, which get expanded properly. +- Variables in nested rules can be deduced with the help of [`postcss-nested`](https://github.com/postcss/postcss-nested) or [`postcss-nesting`](https://github.com/jonathantneal/postcss-nesting). Continue to the next section to see where some of these might be unsafe to do. There are reasons behind the ethos of why the other plugin, [`postcss-custom-properties`](https://github.com/postcss/postcss-custom-properties), is very limited in what it supports, due to differing opinions on what is okay to support. - # Caveats When you declare a CSS variable inside one selector, but consume it in another, this does make an unsafe assumption about it which can be non-conforming in certain edge cases. Here is an example where these limitations result in non-conforming behavior. @@ -311,17 +289,17 @@ Note the nested markup below. We only know about the DOM's inheritance from your ```html
- Black + Black -
- Blue +
+ Blue -
- Green +
+ Green -
Blue with this plugin, but green per spec
-
+
Blue with this plugin, but green per spec
+
``` @@ -340,10 +318,8 @@ Note the nested markup below. We only know about the DOM's inheritance from your } ``` - [`postcss-custom-properties`](https://github.com/postcss/postcss-custom-properties) avoids this problem entirely by restricting itself to just the `:root` selector. This is because the developers there would prefer to not support a feature instead of something almost-spec-compliant like what `postcss-css-variables` does. - # Options ### `preserve` (default: `false`) @@ -352,10 +328,10 @@ Allows you to preserve custom properties & var() usage in output. Possible values: - - `false`: Removes `--var` declarations and replaces `var()` with their resolved/computed values. - - `true`: Keeps `var()` declarations in the output and has the computed value as a fallback declaration. Also keeps computed `--var` declarations. - - `'computed'`: Keeps computed `--var` declarations in the output. Handy to make them available to your JavaScript. - - `(declaration) => boolean|'computed'` : Handles preservation behavior based on the respective declaration. +- `false`: Removes `--var` declarations and replaces `var()` with their resolved/computed values. +- `true`: Keeps `var()` declarations in the output and has the computed value as a fallback declaration. Also keeps computed `--var` declarations. +- `'computed'`: Keeps computed `--var` declarations in the output. Handy to make them available to your JavaScript. +- `(declaration) => boolean|'computed'` : function/callback to programmatically return whether preserve the respective declaration ### `variables` (default: `{}`) @@ -374,43 +350,36 @@ repeating custom property definitions in every module passed through this plugin prevents JS-injected variables from appearing in output CSS. ```js -var postcss = require('postcss'); -var cssvariables = require('postcss-css-variables'); +var postcss = require("postcss"); +var cssvariables = require("postcss-css-variables"); postcss([ - cssvariables({ - variables: { - '--some-var': '100px', - '--other-var': { - value: '#00ff00' - }, - '--important-var': { - value: '#ff0000', - isImportant: true - } - } - }) -]) -.process(css, opts); + cssvariables({ + variables: { + "--some-var": "100px", + "--other-var": { + value: "#00ff00" + }, + "--important-var": { + value: "#ff0000", + isImportant: true + } + } + }) +]).process(css, opts); ``` - ### `preserveAtRulesOrder` (default: `false`) Keeps your at-rules like media queries in the order to defined them. Ideally, this would be defaulted to `true` and it will be in the next major version. All of the tests expecations need to be updated and probably just drop support for `preserveAtRulesOrder: false` - # Quick Reference/Notes - - This plugin was spawned out of a [discussion on the `cssnext` repo](https://github.com/cssnext/cssnext/issues/99). - - We provide a larger CSS variable feature subset than [`postcss-custom-properties`](https://github.com/postcss/postcss-custom-properties). - - Related links and issues: - - [var declared in media query should pull in properties that use/reference that var *on `cssnext/cssnext`*](https://github.com/cssnext/cssnext/issues/99) - - [Investigate support for media-query scoped properties *on `postcss/postcss-custom-properties`*](https://github.com/postcss/postcss-custom-properties/issues/9) - - [remove `:root` limitation by injecting rules with new declarations that just contains modified properties. *on `postcss/postcss-custom-properties`*](https://github.com/postcss/postcss-custom-properties/issues/1) - +- This plugin was spawned out of a [discussion on the `cssnext` repo](https://github.com/cssnext/cssnext/issues/99). +- We provide a larger CSS variable feature subset than [`postcss-custom-properties`](https://github.com/postcss/postcss-custom-properties). +- Related links and issues: - [var declared in media query should pull in properties that use/reference that var _on `cssnext/cssnext`_](https://github.com/cssnext/cssnext/issues/99) - [Investigate support for media-query scoped properties _on `postcss/postcss-custom-properties`_](https://github.com/postcss/postcss-custom-properties/issues/9) - [remove `:root` limitation by injecting rules with new declarations that just contains modified properties. _on `postcss/postcss-custom-properties`_](https://github.com/postcss/postcss-custom-properties/issues/1) # Testing diff --git a/test/fixtures/preserve-variables-conditionally.css b/test/fixtures/preserve-variables-conditionally.css index 2e2f203..33cb840 100644 --- a/test/fixtures/preserve-variables-conditionally.css +++ b/test/fixtures/preserve-variables-conditionally.css @@ -1,21 +1,21 @@ :root { - --color-one: #0000ff; - --color-two: #00ff00; - --color-three: var(--color-two); + --no-preserve: #0000ff; + --color-foo: #00ff00; + --color-bar: var(--color-two); } .before { - prop: before; - color: var(--color-one); + prop: before; + color: var(--no-preserve); } .after { - color: var(--color-two); - prop: after; + color: var(--color-foo); + prop: after; } .before-and-after { - prop: before; - color: var(--missing, #ff0000); - otherprop: after; -} \ No newline at end of file + prop: before; + color: var(--missing, #ff0000); + otherprop: after; +} diff --git a/test/fixtures/preserve-variables-conditionally.expected.css b/test/fixtures/preserve-variables-conditionally.expected.css index c9108ff..78a9d09 100644 --- a/test/fixtures/preserve-variables-conditionally.expected.css +++ b/test/fixtures/preserve-variables-conditionally.expected.css @@ -1,22 +1,22 @@ :root { - --color-two: #00ff00; - --color-three: var(--color-two); + --color-foo: #00ff00; + --color-bar: var(--color-two); } .before { - prop: before; - color: #0000ff; + prop: before; + color: #0000ff; } .after { - color: #00ff00; - color: var(--color-two); - prop: after; + color: #00ff00; + color: var(--color-foo); + prop: after; } .before-and-after { - prop: before; - color: #ff0000; - color: var(--missing, #ff0000); - otherprop: after; -} \ No newline at end of file + prop: before; + color: #ff0000; + color: var(--missing, #ff0000); + otherprop: after; +} diff --git a/test/test.js b/test/test.js index caafea2..a3704fa 100644 --- a/test/test.js +++ b/test/test.js @@ -1,302 +1,375 @@ -var Promise = require('bluebird'); -var fs = Promise.promisifyAll(require('fs')); -var path = require('path'); +var Promise = require("bluebird"); +var fs = Promise.promisifyAll(require("fs")); +var path = require("path"); -var chai = require('chai'); +var chai = require("chai"); var expect = chai.expect; -var chaiAsPromised = require('chai-as-promised'); +var chaiAsPromised = require("chai-as-promised"); chai.use(chaiAsPromised); -var postcss = require('postcss'); -var cssvariables = require('../'); -var cssnano = require('cssnano'); -var normalizeWhitespace = require('postcss-normalize-whitespace'); -var discardComments = require('postcss-discard-comments'); - -var MOCK_JS_VARIABLES = { - '--js-defined1': '75px', - '--js-defined2': { - value: '80px' - }, - '--js-defined-important': { - value: '#0f0', - isImportant: true - }, - // Should be automatically prefixed with `--` - 'js-defined-no-prefix': '#ff0000' +var postcss = require("postcss"); +var cssvariables = require("../"); +var cssnano = require("cssnano"); +var normalizeWhitespace = require("postcss-normalize-whitespace"); +var discardComments = require("postcss-discard-comments"); + +var MOCK_JS_VARIABLES = { + "--js-defined1": "75px", + "--js-defined2": { + value: "80px" + }, + "--js-defined-important": { + value: "#0f0", + isImportant: true + }, + // Should be automatically prefixed with `--` + "js-defined-no-prefix": "#ff0000" }; var NON_STRING_VARIABLES = { - 'number-value': 50, - 'zero-value': 0, - 'null-value': null, - 'undefined-value': undefined, - 'object-value-passed-by-mistake': {}, - 'true-value': true, - 'false-value': false, + "number-value": 50, + "zero-value": 0, + "null-value": null, + "undefined-value": undefined, + "object-value-passed-by-mistake": {}, + "true-value": true, + "false-value": false }; var testPlugin = function(filePath, expectedFilePath, options) { - options = options || {}; - return Promise.props({ - actualBuffer: fs.readFileAsync(filePath), - expectedBuffer: fs.readFileAsync(expectedFilePath) - }) - .then(function({ actualBuffer, expectedBuffer }) { - var actualResult = postcss([ - cssvariables(options), - cssnano({ - preset: { plugins: [normalizeWhitespace, discardComments] } - }) - ]) - .process(String(actualBuffer)); - - var expectedResult = postcss([ - cssnano({ - preset: { plugins: [normalizeWhitespace, discardComments] } - }) - ]) - .process(String(expectedBuffer)); - - return Promise.props({ - actualResult: actualResult, - expectedResult: expectedResult - }); - }) - .then(({ actualResult, expectedResult }) => { - expect(actualResult.css.replace(/\r?\n/g, '')).to.equal(expectedResult.css.replace(/\r?\n/g, '')); - }); + options = options || {}; + return Promise.props({ + actualBuffer: fs.readFileAsync(filePath), + expectedBuffer: fs.readFileAsync(expectedFilePath) + }) + .then(function({ actualBuffer, expectedBuffer }) { + var actualResult = postcss([ + cssvariables(options), + cssnano({ + preset: { plugins: [normalizeWhitespace, discardComments] } + }) + ]).process(String(actualBuffer)); + + var expectedResult = postcss([ + cssnano({ + preset: { plugins: [normalizeWhitespace, discardComments] } + }) + ]).process(String(expectedBuffer)); + + return Promise.props({ + actualResult: actualResult, + expectedResult: expectedResult + }); + }) + .then(({ actualResult, expectedResult }) => { + expect(actualResult.css.replace(/\r?\n/g, "")).to.equal( + expectedResult.css.replace(/\r?\n/g, "") + ); + }); }; -var fixtureBasePath = './test/fixtures/'; +var fixtureBasePath = "./test/fixtures/"; var test = function(message, fixtureName, options) { - it(message, function() { - return testPlugin( - path.join(fixtureBasePath, fixtureName + '.css'), - path.join(fixtureBasePath, fixtureName + '.expected.css'), - options - ); - }); + it(message, function() { + return testPlugin( + path.join(fixtureBasePath, fixtureName + ".css"), + path.join(fixtureBasePath, fixtureName + ".expected.css"), + options + ); + }); }; - - -describe('postcss-css-variables', function() { - // Just make sure it doesn't mangle anything - test('should work when there are no var() functions to consume declarations', 'no-var-func'); - test('should work when there are no var() functions(just `:root`) to consume declarations', 'no-var-func-just-root'); - test('should work when no variable name passed to `var()`', 'empty-var-func'); - - - test('should work with variables declared in root', 'root-variable'); - test('should work with variables with parenthesis in fallback', 'fallback-with-parenthesis'); - - test('should work with locally scoped variable in a non-root rule', 'local-variable-non-root'); - - - test( - 'should work with any combinator selector if the last piece is the variable we have in the map', - 'scope-last-piece-of-combinator-sequence' - ); - - - test('should work with descendant selector type "nesting"', 'descendant-selector'); - test('should work with css4 descendant selector type "nesting"', 'css4-descendant-selector'); - test('should work with direct descendant selector', 'direct-descendant-selector'); - - test( - 'should work with direct descendant selector where variables are scoped in a descendant selector', - 'direct-descendant-selector-descendant-scope' - ); - test( - 'should work with direct descendant selector where variables are scoped in a direct descendant selector', - 'direct-descendant-selector-direct-descendant-scope' - ); - - - test('should work with pseudo selectors', 'pseudo-selector'); - //test('should work with multiple pseudo selectors', 'pseudo-multi'); - test('should work with variables declared in pseudo selectors', 'pseudo-selector-declare-variable'); - - - - test('should work with variables defined in comma separated selector', 'comma-separated-variable-declaration'); - - - test('should work use the correct variable in comma separated selector', 'comma-separated-variable-usage'); - - - test('should work with star selector', 'star-selector-scope'); - - test('should work with `!important` variable declarations', 'important-variable-declaration'); - - - - describe('with at-rules', function() { - test('should add rule declaration of property in @media', 'media-query'); - test('should add rule declaration of property in @support', 'support-directive'); - - test('should work with @media, preserving rule order', 'media-query-preserve-rule-order', { preserveAtRulesOrder: true }); - - test('should work with nested @media', 'media-query-nested', { preserveAtRulesOrder: false }); - test('should work with nested @media, preserving rule order', 'media-query-nested-preserver-rule-order', { preserveAtRulesOrder: true }); - - - test('should cascade to nested rules', 'cascade-on-nested-rules'); - - test('should cascade with calc-expression to nested rules', 'cascade-with-calc-expression-on-nested-rules'); - - test('should cascade to nested rules in the proper scope. See issue #2', 'cascade-on-nested-rules-in-proper-scope'); - }); - - - test('should work with variables that reference other variables', 'variable-reference-other-variable'); - - test( - 'should work with variable with calc-expression that reference other variables', - 'variable-with-calc-expression-reference-other-variable' - ); - - test( - 'should work with variables that reference other variables with at-rule changing the value', - 'variable-reference-other-variable-media-query1' - ); - test( - 'should work with local variables that reference other variables with at-rule changing the value', - 'variable-reference-other-variable-media-query2' - ); - - - - test('should work with variables that try to self reference', 'self-reference'); - test('should work with variables that try to self reference and fallback properly', 'self-reference-fallback'); - test('should work with circular reference', 'circular-reference'); - - - describe('with `options.variables`', function() { - test( - 'should work with JS defined variables', - 'js-defined', - { variables: MOCK_JS_VARIABLES } - ); - test( - 'should work with JS defined important variables', - 'js-defined-important', - { variables: MOCK_JS_VARIABLES } - ); - test( - 'should preserve -- declarations and var() values with `options.variables` AND `options.preserve`', - 'js-defined-preserve', - { - variables: MOCK_JS_VARIABLES, - preserve: true - } - ); - test( - 'should preserve var() values and clean injected declarations with `options.variables` AND `options.preserve` AND `options.preserveInjectedVariables: false`', - 'js-defined-preserve-injected', - { - variables: MOCK_JS_VARIABLES, - preserve: true, - preserveInjectedVariables: false, - } - ); - test( - 'should cast non-string values to string', - 'js-defined-non-string-values-casted-to-string', - { - variables: NON_STRING_VARIABLES - } - ); - }); - - describe('with `options.preserve`', function() { - test( - 'preserves variables when `preserve` is `true`', - 'preserve-variables', - { preserve: true } - ); - - test( - 'preserves variables in @media when `preserve` is `true`', - 'preserve-variables-in-media', - { preserve: true } - ); - - test( - 'preserves computed value when `preserve` is `\'computed\'`', - 'preserve-computed', - { preserve: 'computed' } - ); - - test( - 'preserves variables when `preserve` function applies', - 'preserve-variables-conditionally', - { - preserve: function (declaration) { - return !( - declaration.value.includes("--color-one") - || declaration.prop.includes("--color-one") - ) - } - } - ); - }); - - - describe('missing variable declarations', function() { - test('should work with missing variables', 'missing-variable-usage'); - test('should use fallback value if provided with missing variables', 'missing-variable-should-fallback'); - it('should use string values for `undefined` values, see #22', function() { - return fs.readFileAsync('./test/fixtures/missing-variable-usage.css', 'utf8') - .then(function(buffer) { - var contents = String(buffer); - return postcss([ - cssvariables() - ]) - .process(contents) - .then(function(result) { - var root = result.root; - var fooRule = root.nodes[0]; - expect(fooRule.selector).to.equal('.box-foo'); - var colorDecl = fooRule.nodes[0]; - expect(colorDecl.value).to.be.a('string'); - expect(colorDecl.value).to.be.equal('undefined'); - - expect(result.warnings().length).to.be.equal(1); - expect(result.warnings()[0].type).to.be.equal('warning'); - expect(result.warnings()[0].text).to.be.equal('variable --missing is undefined and used without a fallback'); - }); - }); - }); - test('should use fallback variable if provided with missing variables', 'missing-variable-should-fallback-var'); - test('should use fallback variable if provided with missing variables calc', 'missing-variable-should-fallback-calc'); - test('should use fallback variable if provided with missing variables nested', 'missing-variable-should-fallback-nested'); - test('should not mangle outer function parentheses', 'nested-inside-other-func'); - test('should not mangle outer function parentheses - with fallback', 'nested-inside-other-func-with-fallback'); - test('should not mangle outer function parentheses - calc', 'nested-inside-calc-func'); - test('should not mangle outer function parentheses - calc with fallback', 'nested-inside-calc-func-with-fallback'); - test('should not mangle outer function parentheses - calc with fallback var()', 'nested-inside-calc-func-with-fallback-var'); - }); - - test('should accept whitespace in var() declarations', 'whitespace-in-var-declaration' ) - - it('should not parse malformed var() declarations', function() { - return expect(testPlugin( - './test/fixtures/malformed-variable-usage.css', - './test/fixtures/malformed-variable-usage.expected.css' - ) - ).to.eventually.be.rejected; - }); - - describe('rule clean up', function() { - test( - 'should clean up rules if we removed variable declarations to make it empty', - 'remove-empty-rules-after-variable-collection' - ); - test( - 'should clean up neseted rules if we removed variable declarations to make it empty', - 'remove-nested-empty-rules-after-variable-collection' - ); - }); +describe("postcss-css-variables", function() { + // Just make sure it doesn't mangle anything + test( + "should work when there are no var() functions to consume declarations", + "no-var-func" + ); + test( + "should work when there are no var() functions(just `:root`) to consume declarations", + "no-var-func-just-root" + ); + test("should work when no variable name passed to `var()`", "empty-var-func"); + + test("should work with variables declared in root", "root-variable"); + test( + "should work with variables with parenthesis in fallback", + "fallback-with-parenthesis" + ); + + test( + "should work with locally scoped variable in a non-root rule", + "local-variable-non-root" + ); + + test( + "should work with any combinator selector if the last piece is the variable we have in the map", + "scope-last-piece-of-combinator-sequence" + ); + + test( + 'should work with descendant selector type "nesting"', + "descendant-selector" + ); + test( + 'should work with css4 descendant selector type "nesting"', + "css4-descendant-selector" + ); + test( + "should work with direct descendant selector", + "direct-descendant-selector" + ); + + test( + "should work with direct descendant selector where variables are scoped in a descendant selector", + "direct-descendant-selector-descendant-scope" + ); + test( + "should work with direct descendant selector where variables are scoped in a direct descendant selector", + "direct-descendant-selector-direct-descendant-scope" + ); + + test("should work with pseudo selectors", "pseudo-selector"); + //test('should work with multiple pseudo selectors', 'pseudo-multi'); + test( + "should work with variables declared in pseudo selectors", + "pseudo-selector-declare-variable" + ); + + test( + "should work with variables defined in comma separated selector", + "comma-separated-variable-declaration" + ); + + test( + "should work use the correct variable in comma separated selector", + "comma-separated-variable-usage" + ); + + test("should work with star selector", "star-selector-scope"); + + test( + "should work with `!important` variable declarations", + "important-variable-declaration" + ); + + describe("with at-rules", function() { + test("should add rule declaration of property in @media", "media-query"); + test( + "should add rule declaration of property in @support", + "support-directive" + ); + + test( + "should work with @media, preserving rule order", + "media-query-preserve-rule-order", + { preserveAtRulesOrder: true } + ); + + test("should work with nested @media", "media-query-nested", { + preserveAtRulesOrder: false + }); + test( + "should work with nested @media, preserving rule order", + "media-query-nested-preserver-rule-order", + { preserveAtRulesOrder: true } + ); + + test("should cascade to nested rules", "cascade-on-nested-rules"); + + test( + "should cascade with calc-expression to nested rules", + "cascade-with-calc-expression-on-nested-rules" + ); + + test( + "should cascade to nested rules in the proper scope. See issue #2", + "cascade-on-nested-rules-in-proper-scope" + ); + }); + + test( + "should work with variables that reference other variables", + "variable-reference-other-variable" + ); + + test( + "should work with variable with calc-expression that reference other variables", + "variable-with-calc-expression-reference-other-variable" + ); + + test( + "should work with variables that reference other variables with at-rule changing the value", + "variable-reference-other-variable-media-query1" + ); + test( + "should work with local variables that reference other variables with at-rule changing the value", + "variable-reference-other-variable-media-query2" + ); + + test( + "should work with variables that try to self reference", + "self-reference" + ); + test( + "should work with variables that try to self reference and fallback properly", + "self-reference-fallback" + ); + test("should work with circular reference", "circular-reference"); + + describe("with `options.variables`", function() { + test("should work with JS defined variables", "js-defined", { + variables: MOCK_JS_VARIABLES + }); + test( + "should work with JS defined important variables", + "js-defined-important", + { variables: MOCK_JS_VARIABLES } + ); + test( + "should preserve -- declarations and var() values with `options.variables` AND `options.preserve`", + "js-defined-preserve", + { + variables: MOCK_JS_VARIABLES, + preserve: true + } + ); + test( + "should preserve var() values and clean injected declarations with `options.variables` AND `options.preserve` AND `options.preserveInjectedVariables: false`", + "js-defined-preserve-injected", + { + variables: MOCK_JS_VARIABLES, + preserve: true, + preserveInjectedVariables: false + } + ); + test( + "should cast non-string values to string", + "js-defined-non-string-values-casted-to-string", + { + variables: NON_STRING_VARIABLES + } + ); + }); + + describe("with `options.preserve`", function() { + test( + "preserves variables when `preserve` is `true`", + "preserve-variables", + { preserve: true } + ); + + test( + "preserves variables in @media when `preserve` is `true`", + "preserve-variables-in-media", + { preserve: true } + ); + + test( + "preserves computed value when `preserve` is `'computed'`", + "preserve-computed", + { preserve: "computed" } + ); + + test( + "preserves variables when `preserve` function applies", + "preserve-variables-conditionally", + { + preserve: function(declaration) { + return !( + declaration.prop.includes("--no-preserve") || + declaration.value.includes("--no-preserve") + ); + } + } + ); + }); + + describe("missing variable declarations", function() { + test("should work with missing variables", "missing-variable-usage"); + test( + "should use fallback value if provided with missing variables", + "missing-variable-should-fallback" + ); + it("should use string values for `undefined` values, see #22", function() { + return fs + .readFileAsync("./test/fixtures/missing-variable-usage.css", "utf8") + .then(function(buffer) { + var contents = String(buffer); + return postcss([cssvariables()]) + .process(contents) + .then(function(result) { + var root = result.root; + var fooRule = root.nodes[0]; + expect(fooRule.selector).to.equal(".box-foo"); + var colorDecl = fooRule.nodes[0]; + expect(colorDecl.value).to.be.a("string"); + expect(colorDecl.value).to.be.equal("undefined"); + + expect(result.warnings().length).to.be.equal(1); + expect(result.warnings()[0].type).to.be.equal("warning"); + expect(result.warnings()[0].text).to.be.equal( + "variable --missing is undefined and used without a fallback" + ); + }); + }); + }); + test( + "should use fallback variable if provided with missing variables", + "missing-variable-should-fallback-var" + ); + test( + "should use fallback variable if provided with missing variables calc", + "missing-variable-should-fallback-calc" + ); + test( + "should use fallback variable if provided with missing variables nested", + "missing-variable-should-fallback-nested" + ); + test( + "should not mangle outer function parentheses", + "nested-inside-other-func" + ); + test( + "should not mangle outer function parentheses - with fallback", + "nested-inside-other-func-with-fallback" + ); + test( + "should not mangle outer function parentheses - calc", + "nested-inside-calc-func" + ); + test( + "should not mangle outer function parentheses - calc with fallback", + "nested-inside-calc-func-with-fallback" + ); + test( + "should not mangle outer function parentheses - calc with fallback var()", + "nested-inside-calc-func-with-fallback-var" + ); + }); + + test( + "should accept whitespace in var() declarations", + "whitespace-in-var-declaration" + ); + + it("should not parse malformed var() declarations", function() { + return expect( + testPlugin( + "./test/fixtures/malformed-variable-usage.css", + "./test/fixtures/malformed-variable-usage.expected.css" + ) + ).to.eventually.be.rejected; + }); + + describe("rule clean up", function() { + test( + "should clean up rules if we removed variable declarations to make it empty", + "remove-empty-rules-after-variable-collection" + ); + test( + "should clean up neseted rules if we removed variable declarations to make it empty", + "remove-nested-empty-rules-after-variable-collection" + ); + }); }); From abd793aff24abd4b2ba54e229f4e8ccfb4214556 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 24 Apr 2020 18:01:00 -0500 Subject: [PATCH 09/21] Prepare changelog with #116 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e05606d..0fe1dbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# v0.16.0 - 2020-4-24 + +- Add ability to pass callback function to `options.preserve` to determine whether to preserve declaration + - Thank you to [@ekatioz](https://github.com/ekatioz) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/116) + # v0.15.0 - 2020-4-24 - Fix algorithm to find balanced `var()` pairs and nested parenthesis From 47677f27f149a58661c75ab30033fedfcbb19373 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 24 Apr 2020 18:01:10 -0500 Subject: [PATCH 10/21] 0.16.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac46647..4d33e75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-css-variables", - "version": "0.15.0", + "version": "0.16.0", "description": "PostCSS plugin to transform CSS Custom Properties(CSS variables) syntax into a static representation", "keywords": [ "postcss", From 5af07902c822f7654d4fd163ab707b2c759c6ebe Mon Sep 17 00:00:00 2001 From: Pieter van de Bruggen Date: Fri, 30 Aug 2019 23:15:50 -0700 Subject: [PATCH 11/21] Allow AtRules containing properties to have their variables expanded --- index.js | 9 ++++++--- test/fixtures/at-rules-containing-properties.css | 8 ++++++++ .../at-rules-containing-properties.expected.css | 4 ++++ .../nested-at-rules-containing-properties.css | 13 +++++++++++++ ...sted-at-rules-containing-properties.expected.css | 9 +++++++++ test/test.js | 9 +++++++++ 6 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/at-rules-containing-properties.css create mode 100644 test/fixtures/at-rules-containing-properties.expected.css create mode 100644 test/fixtures/nested-at-rules-containing-properties.css create mode 100644 test/fixtures/nested-at-rules-containing-properties.expected.css diff --git a/index.js b/index.js index 70c8282..d226362 100644 --- a/index.js +++ b/index.js @@ -218,7 +218,10 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) { // Collect all the rules that have declarations that use variables var rulesThatHaveDeclarationsWithVariablesList = []; - css.walkRules(function(rule) { + css.walk(function(rule) { + // We're only interested in Containers with children. + if (rule.nodes === undefined) return; + var doesRuleUseVariables = rule.nodes.some(function(node) { if(node.type === 'decl') { var decl = node; @@ -240,8 +243,8 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) { rulesThatHaveDeclarationsWithVariablesList.forEach(function(rule) { var rulesToWorkOn = [].concat(rule); // Split out the rule into each comma separated selector piece - // We only need to split if is actually comma separted(selectors > 1) - if(rule.selectors.length > 1) { + // We only need to split if it's actually a Rule with multiple selectors + if(rule.type == 'rule' && rule.selectors.length > 1) { // Reverse the selectors so that we can cloneAfter in the same comma separated order rulesToWorkOn = rule.selectors.reverse().map(function(selector) { var ruleClone = rule.cloneAfter(); diff --git a/test/fixtures/at-rules-containing-properties.css b/test/fixtures/at-rules-containing-properties.css new file mode 100644 index 0000000..93e895b --- /dev/null +++ b/test/fixtures/at-rules-containing-properties.css @@ -0,0 +1,8 @@ +:root { + --font-name: 'my-font-family-name'; +} + +@font-face { + font-family: var(--font-name); + src: url('myfont.woff2') format('woff2'); +} diff --git a/test/fixtures/at-rules-containing-properties.expected.css b/test/fixtures/at-rules-containing-properties.expected.css new file mode 100644 index 0000000..cc99efc --- /dev/null +++ b/test/fixtures/at-rules-containing-properties.expected.css @@ -0,0 +1,4 @@ +@font-face { + font-family: 'my-font-family-name'; + src: url('myfont.woff2') format('woff2'); +} diff --git a/test/fixtures/nested-at-rules-containing-properties.css b/test/fixtures/nested-at-rules-containing-properties.css new file mode 100644 index 0000000..54ee93f --- /dev/null +++ b/test/fixtures/nested-at-rules-containing-properties.css @@ -0,0 +1,13 @@ +:root { + --color: red; +} + +/* + Prince XML at-rules. + https://www.princexml.com/doc/11/at-rules/ +*/ +@page { + @footnote { + background-color: var(--color); + } +} diff --git a/test/fixtures/nested-at-rules-containing-properties.expected.css b/test/fixtures/nested-at-rules-containing-properties.expected.css new file mode 100644 index 0000000..2371346 --- /dev/null +++ b/test/fixtures/nested-at-rules-containing-properties.expected.css @@ -0,0 +1,9 @@ +/* + Prince XML at-rules. + https://www.princexml.com/doc/11/at-rules/ +*/ +@page { + @footnote { + background-color: red; + } +} diff --git a/test/test.js b/test/test.js index a3704fa..45a2f33 100644 --- a/test/test.js +++ b/test/test.js @@ -175,6 +175,15 @@ describe("postcss-css-variables", function() { { preserveAtRulesOrder: true } ); + test( + "should work with at-rules containing properties", + "at-rules-containing-properties" + ); + test( + "should work with nested at-rules containing properties", + "nested-at-rules-containing-properties" + ); + test("should cascade to nested rules", "cascade-on-nested-rules"); test( From 933738bbad383a4db1d35362a9ef3768ae40f12d Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 24 Apr 2020 18:15:41 -0500 Subject: [PATCH 12/21] Resolve review from #104 See https://github.com/MadLittleMods/postcss-css-variables/pull/104 --- index.js | 516 +++++++++++++++++++++++++++---------------------------- 1 file changed, 256 insertions(+), 260 deletions(-) diff --git a/index.js b/index.js index d226362..23534e4 100644 --- a/index.js +++ b/index.js @@ -6,291 +6,287 @@ // For Debugging //var nomo = require('node-monkey').start({port: 50501}); -var postcss = require('postcss'); -var extend = require('extend'); - -var shallowCloneNode = require('./lib/shallow-clone-node'); -var resolveValue = require('./lib/resolve-value'); -var resolveDecl = require('./lib/resolve-decl'); +var postcss = require("postcss"); +var extend = require("extend"); +var shallowCloneNode = require("./lib/shallow-clone-node"); +var resolveValue = require("./lib/resolve-value"); +var resolveDecl = require("./lib/resolve-decl"); // A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS) // `--foo` // See: http://dev.w3.org/csswg/css-variables/#custom-property -var RE_VAR_PROP = (/(--(.+))/); - - - +var RE_VAR_PROP = /(--(.+))/; function eachCssVariableDeclaration(css, cb) { - // Loop through all of the declarations and grab the variables and put them in the map - css.walkDecls(function(decl) { - // If declaration is a variable - if(RE_VAR_PROP.test(decl.prop)) { - cb(decl); - } - }); + // Loop through all of the declarations and grab the variables and put them in the map + css.walkDecls(function(decl) { + // If declaration is a variable + if (RE_VAR_PROP.test(decl.prop)) { + cb(decl); + } + }); } - - function cleanUpNode(node) { - // If we removed all of the declarations in the rule(making it empty), - // then just remove it - var nodeToPossiblyCleanUp = node; - while(nodeToPossiblyCleanUp && nodeToPossiblyCleanUp.nodes.length <= 0) { - var nodeToRemove = nodeToPossiblyCleanUp.type !== 'root' ? nodeToPossiblyCleanUp : null; - - if(nodeToRemove) { - // Get a reference to it before we remove - // and lose reference to the child after removing it - nodeToPossiblyCleanUp = nodeToRemove.parent; - - nodeToRemove.remove(); - } - else { - nodeToPossiblyCleanUp = null; - } - } + // If we removed all of the declarations in the rule(making it empty), + // then just remove it + var nodeToPossiblyCleanUp = node; + while (nodeToPossiblyCleanUp && nodeToPossiblyCleanUp.nodes.length <= 0) { + var nodeToRemove = + nodeToPossiblyCleanUp.type !== "root" ? nodeToPossiblyCleanUp : null; + + if (nodeToRemove) { + // Get a reference to it before we remove + // and lose reference to the child after removing it + nodeToPossiblyCleanUp = nodeToRemove.parent; + + nodeToRemove.remove(); + } else { + nodeToPossiblyCleanUp = null; + } + } } - var defaults = { - // Allows you to preserve custom properties & var() usage in output. - // `true`, `false`, or `'computed'` - preserve: false, - // Define variables via JS - // Simple key-value pair - // or an object with a `value` property and an optional `isImportant` bool property - variables: {}, - // Preserve variables injected via JS with the `variables` option above - // before serializing to CSS (`false` will remove these variables from output) - preserveInjectedVariables: true, - // Will write media queries in the same order as in the original file. - // Currently defaulted to false for legacy behavior. We can update to `true` in a major version - preserveAtRulesOrder: false + // Allows you to preserve custom properties & var() usage in output. + // `true`, `false`, or `'computed'` + preserve: false, + // Define variables via JS + // Simple key-value pair + // or an object with a `value` property and an optional `isImportant` bool property + variables: {}, + // Preserve variables injected via JS with the `variables` option above + // before serializing to CSS (`false` will remove these variables from output) + preserveInjectedVariables: true, + // Will write media queries in the same order as in the original file. + // Currently defaulted to false for legacy behavior. We can update to `true` in a major version + preserveAtRulesOrder: false }; -module.exports = postcss.plugin('postcss-css-variables', function(options) { - - var opts = extend({}, defaults, options); +module.exports = postcss.plugin("postcss-css-variables", function(options) { + var opts = extend({}, defaults, options); - // Work with opts here + // Work with opts here - return function (css, result) { - // Transform CSS AST here + return function(css, result) { + // Transform CSS AST here - /* * / + /* * / try { /* */ - // List of nodes that if empty, will be removed - // We use this because we don't want to modify the AST when we still need to reference these later on - var nodesToRemoveAtEnd = []; - - // Keep track of the injected from `opts.variables` to remove at the end - // if user passes `opts.preserveInjectedVariables = false` - var injectedDeclsToRemoveAtEnd = []; - - // Map of variable names to a list of declarations - var map = {}; - - // Add the js defined variables `opts.variables` to the map - map = extend( - map, - Object.keys(opts.variables).reduce(function(prevVariableMap, variableName) { - var variableEntry = opts.variables[variableName]; - // Automatically prefix any variable with `--` (CSS custom property syntax) if it doesn't have it already - variableName = variableName.slice(0, 2) === '--' ? variableName : '--' + variableName; - var variableValue = (variableEntry || {}).value || variableEntry; - var isImportant = (variableEntry || {}).isImportant || false; - - - // Add a root node to the AST - var variableRootRule = postcss.rule({ selector: ':root' }); - css.root().prepend(variableRootRule); - // Add the variable decl to the root node - var varDecl = postcss.decl({ - prop: variableName, - value: variableValue, - important: isImportant - }); - variableRootRule.append(varDecl); - - // Collect JS-injected variables for removal if `opts.preserveInjectedVariables = false` - if (!opts.preserveInjectedVariables) { - injectedDeclsToRemoveAtEnd.push(varDecl); - } - - // Add the entry to the map - prevVariableMap[variableName] = (prevVariableMap[variableName] || []).concat({ - decl: varDecl, - prop: variableName, - calculatedInPlaceValue: variableValue, - isImportant: isImportant, - variablesUsed: [], - parent: variableRootRule, - isUnderAtRule: false - }); - - return prevVariableMap; - }, {}) - ); - - - // Chainable helper function to log any messages (warnings) - var logResolveValueResult = function(valueResult) { - // Log any warnings that might of popped up - var warningList = [].concat(valueResult.warnings); - warningList.forEach(function(warningArgs) { - warningArgs = [].concat(warningArgs); - result.warn.apply(result, warningArgs); - }); - - // Keep the chain going - return valueResult; - }; - - - // Collect all of the variables defined - // --------------------------------------------------------- - // --------------------------------------------------------- - //console.log('Collecting variables defined START'); - eachCssVariableDeclaration(css, function(decl) { - var declParentRule = decl.parent; - - var valueResults = logResolveValueResult(resolveValue(decl, map)); - // Split out each selector piece into its own declaration for easier logic down the road - decl.parent.selectors.forEach(function(selector) { - // Create a detached clone - var splitOutRule = shallowCloneNode(decl.parent); - splitOutRule.selector = selector; - splitOutRule.parent = decl.parent.parent; - - var declClone = decl.clone(); - splitOutRule.append(declClone); - - var prop = decl.prop; - map[prop] = (map[prop] || []).concat({ - decl: declClone, - prop: prop, - calculatedInPlaceValue: valueResults.value, - isImportant: decl.important || false, - variablesUsed: valueResults.variablesUsed, - parent: splitOutRule, - // variables inside root or at-rules (eg. @media, @support) - isUnderAtRule: splitOutRule.parent.type === 'atrule' - }); - }); - - let preserveDecl; - if (typeof opts.preserve === "function") { - preserveDecl = opts.preserve(decl); - } else { - preserveDecl = opts.preserve; - } - // Remove the variable declaration because they are pretty much useless after we resolve them - if(!preserveDecl) { - decl.remove(); - } - // Or we can also just show the computed value used for that variable - else if(preserveDecl === 'computed') { - decl.value = valueResults.value; - } - // Otherwise just keep them as var declarations - //else {} - - // We add to the clean up list if we removed some variable declarations to make it become an empty rule - // We clean up later on because we don't want to modify the AST when we still need to reference these later on - if(declParentRule.nodes.length <= 0) { - nodesToRemoveAtEnd.push(declParentRule); - } - }); - //console.log('Collecting variables defined END'); - - - - - - // Resolve variables everywhere - // --------------------------------------------------------- - // --------------------------------------------------------- - - // Collect all the rules that have declarations that use variables - var rulesThatHaveDeclarationsWithVariablesList = []; - css.walk(function(rule) { - // We're only interested in Containers with children. - if (rule.nodes === undefined) return; - - var doesRuleUseVariables = rule.nodes.some(function(node) { - if(node.type === 'decl') { - var decl = node; - // If it uses variables - // and is not a variable declarations that we may be preserving from earlier - if(resolveValue.RE_VAR_FUNC.test(decl.value) && !RE_VAR_PROP.test(decl.prop)) { - return true; - } - } - - return false; - }); - - if(doesRuleUseVariables) { - rulesThatHaveDeclarationsWithVariablesList.push(rule); - } - }); - - rulesThatHaveDeclarationsWithVariablesList.forEach(function(rule) { - var rulesToWorkOn = [].concat(rule); - // Split out the rule into each comma separated selector piece - // We only need to split if it's actually a Rule with multiple selectors - if(rule.type == 'rule' && rule.selectors.length > 1) { - // Reverse the selectors so that we can cloneAfter in the same comma separated order - rulesToWorkOn = rule.selectors.reverse().map(function(selector) { - var ruleClone = rule.cloneAfter(); - ruleClone.selector = selector; - - return ruleClone; - }); - - rule.remove(); - } - - // Resolve the declarations - rulesToWorkOn.forEach(function(ruleToWorkOn) { - ruleToWorkOn.nodes.slice(0).forEach(function(node) { - if(node.type === 'decl') { - var decl = node; - resolveDecl(decl, map, opts.preserve, opts.preserveAtRulesOrder, logResolveValueResult); - } - }); - }); - - }); - - - - - - // Clean up any nodes we don't want anymore - // We clean up at the end because we don't want to modify the AST when we still need to reference these later on - nodesToRemoveAtEnd.forEach(cleanUpNode); - - // Clean up JS-injected variables marked for removal - injectedDeclsToRemoveAtEnd.forEach(function(injectedDecl) { - injectedDecl.remove(); - }); - - - //console.log('map', map); - - /* * / + // List of nodes that if empty, will be removed + // We use this because we don't want to modify the AST when we still need to reference these later on + var nodesToRemoveAtEnd = []; + + // Keep track of the injected from `opts.variables` to remove at the end + // if user passes `opts.preserveInjectedVariables = false` + var injectedDeclsToRemoveAtEnd = []; + + // Map of variable names to a list of declarations + var map = {}; + + // Add the js defined variables `opts.variables` to the map + map = extend( + map, + Object.keys(opts.variables).reduce(function( + prevVariableMap, + variableName + ) { + var variableEntry = opts.variables[variableName]; + // Automatically prefix any variable with `--` (CSS custom property syntax) if it doesn't have it already + variableName = + variableName.slice(0, 2) === "--" + ? variableName + : "--" + variableName; + var variableValue = (variableEntry || {}).value || variableEntry; + var isImportant = (variableEntry || {}).isImportant || false; + + // Add a root node to the AST + var variableRootRule = postcss.rule({ selector: ":root" }); + css.root().prepend(variableRootRule); + // Add the variable decl to the root node + var varDecl = postcss.decl({ + prop: variableName, + value: variableValue, + important: isImportant + }); + variableRootRule.append(varDecl); + + // Collect JS-injected variables for removal if `opts.preserveInjectedVariables = false` + if (!opts.preserveInjectedVariables) { + injectedDeclsToRemoveAtEnd.push(varDecl); + } + + // Add the entry to the map + prevVariableMap[variableName] = ( + prevVariableMap[variableName] || [] + ).concat({ + decl: varDecl, + prop: variableName, + calculatedInPlaceValue: variableValue, + isImportant: isImportant, + variablesUsed: [], + parent: variableRootRule, + isUnderAtRule: false + }); + + return prevVariableMap; + }, + {}) + ); + + // Chainable helper function to log any messages (warnings) + var logResolveValueResult = function(valueResult) { + // Log any warnings that might of popped up + var warningList = [].concat(valueResult.warnings); + warningList.forEach(function(warningArgs) { + warningArgs = [].concat(warningArgs); + result.warn.apply(result, warningArgs); + }); + + // Keep the chain going + return valueResult; + }; + + // Collect all of the variables defined + // --------------------------------------------------------- + // --------------------------------------------------------- + //console.log('Collecting variables defined START'); + eachCssVariableDeclaration(css, function(decl) { + var declParentRule = decl.parent; + + var valueResults = logResolveValueResult(resolveValue(decl, map)); + // Split out each selector piece into its own declaration for easier logic down the road + decl.parent.selectors.forEach(function(selector) { + // Create a detached clone + var splitOutRule = shallowCloneNode(decl.parent); + splitOutRule.selector = selector; + splitOutRule.parent = decl.parent.parent; + + var declClone = decl.clone(); + splitOutRule.append(declClone); + + var prop = decl.prop; + map[prop] = (map[prop] || []).concat({ + decl: declClone, + prop: prop, + calculatedInPlaceValue: valueResults.value, + isImportant: decl.important || false, + variablesUsed: valueResults.variablesUsed, + parent: splitOutRule, + // variables inside root or at-rules (eg. @media, @support) + isUnderAtRule: splitOutRule.parent.type === "atrule" + }); + }); + + let preserveDecl; + if (typeof opts.preserve === "function") { + preserveDecl = opts.preserve(decl); + } else { + preserveDecl = opts.preserve; + } + // Remove the variable declaration because they are pretty much useless after we resolve them + if (!preserveDecl) { + decl.remove(); + } + // Or we can also just show the computed value used for that variable + else if (preserveDecl === "computed") { + decl.value = valueResults.value; + } + // Otherwise just keep them as var declarations + //else {} + + // We add to the clean up list if we removed some variable declarations to make it become an empty rule + // We clean up later on because we don't want to modify the AST when we still need to reference these later on + if (declParentRule.nodes.length <= 0) { + nodesToRemoveAtEnd.push(declParentRule); + } + }); + //console.log('Collecting variables defined END'); + + // Resolve variables everywhere + // --------------------------------------------------------- + // --------------------------------------------------------- + + // Collect all the rules that have declarations that use variables + var rulesThatHaveDeclarationsWithVariablesList = []; + css.walk(function(rule) { + // We're only interested in Containers with children. + if (rule.nodes === undefined) return; + + var doesRuleUseVariables = rule.nodes.some(function(node) { + if (node.type === "decl") { + var decl = node; + // If it uses variables + // and is not a variable declarations that we may be preserving from earlier + if ( + resolveValue.RE_VAR_FUNC.test(decl.value) && + !RE_VAR_PROP.test(decl.prop) + ) { + return true; + } + } + + return false; + }); + + if (doesRuleUseVariables) { + rulesThatHaveDeclarationsWithVariablesList.push(rule); + } + }); + + rulesThatHaveDeclarationsWithVariablesList.forEach(function(rule) { + var rulesToWorkOn = [].concat(rule); + // Split out the rule into each comma separated selector piece + // We only need to split if it's actually a Rule with multiple selectors (comma separated) + if (rule.type === "rule" && rule.selectors.length > 1) { + // Reverse the selectors so that we can cloneAfter in the same comma separated order + rulesToWorkOn = rule.selectors.reverse().map(function(selector) { + var ruleClone = rule.cloneAfter(); + ruleClone.selector = selector; + + return ruleClone; + }); + + rule.remove(); + } + + // Resolve the declarations + rulesToWorkOn.forEach(function(ruleToWorkOn) { + ruleToWorkOn.nodes.slice(0).forEach(function(node) { + if (node.type === "decl") { + var decl = node; + resolveDecl( + decl, + map, + opts.preserve, + opts.preserveAtRulesOrder, + logResolveValueResult + ); + } + }); + }); + }); + + // Clean up any nodes we don't want anymore + // We clean up at the end because we don't want to modify the AST when we still need to reference these later on + nodesToRemoveAtEnd.forEach(cleanUpNode); + + // Clean up JS-injected variables marked for removal + injectedDeclsToRemoveAtEnd.forEach(function(injectedDecl) { + injectedDecl.remove(); + }); + + //console.log('map', map); + + /* * / } catch(e) { //console.log('e', e.message); console.log('e', e.message, e.stack); } /* */ - - }; + }; }); From f4b703f2a60eb12a286738fa7f85cd58fa9bab9e Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 24 Apr 2020 18:22:13 -0500 Subject: [PATCH 13/21] Prepare changelog with #104 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fe1dbf..fc16a22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# v0.17.0 - 2020-4-24 + +- Expand variables in AtRule properties + - Thank you to [@pvande](https://github.com/pvande) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/104) + - Merged via https://github.com/MadLittleMods/postcss-css-variables/pull/121 + # v0.16.0 - 2020-4-24 - Add ability to pass callback function to `options.preserve` to determine whether to preserve declaration From 8eb32cf2a2be1550e59f47bcb0eec11393496a8e Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 24 Apr 2020 18:23:22 -0500 Subject: [PATCH 14/21] 0.17.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4d33e75..8fbb1d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-css-variables", - "version": "0.16.0", + "version": "0.17.0", "description": "PostCSS plugin to transform CSS Custom Properties(CSS variables) syntax into a static representation", "keywords": [ "postcss", From de77c3ca1bdeeef681b9b81484b29bafe865d8f7 Mon Sep 17 00:00:00 2001 From: Chris Stiles Date: Mon, 10 Aug 2020 10:54:07 -0700 Subject: [PATCH 15/21] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ea6978a..42ce497 100644 --- a/README.md +++ b/README.md @@ -262,7 +262,7 @@ Will be transformed to: This plugin was spawned out of a [discussion on the `cssnext` repo](https://github.com/cssnext/cssnext/issues/99) and a personal need. -There is another similar plugin available, [`postcss-custom-properties`](https://github.com/postcss/postcss-custom-properties), although it restricts itself much more than this plugin, preferring partial spec conformance. This plugin has the same capabilities but also adds imperfect feature support which stem from not being to know what the DOM will look like when you compile your CSS. We instead look at the explicit structure of your CSS selectors. +There is another similar plugin available, [`postcss-custom-properties`](https://github.com/postcss/postcss-custom-properties), although it restricts itself much more than this plugin, preferring partial spec conformance. This plugin has the same capabilities but also adds imperfect feature support which stem from not being able to know what the DOM will look like when you compile your CSS. We instead look at the explicit structure of your CSS selectors. ### Interoperability and differences from `postcss-custom-properties` From 2d7e0ee376b7c52e89e6a038ad64a5d0b1dd5d35 Mon Sep 17 00:00:00 2001 From: Lars Laade Date: Mon, 15 Feb 2021 21:39:33 +0100 Subject: [PATCH 16/21] adding basic postcss 8 support --- README.md | 2 +- index.js | 416 ++++++++++++++++++++++++++------------------------- package.json | 7 +- 3 files changed, 216 insertions(+), 209 deletions(-) diff --git a/README.md b/README.md index 42ce497..85f2185 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ### Install ``` -npm install postcss-css-variables --save-dev +npm install postcss postcss-css-variables --save-dev ``` ### Table of Contents diff --git a/index.js b/index.js index 23534e4..95dc3eb 100644 --- a/index.js +++ b/index.js @@ -6,7 +6,6 @@ // For Debugging //var nomo = require('node-monkey').start({port: 50501}); -var postcss = require("postcss"); var extend = require("extend"); var shallowCloneNode = require("./lib/shallow-clone-node"); @@ -64,229 +63,234 @@ var defaults = { preserveAtRulesOrder: false }; -module.exports = postcss.plugin("postcss-css-variables", function(options) { +module.exports = (options = {}) => { var opts = extend({}, defaults, options); // Work with opts here - return function(css, result) { - // Transform CSS AST here - - /* * / - try { - /* */ - - // List of nodes that if empty, will be removed - // We use this because we don't want to modify the AST when we still need to reference these later on - var nodesToRemoveAtEnd = []; - - // Keep track of the injected from `opts.variables` to remove at the end - // if user passes `opts.preserveInjectedVariables = false` - var injectedDeclsToRemoveAtEnd = []; - - // Map of variable names to a list of declarations - var map = {}; - - // Add the js defined variables `opts.variables` to the map - map = extend( - map, - Object.keys(opts.variables).reduce(function( - prevVariableMap, - variableName - ) { - var variableEntry = opts.variables[variableName]; - // Automatically prefix any variable with `--` (CSS custom property syntax) if it doesn't have it already - variableName = - variableName.slice(0, 2) === "--" - ? variableName - : "--" + variableName; - var variableValue = (variableEntry || {}).value || variableEntry; - var isImportant = (variableEntry || {}).isImportant || false; - - // Add a root node to the AST - var variableRootRule = postcss.rule({ selector: ":root" }); - css.root().prepend(variableRootRule); - // Add the variable decl to the root node - var varDecl = postcss.decl({ - prop: variableName, - value: variableValue, - important: isImportant - }); - variableRootRule.append(varDecl); - - // Collect JS-injected variables for removal if `opts.preserveInjectedVariables = false` - if (!opts.preserveInjectedVariables) { - injectedDeclsToRemoveAtEnd.push(varDecl); - } + return { + postcssPlugin: 'postcss-css-variables', + Once(css, { decl, result, rule }) { + // Transform CSS AST here + + /* * / + try { + /* */ + + // List of nodes that if empty, will be removed + // We use this because we don't want to modify the AST when we still need to reference these later on + var nodesToRemoveAtEnd = []; + + // Keep track of the injected from `opts.variables` to remove at the end + // if user passes `opts.preserveInjectedVariables = false` + var injectedDeclsToRemoveAtEnd = []; + + // Map of variable names to a list of declarations + var map = {}; + + // Add the js defined variables `opts.variables` to the map + map = extend( + map, + Object.keys(opts.variables).reduce(function( + prevVariableMap, + variableName + ) { + var variableEntry = opts.variables[variableName]; + // Automatically prefix any variable with `--` (CSS custom property syntax) if it doesn't have it already + variableName = + variableName.slice(0, 2) === "--" + ? variableName + : "--" + variableName; + var variableValue = (variableEntry || {}).value || variableEntry; + var isImportant = (variableEntry || {}).isImportant || false; + + // Add a root node to the AST + var variableRootRule = rule({ selector: ":root" }); + css.root().prepend(variableRootRule); + // Add the variable decl to the root node + var varDecl = decl({ + prop: variableName, + value: variableValue, + important: isImportant + }); + variableRootRule.append(varDecl); + + // Collect JS-injected variables for removal if `opts.preserveInjectedVariables = false` + if (!opts.preserveInjectedVariables) { + injectedDeclsToRemoveAtEnd.push(varDecl); + } - // Add the entry to the map - prevVariableMap[variableName] = ( - prevVariableMap[variableName] || [] - ).concat({ - decl: varDecl, - prop: variableName, - calculatedInPlaceValue: variableValue, - isImportant: isImportant, - variablesUsed: [], - parent: variableRootRule, - isUnderAtRule: false + // Add the entry to the map + prevVariableMap[variableName] = ( + prevVariableMap[variableName] || [] + ).concat({ + decl: varDecl, + prop: variableName, + calculatedInPlaceValue: variableValue, + isImportant: isImportant, + variablesUsed: [], + parent: variableRootRule, + isUnderAtRule: false + }); + + return prevVariableMap; + }, + {}) + ); + + // Chainable helper function to log any messages (warnings) + var logResolveValueResult = function(valueResult) { + // Log any warnings that might of popped up + var warningList = [].concat(valueResult.warnings); + warningList.forEach(function(warningArgs) { + warningArgs = [].concat(warningArgs); + result.warn.apply(result, warningArgs); }); - return prevVariableMap; - }, - {}) - ); - - // Chainable helper function to log any messages (warnings) - var logResolveValueResult = function(valueResult) { - // Log any warnings that might of popped up - var warningList = [].concat(valueResult.warnings); - warningList.forEach(function(warningArgs) { - warningArgs = [].concat(warningArgs); - result.warn.apply(result, warningArgs); - }); - - // Keep the chain going - return valueResult; - }; - - // Collect all of the variables defined - // --------------------------------------------------------- - // --------------------------------------------------------- - //console.log('Collecting variables defined START'); - eachCssVariableDeclaration(css, function(decl) { - var declParentRule = decl.parent; - - var valueResults = logResolveValueResult(resolveValue(decl, map)); - // Split out each selector piece into its own declaration for easier logic down the road - decl.parent.selectors.forEach(function(selector) { - // Create a detached clone - var splitOutRule = shallowCloneNode(decl.parent); - splitOutRule.selector = selector; - splitOutRule.parent = decl.parent.parent; - - var declClone = decl.clone(); - splitOutRule.append(declClone); - - var prop = decl.prop; - map[prop] = (map[prop] || []).concat({ - decl: declClone, - prop: prop, - calculatedInPlaceValue: valueResults.value, - isImportant: decl.important || false, - variablesUsed: valueResults.variablesUsed, - parent: splitOutRule, - // variables inside root or at-rules (eg. @media, @support) - isUnderAtRule: splitOutRule.parent.type === "atrule" + // Keep the chain going + return valueResult; + }; + + // Collect all of the variables defined + // --------------------------------------------------------- + // --------------------------------------------------------- + //console.log('Collecting variables defined START'); + eachCssVariableDeclaration(css, function(decl) { + var declParentRule = decl.parent; + + var valueResults = logResolveValueResult(resolveValue(decl, map)); + // Split out each selector piece into its own declaration for easier logic down the road + decl.parent.selectors.forEach(function(selector) { + // Create a detached clone + var splitOutRule = shallowCloneNode(decl.parent); + splitOutRule.selector = selector; + splitOutRule.parent = decl.parent.parent; + + var declClone = decl.clone(); + splitOutRule.append(declClone); + + var prop = decl.prop; + map[prop] = (map[prop] || []).concat({ + decl: declClone, + prop: prop, + calculatedInPlaceValue: valueResults.value, + isImportant: decl.important || false, + variablesUsed: valueResults.variablesUsed, + parent: splitOutRule, + // variables inside root or at-rules (eg. @media, @support) + isUnderAtRule: splitOutRule.parent.type === "atrule" + }); }); - }); - - let preserveDecl; - if (typeof opts.preserve === "function") { - preserveDecl = opts.preserve(decl); - } else { - preserveDecl = opts.preserve; - } - // Remove the variable declaration because they are pretty much useless after we resolve them - if (!preserveDecl) { - decl.remove(); - } - // Or we can also just show the computed value used for that variable - else if (preserveDecl === "computed") { - decl.value = valueResults.value; - } - // Otherwise just keep them as var declarations - //else {} - // We add to the clean up list if we removed some variable declarations to make it become an empty rule - // We clean up later on because we don't want to modify the AST when we still need to reference these later on - if (declParentRule.nodes.length <= 0) { - nodesToRemoveAtEnd.push(declParentRule); - } - }); - //console.log('Collecting variables defined END'); - - // Resolve variables everywhere - // --------------------------------------------------------- - // --------------------------------------------------------- - - // Collect all the rules that have declarations that use variables - var rulesThatHaveDeclarationsWithVariablesList = []; - css.walk(function(rule) { - // We're only interested in Containers with children. - if (rule.nodes === undefined) return; - - var doesRuleUseVariables = rule.nodes.some(function(node) { - if (node.type === "decl") { - var decl = node; - // If it uses variables - // and is not a variable declarations that we may be preserving from earlier - if ( - resolveValue.RE_VAR_FUNC.test(decl.value) && - !RE_VAR_PROP.test(decl.prop) - ) { - return true; - } + let preserveDecl; + if (typeof opts.preserve === "function") { + preserveDecl = opts.preserve(decl); + } else { + preserveDecl = opts.preserve; + } + // Remove the variable declaration because they are pretty much useless after we resolve them + if (!preserveDecl) { + decl.remove(); + } + // Or we can also just show the computed value used for that variable + else if (preserveDecl === "computed") { + decl.value = valueResults.value; } + // Otherwise just keep them as var declarations + //else {} - return false; + // We add to the clean up list if we removed some variable declarations to make it become an empty rule + // We clean up later on because we don't want to modify the AST when we still need to reference these later on + if (declParentRule.nodes.length <= 0) { + nodesToRemoveAtEnd.push(declParentRule); + } }); + //console.log('Collecting variables defined END'); - if (doesRuleUseVariables) { - rulesThatHaveDeclarationsWithVariablesList.push(rule); - } - }); - - rulesThatHaveDeclarationsWithVariablesList.forEach(function(rule) { - var rulesToWorkOn = [].concat(rule); - // Split out the rule into each comma separated selector piece - // We only need to split if it's actually a Rule with multiple selectors (comma separated) - if (rule.type === "rule" && rule.selectors.length > 1) { - // Reverse the selectors so that we can cloneAfter in the same comma separated order - rulesToWorkOn = rule.selectors.reverse().map(function(selector) { - var ruleClone = rule.cloneAfter(); - ruleClone.selector = selector; - - return ruleClone; - }); + // Resolve variables everywhere + // --------------------------------------------------------- + // --------------------------------------------------------- - rule.remove(); - } + // Collect all the rules that have declarations that use variables + var rulesThatHaveDeclarationsWithVariablesList = []; + css.walk(function(rule) { + // We're only interested in Containers with children. + if (rule.nodes === undefined) return; - // Resolve the declarations - rulesToWorkOn.forEach(function(ruleToWorkOn) { - ruleToWorkOn.nodes.slice(0).forEach(function(node) { + var doesRuleUseVariables = rule.nodes.some(function(node) { if (node.type === "decl") { var decl = node; - resolveDecl( - decl, - map, - opts.preserve, - opts.preserveAtRulesOrder, - logResolveValueResult - ); + // If it uses variables + // and is not a variable declarations that we may be preserving from earlier + if ( + resolveValue.RE_VAR_FUNC.test(decl.value) && + !RE_VAR_PROP.test(decl.prop) + ) { + return true; + } } + + return false; + }); + + if (doesRuleUseVariables) { + rulesThatHaveDeclarationsWithVariablesList.push(rule); + } + }); + + rulesThatHaveDeclarationsWithVariablesList.forEach(function(rule) { + var rulesToWorkOn = [].concat(rule); + // Split out the rule into each comma separated selector piece + // We only need to split if it's actually a Rule with multiple selectors (comma separated) + if (rule.type === "rule" && rule.selectors.length > 1) { + // Reverse the selectors so that we can cloneAfter in the same comma separated order + rulesToWorkOn = rule.selectors.reverse().map(function(selector) { + var ruleClone = rule.cloneAfter(); + ruleClone.selector = selector; + + return ruleClone; + }); + + rule.remove(); + } + + // Resolve the declarations + rulesToWorkOn.forEach(function(ruleToWorkOn) { + ruleToWorkOn.nodes.slice(0).forEach(function(node) { + if (node.type === "decl") { + var decl = node; + resolveDecl( + decl, + map, + opts.preserve, + opts.preserveAtRulesOrder, + logResolveValueResult + ); + } + }); }); }); - }); - - // Clean up any nodes we don't want anymore - // We clean up at the end because we don't want to modify the AST when we still need to reference these later on - nodesToRemoveAtEnd.forEach(cleanUpNode); - - // Clean up JS-injected variables marked for removal - injectedDeclsToRemoveAtEnd.forEach(function(injectedDecl) { - injectedDecl.remove(); - }); - - //console.log('map', map); - - /* * / - } - catch(e) { - //console.log('e', e.message); - console.log('e', e.message, e.stack); - } - /* */ + + // Clean up any nodes we don't want anymore + // We clean up at the end because we don't want to modify the AST when we still need to reference these later on + nodesToRemoveAtEnd.forEach(cleanUpNode); + + // Clean up JS-injected variables marked for removal + injectedDeclsToRemoveAtEnd.forEach(function(injectedDecl) { + injectedDecl.remove(); + }); + + //console.log('map', map); + + /* * / + } + catch(e) { + //console.log('e', e.message); + console.log('e', e.message, e.stack); + } + /* */ + } }; -}); +}; + +module.exports.postcss = true; diff --git a/package.json b/package.json index 8fbb1d5..e48618f 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,7 @@ "dependencies": { "balanced-match": "^1.0.0", "escape-string-regexp": "^1.0.3", - "extend": "^3.0.1", - "postcss": "^6.0.8" + "extend": "^3.0.1" }, "devDependencies": { "bluebird": "^3.5.0", @@ -27,9 +26,13 @@ "eslint": "^4.4.1", "eslint-plugin-react": "^7.1.0", "mocha": "^5.2.0", + "postcss": "^8.2.6", "postcss-discard-comments": "^4.0.0", "postcss-normalize-whitespace": "^4.0.0" }, + "peerDependencies": { + "postcss": "^8.2.6" + }, "scripts": { "test": "mocha", "lint": "eslint ." From 0ae84a1d376f5339f84cb16e5d8f95f2bf22c911 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 11 May 2021 15:19:42 -0500 Subject: [PATCH 17/21] Prepare changelog with #129 --- CHANGELOG.md | 60 +++++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc16a22..dece1fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,21 @@ -# v0.17.0 - 2020-4-24 +# v0.18.0 - 2021-05-11 + +- [breaking] Add basic postcss 8 support (older versions of PostCSS no longer compatible) + - Thank you to [@delucis](https://github.com/delucis) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/129) + + +# v0.17.0 - 2020-04-24 - Expand variables in AtRule properties - Thank you to [@pvande](https://github.com/pvande) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/104) - Merged via https://github.com/MadLittleMods/postcss-css-variables/pull/121 -# v0.16.0 - 2020-4-24 +# v0.16.0 - 2020-04-24 - Add ability to pass callback function to `options.preserve` to determine whether to preserve declaration - Thank you to [@ekatioz](https://github.com/ekatioz) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/116) -# v0.15.0 - 2020-4-24 +# v0.15.0 - 2020-04-24 - Fix algorithm to find balanced `var()` pairs and nested parenthesis - Thank you to [@Poetro](https://github.com/Poetro) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/112) @@ -19,55 +25,55 @@ - Fix regex in `resolve-value.js` to allow nested CSS functions - Thank you to [@juliovedovatto](https://github.com/juliovedovatto) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/97) -# v0.13.0 - 2019-6-17 +# v0.13.0 - 2019-06-17 - Add `options.preserveAtRulesOrder` so media queries are outputted in the order they are defined (as expected) - Thank you to [@erikthalen](https://github.com/erikthalen) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/92) via https://github.com/MadLittleMods/postcss-css-variables/pull/101 - Remove `calc` from readme table of contents for non-existent section - Thank you to [@AlexandreArpin](https://github.com/AlexandreArpin) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/99) -# v0.12.0 - 2019-2-21 +# v0.12.0 - 2019-02-21 - Accept whitespace in `var( --var )` expression - Thank you to [@benwest](https://github.com/benwest) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/93) -# v0.11.0 - 2018-10-9 +# v0.11.0 - 2018-10-09 - Fix JS-defined variables using `isImportant`, https://github.com/MadLittleMods/postcss-css-variables/pull/87 -# v0.10.0 - 2018-9-25 +# v0.10.0 - 2018-09-25 - Cast `opts.variables` variable values to string - Thank you to [@shonie](https://github.com/shonie) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/84) -# v0.9.0 - 2018-6-26 +# v0.9.0 - 2018-06-26 - Adds `opts.preserveInjectedVariables`, which when set to `false`, removes the `:root { ... }` custom property declarations added via `opts.variables` - Thank you to [@akdetrick](https://github.com/akdetrick) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/74) -# v0.8.1 - 2018-3-21 +# v0.8.1 - 2018-03-21 - Log `undefined` variables (available in `result.warnings()`) - Thank you to [@pixeldrew](https://github.com/pixeldrew) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/69) -# v0.8.0 - 2017-8-8 +# v0.8.0 - 2017-08-08 - Remove PostCSS `moveTo`/`append` deprecation warnings, [#50](https://github.com/MadLittleMods/postcss-css-variables/issues/50) - Thank you to [@modosc](https://github.com/modosc) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/56) -# v0.7.0 - 2017-3-12 +# v0.7.0 - 2017-03-12 - Resolve `var` usage in fallbacks, [#37](https://github.com/MadLittleMods/postcss-css-variables/issues/37) - Thank you to [@asvny](https://github.com/asvny) and [@marklu](https://github.com/marklu) for the contribution, [#39](https://github.com/MadLittleMods/postcss-css-variables/issues/39) -> [#49](https://github.com/MadLittleMods/postcss-css-variables/pull/49) -# v0.6.0 - 2016-9-23 +# v0.6.0 - 2016-09-23 - Update/refactor readme - Thank you to [@isiahmeadows](github.com/isiahmeadows) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/36) - Use string value for `undefined` variables to play nice with other plugins downstream - Thank you to [@vincentorback](github.com/vincentorback) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/44) -# v0.5.2 - 2016-8-24 +# v0.5.2 - 2016-08-24 - Fix [#42](https://github.com/MadLittleMods/postcss-css-variables/issues/42) where `opts.preserve` was not working inside at-rules - Thank you to [@muftiev](github.com/muftiev) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/43) @@ -77,64 +83,64 @@ - Fix [postcss/postcss#611](https://github.com/postcss/postcss/issues/611) where we were trying to remove the root node on clean up - Improved test setup -# v0.5.0 - 2015-9-12 +# v0.5.0 - 2015-09-12 - Upgrade to PostCSS v5. Fix [#20](https://github.com/MadLittleMods/postcss-css-variables/issues/20) -# v0.4.0 - 2015-7-2 +# v0.4.0 - 2015-07-02 - Fix [#15](https://github.com/MadLittleMods/postcss-css-variables/issues/15) - Remove slowness from cloning the `root` with `node.clone().removeAll()`. Now using `./lib/shallow-clone-node.js` to avoid cloning children which will get removed right after. - Thank you to [@ddprrt](https://github.com/ddprrt) for bringing up the slowness issue in this article, [PostCSS misconceptions](https://medium.com/@ddprrt/postcss-misconceptions-faf5dc5038df). -# v0.3.9 - 2015-6-29 +# v0.3.9 - 2015-06-29 - Remove `opts` global leak. Fix [#13](https://github.com/MadLittleMods/postcss-css-variables/issues/13) -# v0.3.8 - 2015-5-28 +# v0.3.8 - 2015-05-28 - Add support for pseudo selectors `:hover` `:before` -# v0.3.7 - 2015-5-27 +# v0.3.7 - 2015-05-27 - Fix [#7](https://github.com/MadLittleMods/postcss-css-variables/issues/7): Support for child combinator - Added tests for child-combinator/direct-descendant coverage -# v0.3.6 - 2015-5-21 +# v0.3.6 - 2015-05-21 - Fix [#6](https://github.com/MadLittleMods/postcss-css-variables/issues/6). Variable usage in comma separated selector to use proper scope -# v0.3.5 - 2015-5-12 +# v0.3.5 - 2015-05-12 - Big refactor of code to reduce cyclomatic complexity. Still needs work though. - Fix variable referencing another variable resolution when being changed by at-rule in non-root rule -# v0.3.4 - 2015-5-12 +# v0.3.4 - 2015-05-12 - Fix variable referencing another variable resolution when being changed by at-rule -# v0.3.3 - 2015-5-11 +# v0.3.3 - 2015-05-11 - Add support for last piece of combinator chain in selector resolution matching. - `.foo + .bar` can match variables declared in `.bar` -# v0.3.1 - 2015-5-5 +# v0.3.1 - 2015-05-05 - Large overhaul of code to make it more robust on proper scope resolution. - Fix [#2]](https://github.com/MadLittleMods/postcss-css-variables/issues/2) -# v0.2.3 - 2015-5-4 +# v0.2.3 - 2015-05-04 - Add support for CSS4 descendant selector `>>` syntax -# v0.2.2 - 2015-5-1 +# v0.2.2 - 2015-05-01 - Automatically prefix any variables defined in `options.variables` with `--` (according to CSS custom property syntax). -# v0.2.1 - 2015-4-30 +# v0.2.1 - 2015-04-30 - Added support for descendant selector nesting instead of just physical space nesting - Fixed issue with comma separated rules. It was throwing a undefined is not a function error - Moved to external scope check `isUnderScope` instead of integrated into `resolveValue` - Added test for empty `var()` call. See [test/fixtures/empty-var-func.css](https://github.com/MadLittleMods/postcss-css-variables/blob/master/test/fixtures/empty-var-func.css) -# v0.1.0 - 2015-4-29 +# v0.1.0 - 2015-04-29 - First release From f791dd22743f5b7ba05485789860de0fc5fc59f9 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 11 May 2021 15:20:13 -0500 Subject: [PATCH 18/21] 0.18.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e48618f..568d0e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-css-variables", - "version": "0.17.0", + "version": "0.18.0", "description": "PostCSS plugin to transform CSS Custom Properties(CSS variables) syntax into a static representation", "keywords": [ "postcss", From b881aaa8b1913dac9883bfcf8de54c19132ea769 Mon Sep 17 00:00:00 2001 From: Sergey Bondar Date: Fri, 7 Jan 2022 01:33:31 +0300 Subject: [PATCH 19/21] fix: #130 variables in nested comma-separated selectors --- index.js | 56 ++++++------ .../cascade-and-multiple-on-nested-rules.css | 37 ++++++++ ...-and-multiple-on-nested-rules.expected.css | 87 +++++++++++++++++++ test/test.js | 2 + 4 files changed, 153 insertions(+), 29 deletions(-) create mode 100644 test/fixtures/cascade-and-multiple-on-nested-rules.css create mode 100644 test/fixtures/cascade-and-multiple-on-nested-rules.expected.css diff --git a/index.js b/index.js index 95dc3eb..4174270 100644 --- a/index.js +++ b/index.js @@ -234,40 +234,38 @@ module.exports = (options = {}) => { }); if (doesRuleUseVariables) { - rulesThatHaveDeclarationsWithVariablesList.push(rule); + if (rule.type === "rule" && rule.selectors.length > 1) { + // Split out the rule into each comma separated selector piece + // We only need to split if it's actually a Rule with multiple selectors (comma separated) + // duplicate rules would be probably merged with cssnano (cannot be sure about nested) + rule.selectors.reverse().forEach(function(selector) { + var ruleClone = rule.cloneAfter(); + ruleClone.selector = selector; + + return ruleClone; + }); + + // Rules will be added to list in the next traverse + rule.remove(); + } else { + rulesThatHaveDeclarationsWithVariablesList.push(rule); + } } }); rulesThatHaveDeclarationsWithVariablesList.forEach(function(rule) { - var rulesToWorkOn = [].concat(rule); - // Split out the rule into each comma separated selector piece - // We only need to split if it's actually a Rule with multiple selectors (comma separated) - if (rule.type === "rule" && rule.selectors.length > 1) { - // Reverse the selectors so that we can cloneAfter in the same comma separated order - rulesToWorkOn = rule.selectors.reverse().map(function(selector) { - var ruleClone = rule.cloneAfter(); - ruleClone.selector = selector; - - return ruleClone; - }); - - rule.remove(); - } - // Resolve the declarations - rulesToWorkOn.forEach(function(ruleToWorkOn) { - ruleToWorkOn.nodes.slice(0).forEach(function(node) { - if (node.type === "decl") { - var decl = node; - resolveDecl( - decl, - map, - opts.preserve, - opts.preserveAtRulesOrder, - logResolveValueResult - ); - } - }); + rule.nodes.slice(0).forEach(function(node) { + if (node.type === "decl") { + var decl = node; + resolveDecl( + decl, + map, + opts.preserve, + opts.preserveAtRulesOrder, + logResolveValueResult + ); + } }); }); diff --git a/test/fixtures/cascade-and-multiple-on-nested-rules.css b/test/fixtures/cascade-and-multiple-on-nested-rules.css new file mode 100644 index 0000000..cd5a678 --- /dev/null +++ b/test/fixtures/cascade-and-multiple-on-nested-rules.css @@ -0,0 +1,37 @@ +:root { + --some-width: 150px; +} +:root { + --White1: #FFF; +} + +.a, .b { + width: var(--some-width); + + .simple { + color: var(--White1); + } +} + +.a { + width: var(--some-width); + + a, label, &:after { + color: var(--White1); + } +} + +/* postcss-nested double parent selector case */ +.a, .b { + /* here variable */ + width: var(--some-width); + + /* and here another */ + a, label { + background: var(--White1); + + ol, ul { + width: var(--some-width); + } + } +} diff --git a/test/fixtures/cascade-and-multiple-on-nested-rules.expected.css b/test/fixtures/cascade-and-multiple-on-nested-rules.expected.css new file mode 100644 index 0000000..30a5568 --- /dev/null +++ b/test/fixtures/cascade-and-multiple-on-nested-rules.expected.css @@ -0,0 +1,87 @@ +.a { + width: 150px; + + .simple { + color: #FFF + } +} + +.b { + width: 150px; + + .simple { + color: #FFF + } +} + +.a { + width: 150px; + + a { + color: #FFF + } + + label { + color: #FFF + } + + &:after { + color: #FFF + } +} + +.a { + width: 150px; + + a { + background: #FFF; + + ol { + width: 150px; + } + + ul { + width: 150px; + } + } + + label { + background: #FFF; + + ol { + width: 150px; + } + + ul { + width: 150px; + } + } +} + +.b { + width: 150px; + + a { + background: #FFF; + + ol { + width: 150px; + } + + ul { + width: 150px; + } + } + + label { + background: #FFF; + + ol { + width: 150px; + } + + ul { + width: 150px; + } + } +} diff --git a/test/test.js b/test/test.js index 45a2f33..e8f0af8 100644 --- a/test/test.js +++ b/test/test.js @@ -186,6 +186,8 @@ describe("postcss-css-variables", function() { test("should cascade to nested rules", "cascade-on-nested-rules"); + test("should cascade to nested multiple rules", "cascade-and-multiple-on-nested-rules"); + test( "should cascade with calc-expression to nested rules", "cascade-with-calc-expression-on-nested-rules" From fd599ce754e7928ac00b453dee561b45da610371 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 12 Apr 2023 03:07:42 -0500 Subject: [PATCH 20/21] Prepare changelog with #131 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dece1fe..4cd88f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# v0.19.0 - 2023-04-12 + +- Fix nesting edge case with comma separated selectors + - Thank you to [@marapper](https://github.com/marapper) for the [contribution](https://github.com/MadLittleMods/postcss-css-variables/pull/131) + # v0.18.0 - 2021-05-11 - [breaking] Add basic postcss 8 support (older versions of PostCSS no longer compatible) From 0b8e46d84ce975b3f28598cf4f35dc654b88c7f6 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 12 Apr 2023 03:08:33 -0500 Subject: [PATCH 21/21] 0.19.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 568d0e9..35c8403 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-css-variables", - "version": "0.18.0", + "version": "0.19.0", "description": "PostCSS plugin to transform CSS Custom Properties(CSS variables) syntax into a static representation", "keywords": [ "postcss",