diff --git a/CHANGELOG.md b/CHANGELOG.md index 481bd2c..4cd88f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,139 +1,151 @@ -# v0.14.0 - 2019-11-24 +# v0.19.0 - 2023-04-12 - - 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 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.13.0 - 2019-6-17 +# v0.18.0 - 2021-05-11 - - 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) +- [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.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) +# 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.11.0 - 2018-10-9 +# v0.16.0 - 2020-04-24 - - Fix JS-defined variables using `isImportant`, https://github.com/MadLittleMods/postcss-css-variables/pull/87 +- 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-04-24 -# v0.10.0 - 2018-9-25 +- 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) - - 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.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) -# v0.9.0 - 2018-6-26 +# v0.13.0 - 2019-06-17 - - 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) +- 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-02-21 -# v0.8.1 - 2018-3-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) - - 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.11.0 - 2018-10-09 +- Fix JS-defined variables using `isImportant`, https://github.com/MadLittleMods/postcss-css-variables/pull/87 -# v0.8.0 - 2017-8-8 +# v0.10.0 - 2018-09-25 - - 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) +- 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-06-26 -# v0.7.0 - 2017-3-12 +- 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) - - 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.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.6.0 - 2016-9-23 +# v0.8.0 - 2017-08-08 - - 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) +- 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-03-12 -# v0.5.2 - 2016-8-24 +- 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) - - 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.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.1 - 2015-10-24 +# v0.5.2 - 2016-08-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 [#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.0 - 2015-9-12 - - - Upgrade to PostCSS v5. Fix [#20](https://github.com/MadLittleMods/postcss-css-variables/issues/20) +# v0.5.1 - 2015-10-24 -# v0.4.0 - 2015-7-2 +- 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 [#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.5.0 - 2015-09-12 +- Upgrade to PostCSS v5. Fix [#20](https://github.com/MadLittleMods/postcss-css-variables/issues/20) +# v0.4.0 - 2015-07-02 -# v0.3.9 - 2015-6-29 +- 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). - - Remove `opts` global leak. Fix [#13](https://github.com/MadLittleMods/postcss-css-variables/issues/13) +# 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` +- 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 +- 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 +- 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 +- 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 +- 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` +- 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) +- 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 +- 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). +- 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) +- 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 +- First release diff --git a/README.md b/README.md index eebf01c..85f2185 100644 --- a/README.md +++ b/README.md @@ -7,75 +7,61 @@ ### Install ``` -npm install postcss-css-variables --save-dev +npm install postcss 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,31 +240,29 @@ 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. -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` @@ -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,9 +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. +- `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: `{}`) @@ -373,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/index.js b/index.js index abcfe8d..4174270 100644 --- a/index.js +++ b/index.js @@ -6,282 +6,289 @@ // 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 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); - - // 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); - } - - // 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' - }); - }); - - // Remove the variable declaration because they are pretty much useless after we resolve them - if(!opts.preserve) { - decl.remove(); - } - // Or we can also just show the computed value used for that variable - else if(opts.preserve === '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.walkRules(function(rule) { - 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 is actually comma separted(selectors > 1) - if(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); - } - /* */ +module.exports = (options = {}) => { + var opts = extend({}, defaults, options); + + // Work with opts here + + 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 + }); + + 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) { + 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) { + // Resolve the declarations + rule.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); + } + /* */ + } + }; +}; - }; -}); +module.exports.postcss = true; 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/lib/resolve-value.js b/lib/resolve-value.js index 1954758..60b6789 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 (/(?:^|[^\w-])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/package.json b/package.json index b92a5e7..35c8403 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-css-variables", - "version": "0.14.0", + "version": "0.19.0", "description": "PostCSS plugin to transform CSS Custom Properties(CSS variables) syntax into a static representation", "keywords": [ "postcss", @@ -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 ." 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/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/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 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/fixtures/preserve-variables-conditionally.css b/test/fixtures/preserve-variables-conditionally.css new file mode 100644 index 0000000..33cb840 --- /dev/null +++ b/test/fixtures/preserve-variables-conditionally.css @@ -0,0 +1,21 @@ +:root { + --no-preserve: #0000ff; + --color-foo: #00ff00; + --color-bar: var(--color-two); +} + +.before { + prop: before; + color: var(--no-preserve); +} + +.after { + color: var(--color-foo); + prop: after; +} + +.before-and-after { + 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 new file mode 100644 index 0000000..78a9d09 --- /dev/null +++ b/test/fixtures/preserve-variables-conditionally.expected.css @@ -0,0 +1,22 @@ +:root { + --color-foo: #00ff00; + --color-bar: var(--color-two); +} + +.before { + prop: before; + color: #0000ff; +} + +.after { + color: #00ff00; + color: var(--color-foo); + prop: after; +} + +.before-and-after { + prop: before; + color: #ff0000; + color: var(--missing, #ff0000); + otherprop: after; +} diff --git a/test/test.js b/test/test.js index b0867b0..e8f0af8 100644 --- a/test/test.js +++ b/test/test.js @@ -1,288 +1,386 @@ -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 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' } - ); - }); - - - 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 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("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" + ); + + 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" + ); + }); });