├── .circleci └── config.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── css-vars.scss ├── gulpfile.js ├── package-lock.json ├── package.json └── test ├── browser ├── highlight.js │ └── highlight.pack.js ├── test.html └── test.js ├── fixtures ├── -playground.scss ├── caveats-native.scss ├── caveats-sass.scss ├── default-native.scss ├── default-sass.scss ├── expected │ ├── caveats-native.expected.css │ ├── caveats-sass.expected.css │ ├── caveats.scss │ ├── default-native.expected.css │ ├── default-sass.expected.css │ ├── default.scss │ ├── non-existing-usage-native.expected.css │ ├── non-existing-usage-sass.expected.css │ ├── non-existing-usage.scss │ ├── redefine-native.expected.css │ ├── redefine-sass.expected.css │ ├── redefine.scss │ ├── usage-outside-selector-native.expected.css │ ├── usage-outside-selector-sass.expected.css │ ├── usage-outside-selector.scss │ ├── use-default-native.expected.css │ ├── use-default-sass.expected.css │ ├── use-default.scss │ ├── with-sass-variables-native.expected.css │ ├── with-sass-variables-sass.expected.css │ └── with-sass-variables.scss ├── non-existing-usage-native.scss ├── non-existing-usage-sass.scss ├── redefine-native.scss ├── redefine-sass.scss ├── usage-outside-selector-native.scss ├── usage-outside-selector-sass.scss ├── use-default-native.scss ├── use-default-sass.scss ├── with-sass-variables-native.scss └── with-sass-variables-sass.scss └── index.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:7.10 11 | 12 | working_directory: ~/repo 13 | 14 | steps: 15 | - checkout 16 | 17 | # Download and cache dependencies 18 | - restore_cache: 19 | keys: 20 | - v1-dependencies-{{ checksum "package.json" }} 21 | # fallback to using the latest cache if no exact match is found 22 | - v1-dependencies- 23 | 24 | - run: npm install 25 | 26 | - save_cache: 27 | paths: 28 | - node_modules 29 | key: v1-dependencies-{{ checksum "package.json" }} 30 | 31 | # run tests! 32 | - run: npm test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | /*.iml 3 | /.idea/ 4 | 5 | # Node 6 | node_modules 7 | npm-debug.log* 8 | 9 | # tests 10 | test/**/*.css 11 | !test/**/*.expected.css -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /*.iml 2 | /.idea/ 3 | test 4 | gulpfile.js 5 | .npmignore 6 | .travis.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "12" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Serg Hospodarets 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # css-vars: Use CSS Custom Properties with Sass [![CircleCI](https://circleci.com/gh/malyw/css-vars.svg?style=svg)](https://circleci.com/gh/malyw/css-vars) [![TravisCI](https://travis-ci.org/malyw/css-vars.png)](https://travis-ci.org/malyw/css-vars) 2 | 3 | ## Installation 4 | 5 | * With npm: `npm install css-vars` 6 | * With yarn: `yarn add css-vars` 7 | * Manually: get [this file](https://raw.githubusercontent.com/malyw/css-vars/master/css-vars.scss) 8 | 9 | Include the main mixin file in your project using an `@import` statement: 10 | 11 | ```scss 12 | @import "[%PATH%]/css-vars/css-vars"; 13 | ``` 14 | 15 | ## Usage 16 | 17 | To declare variables, use `@include css-vars()` 18 | (you can reuse Sass variables): 19 | 20 | ```scss 21 | // $css-vars-use-native: true; 22 | $white-color: #fff; 23 | $base-font-size: 10px; 24 | 25 | @include css-vars(( 26 | --main-color: #000, 27 | --main-bg: $white-color, 28 | --main-font-size: 1.5*$base-font-size, 29 | --padding-top: calc(2vh + 20px) 30 | )); 31 | ``` 32 | 33 | To use variables, use the `var()` function: 34 | 35 | ```scss 36 | body { 37 | color: var(--main-color); 38 | background: var(--main-bg, #f00); 39 | font-size: var(--main-font-size); 40 | padding: var(--padding-top) 0 10px; 41 | } 42 | ``` 43 | 44 | Both these syntaxes are taken from the 45 | [Custom Properties spec](https://drafts.csswg.org/css-variables/) 46 | which [is already implemented in most of the browsers](http://caniuse.com/#feat=css-variables). 47 | 48 | ## CSS output 49 | 50 | The default output from the above is: 51 | 52 | ```css 53 | body { 54 | color: #000; 55 | background: #fff; 56 | font-size: 15px; 57 | padding: calc(2vh + 20px) 0 10px; 58 | } 59 | 60 | ``` 61 | 62 | If **`$css-vars-use-native: true;`** is uncommented, native CSS Custom Properties are used: 63 | 64 | ```css 65 | :root { 66 | --main-color: #000; 67 | --main-bg: #fff; 68 | --main-font-size: 15px; 69 | --padding-top: calc(2vh + 20px); 70 | } 71 | 72 | body { 73 | color: var(--main-color); 74 | background: var(--main-bg, #f00); 75 | font-size: var(--main-font-size); 76 | padding: var(--padding-top) 0 10px; 77 | } 78 | 79 | ``` 80 | 81 | ## Declaration in selectors, reassigning variables 82 | 83 | Variables are declared on the global scope (`$css-vars` map for Sass, `root` for native CSS). 84 | 85 | You can declare variables inside selectors 86 | and, of course, redefine any variable, e.g.: 87 | 88 | ```scss 89 | // declaration outside of any selectors 90 | @include css-vars(( 91 | --line-height: 1, 92 | --main-font-family: (Helvetica, Arial, sans-serif) 93 | )); 94 | 95 | header{ 96 | // declaration inside of a selector 97 | @include css-vars(( 98 | --header-padding: 10px 20px, 99 | --line-height: 1.428571429, 100 | --border-color: rebeccapurple 101 | )); 102 | 103 | padding: var(--header-padding); 104 | line-height: var(--line-height); // 1.428571429 is applied 105 | border: 1px solid var(--other-border-color); 106 | } 107 | ``` 108 | 109 | ### Default values 110 | 111 | `var()` function takes the second param to assign the default value if a variable is not defined: 112 | 113 | ```scss 114 | a::after{ 115 | content: var(--external-link, "external link"); 116 | // "external link" is applied, as --external-link is not defined 117 | } 118 | ``` 119 | 120 | ## Advantages of the mixin 121 | 122 | Usage of the mixin gives the useful debug information: 123 | 124 | - logs when a variable was not assigned but used 125 | - logs when some variable is reassigned 126 | - provides info when a variable is not defined, but there is a default value passed, which is used instead 127 | 128 | This information is helpful in both cases for Sass and CSS variables. 129 | 130 | None browsers so far provide such debug info for CSS custom properties. 131 | 132 | To enable the mixin debug messages output during the Sass compilation, just add the following to your project: 133 | 134 | ```scss 135 | $css-vars-debug-log: true; 136 | ``` 137 | 138 | ## Trigger using of native CSS Custom Properties 139 | 140 | To switch the mixin to use native CSS Custom Properties, just provide: 141 | 142 | ```scss 143 | $css-vars-use-native: true; 144 | ``` 145 | 146 | It's useful when: 147 | * You don't support [browsers without CSS Custom Properties](http://caniuse.com/#feat=css-variables) 148 | * To generate a separate CSS file which will be used for browsers which support them 149 | 150 | E.g. 151 | ```js 152 | const isSupported = window.CSS && window.CSS.supports && 153 | window.CSS.supports('--a', 0); 154 | if(!isSupported){ 155 | removeCss('css-custom-properties.css'); 156 | loadCss('without-css-custom-properties.css'); 157 | } 158 | ``` 159 | 160 | ## Caveats 161 | 162 | There are some caveats, when CSS Custom Properties work in a different way. 163 | 164 | E.g. they cannot be used inside Media Query values, 165 | so the following code will work in Sass, but not in CSS (when you switch): 166 | 167 | ```scss 168 | @include css-vars(( 169 | --tablet: 768px 170 | )); 171 | 172 | @media screen and (min-width: var(--tablet)) { 173 | body { 174 | background: #ff0; 175 | } 176 | } 177 | ``` 178 | 179 | Another differences are regarding the different scopes for CSS and Sass variables. 180 | 181 | E.g. when you use assigned variable to define something in CSS and then reassign it- the used value will be updated. 182 | sass just inline values, so there will be no effect when you change a variable after. 183 | 184 | To debug such cases when you switch just enable the debug messages: 185 | 186 | ```scss 187 | $css-vars-debug-log: true; 188 | ``` 189 | 190 | ## Limitations (**in case of Sass variables**) 191 | 192 | There are some limitation because of the Sass nature: 193 | 194 | - mixin uses the global map to reassign variables, 195 | which may result in a different behavior from Custom Properties when non global variables are used. 196 | 197 | - Before passing a map of variables to the mixin, Sass invokes all the functions in it, together with var(). 198 | As result you cannot reuse variables in one declaration, e.g. this will not work: 199 | 200 | ```scss 201 | @include css-vars(( 202 | --link-color: #4183C4, 203 | --title-hover-color: var(--link-color) 204 | )); 205 | ``` 206 | 207 | To make it work, just split the declaration and the usage: 208 | 209 | ```scss 210 | @include css-vars(( 211 | --link-color: #4183C4, 212 | )); 213 | 214 | @include css-vars(( 215 | --title-hover-color: var(--link-color) 216 | )); 217 | ``` 218 | 219 | - Sass doesn't invoke functions inside `calc()`, so in that case you have to trigger that using Sass interpolation `#{}`: 220 | 221 | ```scss 222 | @include css-vars(( 223 | --link-indent: calc(#{var(--main-vertical-indent)} / 2) 224 | )); 225 | .link{ 226 | width: calc(#{var(--btn-width)} * 2); 227 | margin-bottom: var(--link-indent); 228 | } 229 | ``` 230 | 231 | # License 232 | 233 | MIT 234 | 235 | --- 236 | 237 | ![alt](https://static.hospodarets.com/img/blog/1482761911710817000.png) 238 | -------------------------------------------------------------------------------- /css-vars.scss: -------------------------------------------------------------------------------- 1 | //// VARIABLES //// 2 | 3 | // global map to be filled via variables 4 | $css-vars: (); 5 | 6 | // the variable may be set to "true" anywhere in the code, 7 | // so native CSS custom properties will be used instead of the Sass global map 8 | $css-vars-use-native: false !default; 9 | 10 | // enables the output of debug messages 11 | $css-vars-debug-log: false !default; 12 | 13 | //// FUNCTIONS //// 14 | 15 | /// 16 | // Assigns a variable to the global map 17 | /// 18 | @function _cssVarAssign($varName: null, $varValue: null) { 19 | // CHECK PARAMS 20 | @if ($varName==null) { 21 | @error "Variable name is expected, instead got: null"; 22 | } 23 | @if ($varValue==null) { 24 | @error "Variable value is expected, instead got: null"; 25 | } 26 | 27 | // assign to the global map 28 | @if ($css-vars-debug-log and map-get($css-vars, $varName)) { 29 | @debug "'#{$varName}' variable is reassigned"; 30 | } 31 | 32 | @return map-merge($css-vars, ($varName: $varValue)); 33 | } 34 | 35 | /// 36 | // Emulates var() CSS native function behavior 37 | // 38 | // $args[0] {String} "--" + variable name 39 | // [$args[1]] Optional default value if variable is not assigned yet 40 | // 41 | // E.G.: 42 | // color: var(--main-color); 43 | // background: var(--main-bg, green); 44 | /// 45 | @function var($args...) { 46 | // CHECK PARAMS 47 | @if (length($args)==0) { 48 | @error "Variable name is expected to be passed to the var() function"; 49 | } 50 | @if (str-length(nth($args, 1)) < 2 or str-slice(nth($args, 1), 0, 2) != '--') { 51 | @error "Variable name is expected to start from '--'"; 52 | } 53 | 54 | // PROCESS 55 | $varName: nth($args, 1); 56 | $varValue: map-get($css-vars, $varName); 57 | 58 | @if ($css-vars-debug-log or not $css-vars-use-native) { // Sass or debug 59 | @if ($varValue==null) { // variable is not provided so far 60 | @if (length($args)==2) { // the default value is passed 61 | @if ($css-vars-debug-log) { 62 | @debug "Provided default value is used for the variable: '#{$varName}'"; 63 | } 64 | $varValue: nth($args, 2); 65 | } @else if ($css-vars-debug-log) { 66 | @debug "Variable '#{$varName}' is not assigned"; 67 | @if (not $css-vars-use-native) { 68 | @debug "The 'var(#{$varName}...)' usage will be skipped in the output CSS"; 69 | } 70 | } 71 | } 72 | } 73 | 74 | @if ($css-vars-use-native) { // CSS variables 75 | // Native CSS: don't process function in case of native 76 | @return unquote('var(' + $args + ')'); 77 | } @else { 78 | // Sass: return value from the map 79 | @return $varValue; 80 | } 81 | } 82 | 83 | //// MIXIN //// 84 | 85 | /// 86 | // CSS mixin to provide variables 87 | // E.G.: 88 | // @include css-vars(( 89 | // --color: rebeccapurple, 90 | // --height: 68px, 91 | // --margin-top: calc(2vh + 20px) 92 | // )); 93 | /// 94 | @mixin css-vars($varMap: null) { 95 | // CHECK PARAMS 96 | @if ($varMap==null) { 97 | @error "Map of variables is expected, instead got: null"; 98 | } 99 | @if (type_of($varMap)!=map) { 100 | @error "Map of variables is expected, instead got another type passed: #{type_of($varMap)}"; 101 | } 102 | 103 | // PROCESS 104 | @if ($css-vars-debug-log or not $css-vars-use-native) { // Sass or debug 105 | // merge variables and values to the global map (provides no output) 106 | @each $varName, $varValue in $varMap { 107 | $css-vars: _cssVarAssign($varName, $varValue) !global; // store in global variable 108 | } 109 | } 110 | 111 | @if ($css-vars-use-native) { // CSS variables 112 | // Native CSS: assign CSS custom properties to the global scope 113 | @at-root :root { 114 | @each $varName, $varValue in $varMap { 115 | @if (type_of($varValue)==string) { 116 | #{$varName}: $varValue // to prevent quotes interpolation 117 | } @else { 118 | #{$varName}: #{$varValue} 119 | } 120 | } 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const sass = require('gulp-sass')(require('node-sass')); 3 | 4 | gulp.task('sass:test', () => { 5 | return gulp.src('./test/fixtures/*.scss') 6 | .pipe(sass({ 7 | outputStyle: 'expanded' 8 | }).on('error', sass.logError)) 9 | .pipe(gulp.dest('./test/fixtures/')); 10 | }); 11 | 12 | gulp.task('sass:watch', () => { 13 | gulp.watch( 14 | [ 15 | './test/fixtures/**/*.scss', 16 | './*.scss' 17 | ], 18 | gulp.series('sass:test') 19 | ); 20 | }); 21 | 22 | // PLAYGROUND 23 | gulp.task('sass:playground', () => { 24 | return gulp.src('./test/fixtures/-playground.scss') 25 | .pipe( 26 | sass({ 27 | outputStyle: 'expanded' 28 | }).on('error', sass.logError) 29 | ) 30 | .pipe(gulp.dest('./test/fixtures/')); 31 | }); 32 | 33 | gulp.task('playground', () => { 34 | gulp.watch( 35 | [ 36 | './test/fixtures/-playground.scss', 37 | './css-vars.scss' 38 | ], 39 | gulp.series('sass:playground') 40 | ); 41 | }); 42 | 43 | gulp.task('default', gulp.series('sass:test', 'sass:watch')); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-vars", 3 | "version": "2.4.0", 4 | "main": "css-vars.scss", 5 | "description": "Use CSS Custom Properties with Sass", 6 | "author": { 7 | "name" : "Serg Hospodarets", 8 | "email" : "shospodarets@gmail.com", 9 | "url" : "https://hospodarets.com/" 10 | }, 11 | "license": "MIT", 12 | "devDependencies": { 13 | "gulp": "^4.0.2", 14 | "gulp-sass": "^5.1.0", 15 | "node-glob": "^1.2.0", 16 | "node-sass": "^7.0.1", 17 | "tape": "^5.5.2" 18 | }, 19 | "repository": "https://github.com/malyw/css-vars", 20 | "bugs": { 21 | "url": "https://github.com/malyw/css-vars/issues" 22 | }, 23 | "keywords": [ 24 | "sass", 25 | "scss", 26 | "css", 27 | "variables", 28 | "custom properties" 29 | ], 30 | "scripts": { 31 | "gulp": "gulp", 32 | "test": "gulp sass:test && tape test", 33 | "playground": "gulp playground" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/browser/highlight.js/highlight.pack.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.8.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/[&<>]/gm,function(e){return I[e]})}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return R(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||R(i))return i}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===s);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):E(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"===e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function g(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function h(e,n,t,r){var a=r?"":y.classPrefix,i='',i+n+o}function p(){var e,t,r,a;if(!E.k)return n(B);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(B);r;)a+=n(B.substr(t,r.index-t)),e=g(E,r),e?(M+=e[1],a+=h(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(B);return a+n(B.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!x[E.sL])return n(B);var t=e?l(E.sL,B,!0,L[E.sL]):f(B,E.sL.length?E.sL:void 0);return E.r>0&&(M+=t.r),e&&(L[E.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){k+=null!=E.sL?d():p(),B=""}function v(e){k+=e.cN?h(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(B+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?B+=n:(t.eB&&(B+=n),b(),t.rB||t.eB||(B=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?B+=n:(a.rE||a.eE||(B+=n),b(),a.eE&&(B=n));do E.cN&&(k+=C),E.skip||(M+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return B+=n,n.length||1}var N=R(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var w,E=i||N,L={},k="";for(w=E;w!==N;w=w.parent)w.cN&&(k=h(w.cN,"",!0)+k);var B="",M=0;try{for(var I,j,O=0;;){if(E.t.lastIndex=O,I=E.t.exec(t),!I)break;j=m(t.substr(O,I.index-O),I[0]),O=I.index+j}for(m(t.substr(O)),w=E;w.parent;w=w.parent)w.cN&&(k+=C);return{r:M,value:k,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function f(e,t){t=t||y.languages||E(x);var r={r:0,value:n(e)},a=r;return t.filter(R).forEach(function(n){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function g(e){return y.tabReplace||y.useBR?e.replace(M,function(e,n){return y.useBR&&"\n"===e?"
":y.tabReplace?n.replace(/\t/g,y.tabReplace):void 0}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n,t,r,o,s,p=i(e);a(p)||(y.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,s=n.textContent,r=p?l(p,s,!0):f(s),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),s)),r.value=g(r.value),e.innerHTML=r.value,e.className=h(e.className,p,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function d(e){y=o(y,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");w.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function N(){return E(x)}function R(e){return e=(e||"").toLowerCase(),x[e]||x[L[e]]}var w=[],E=Object.keys,x={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",y={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},I={"&":"&","<":"<",">":">"};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=R,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("scss",function(e){var t="[a-zA-Z-][a-zA-Z0-9_-]*",i={cN:"variable",b:"(\\$"+t+")\\b"},r={cN:"number",b:"#[0-9A-Fa-f]+"};({cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:!0,i:"[^\\s]",starts:{eW:!0,eE:!0,c:[r,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"meta",b:"!important"}]}});return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,{cN:"selector-id",b:"\\#[A-Za-z0-9_-]+",r:0},{cN:"selector-class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"selector-attr",b:"\\[",e:"\\]",i:"$"},{cN:"selector-tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",r:0},{b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},i,{cN:"attribute",b:"\\b(z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{b:":",e:";",c:[i,r,e.CSSNM,e.QSM,e.ASM,{cN:"meta",b:"!important"}]},{b:"@",e:"[{;]",k:"mixin include extend for if else each while charset import debug media page content font-face namespace warn",c:[i,e.QSM,e.ASM,r,e.CSSNM,{b:"\\s[A-Za-z0-9_.-]+",r:0}]}]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}}); -------------------------------------------------------------------------------- /test/browser/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test in browser 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

Applied styles:

14 |
15 | 31 |
32 | 33 | 34 |

Visual test in the browser:

35 | 36 |
37 | other 38 |
39 | 40 |

Code:

41 | 42 |
43 |
44 |
45 |

Source:

46 |
47 |
48 |
49 | 50 |
51 |
52 |

Native variables:

53 |
54 |
55 |
56 |

Sass variables:

57 |
58 |
59 |
60 |
61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /test/browser/test.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const cssFileNamesSelect = document.querySelector('.css-file-names'); 3 | const cssLink = document.querySelector('.css-link'); 4 | const cssSourceEl = document.querySelector('.css-source'); 5 | const cssNativeEl = document.querySelector('.css-native'); 6 | const cssSassEl = document.querySelector('.css-sass'); 7 | 8 | 9 | function applyTest(cssFileName) { 10 | const cssGeneratedPath = '../fixtures'; 11 | const cssExpectedPath = '../fixtures/expected'; 12 | const cssToApplyPath = `${cssGeneratedPath}/${cssFileName}.css`; 13 | const testName = cssFileName.replace(/-native$/, '').replace(/-sass$/, ''); 14 | 15 | const cssNativePath = `${cssGeneratedPath}/${testName}-native.css`; 16 | const cssSassPath = `${cssGeneratedPath}/${testName}-sass.css`; 17 | const cssSourcePath = `${cssExpectedPath}/${testName}.scss`; 18 | 19 | cssLink.href = cssToApplyPath; 20 | 21 | // show the code 22 | fetch(cssSourcePath) 23 | .then((res) => res.text()) 24 | .then((text) => { 25 | cssSourceEl.innerHTML = hljs.highlight("scss", text).value; 26 | }); 27 | 28 | fetch(cssNativePath) 29 | .then((res) => res.text()) 30 | .then((text) => { 31 | cssNativeEl.innerHTML = hljs.highlight("css", text).value; 32 | }); 33 | 34 | fetch(cssSassPath) 35 | .then((res) => res.text()) 36 | .then((text) => { 37 | cssSassEl.innerHTML = hljs.highlight("css", text).value; 38 | }); 39 | } 40 | 41 | function getSelectedCssFileName() { 42 | return cssFileNamesSelect.options[cssFileNamesSelect.selectedIndex].innerHTML; 43 | } 44 | 45 | cssFileNamesSelect.addEventListener('change', function () { 46 | applyTest(getSelectedCssFileName()); 47 | }); 48 | 49 | // INIT 50 | applyTest(getSelectedCssFileName()); 51 | }()); -------------------------------------------------------------------------------- /test/fixtures/-playground.scss: -------------------------------------------------------------------------------- 1 | //$css-vars-use-native: true; 2 | $css-vars-debug-log: true; 3 | 4 | @import "../../css-vars"; 5 | 6 | // PLAY HERE 7 | 8 | //@include css-vars(( 9 | // --bg: #fff 10 | //)); 11 | // 12 | //.test{ 13 | // background-color: var(--non-existing, #f00); 14 | //} -------------------------------------------------------------------------------- /test/fixtures/caveats-native.scss: -------------------------------------------------------------------------------- 1 | $css-vars-use-native: true; 2 | @import "../../css-vars"; 3 | @import "expected/caveats"; -------------------------------------------------------------------------------- /test/fixtures/caveats-sass.scss: -------------------------------------------------------------------------------- 1 | @import "../../css-vars"; 2 | @import "expected/caveats"; -------------------------------------------------------------------------------- /test/fixtures/default-native.scss: -------------------------------------------------------------------------------- 1 | $css-vars-use-native: true; 2 | @import "../../css-vars"; 3 | @import "expected/default"; -------------------------------------------------------------------------------- /test/fixtures/default-sass.scss: -------------------------------------------------------------------------------- 1 | @import "../../css-vars"; 2 | @import "expected/default"; -------------------------------------------------------------------------------- /test/fixtures/expected/caveats-native.expected.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --tablet: 768px; 3 | --bg: #aaa; 4 | } 5 | 6 | @media screen and (min-width: var(--tablet)) { 7 | div { 8 | background: #ff0; 9 | } 10 | } 11 | 12 | body { 13 | background: var(--bg); 14 | } 15 | 16 | :root { 17 | --bg: #555; 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/expected/caveats-sass.expected.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 768px) { 2 | div { 3 | background: #ff0; 4 | } 5 | } 6 | 7 | body { 8 | background: #aaa; 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/expected/caveats.scss: -------------------------------------------------------------------------------- 1 | @include css-vars(( 2 | --tablet: 768px, 3 | --bg: #aaa 4 | )); 5 | 6 | @media screen and (min-width: var(--tablet)) { 7 | div { 8 | background: #ff0; 9 | } 10 | } 11 | 12 | body { 13 | background: var(--bg); 14 | } 15 | 16 | @include css-vars(( 17 | --bg: #555 18 | )); -------------------------------------------------------------------------------- /test/fixtures/expected/default-native.expected.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-color: #4d4e53; 3 | --main-bg: white; 4 | } 5 | 6 | body { 7 | color: var(--main-color); 8 | background: var(--main-bg); 9 | } 10 | 11 | .other { 12 | border: 1px solid var(--other-border-color); 13 | height: var(--other-height); 14 | padding: var(--other-padding); 15 | line-height: var(--other-line-height); 16 | transition-duration: var(--transition-duration); 17 | margin-top: var(--margin-top); 18 | } 19 | 20 | :root { 21 | --other-border-color: rebeccapurple; 22 | --other-height: 68px; 23 | --other-padding: 10px 20px; 24 | --other-line-height: 1.42857; 25 | --transition-duration: 0.35s; 26 | --external-link: "external link"; 27 | --margin-top: calc(2vh + 20px); 28 | } 29 | 30 | .other::after { 31 | content: var(--external-link); 32 | } 33 | -------------------------------------------------------------------------------- /test/fixtures/expected/default-sass.expected.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #4d4e53; 3 | background: white; 4 | } 5 | 6 | .other { 7 | border: 1px solid rebeccapurple; 8 | height: 68px; 9 | padding: 10px 20px; 10 | line-height: 1.42857; 11 | transition-duration: 0.35s; 12 | margin-top: calc(2vh + 20px); 13 | } 14 | 15 | .other::after { 16 | content: "external link"; 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/expected/default.scss: -------------------------------------------------------------------------------- 1 | @include css-vars(( 2 | --main-color:#4d4e53, 3 | --main-bg:rgb(255, 255, 255) 4 | )); 5 | 6 | body { 7 | color: var(--main-color); 8 | background: var(--main-bg); 9 | } 10 | 11 | .other { 12 | @include css-vars(( 13 | --other-border-color: rebeccapurple, 14 | --other-height: 68px, 15 | --other-padding: 10px 20px, 16 | --other-line-height: 1.428571429, 17 | --transition-duration: .35s, 18 | --external-link: "external link", 19 | --margin-top: calc(2vh + 20px) 20 | )); 21 | 22 | border: 1px solid var(--other-border-color); 23 | height: var(--other-height); 24 | padding: var(--other-padding); 25 | line-height: var(--other-line-height); 26 | transition-duration: var(--transition-duration); 27 | margin-top: var(--margin-top); 28 | } 29 | 30 | .other::after { 31 | content: var(--external-link); 32 | } -------------------------------------------------------------------------------- /test/fixtures/expected/non-existing-usage-native.expected.css: -------------------------------------------------------------------------------- 1 | html { 2 | color: var(--main-colo); 3 | } 4 | 5 | :root { 6 | --main-color: green; 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/expected/non-existing-usage-sass.expected.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shospodarets/css-vars/a3ac2f75ca97b33873af1b7eb357e656b558b7ce/test/fixtures/expected/non-existing-usage-sass.expected.css -------------------------------------------------------------------------------- /test/fixtures/expected/non-existing-usage.scss: -------------------------------------------------------------------------------- 1 | html { 2 | @include css-vars(( 3 | --main-color:green 4 | )); 5 | color: var(--main-colo); 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/expected/redefine-native.expected.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-color: red; 3 | } 4 | 5 | div { 6 | color: var(--main-color); 7 | } 8 | 9 | :root { 10 | --main-color: yellow; 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/expected/redefine-sass.expected.css: -------------------------------------------------------------------------------- 1 | div { 2 | color: yellow; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/expected/redefine.scss: -------------------------------------------------------------------------------- 1 | @include css-vars(( 2 | --main-color:red 3 | )); 4 | 5 | div { 6 | @include css-vars(( 7 | --main-color:yellow 8 | )); 9 | color: var(--main-color); 10 | } -------------------------------------------------------------------------------- /test/fixtures/expected/usage-outside-selector-native.expected.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-color: green; 3 | --main-bg: yellow; 4 | } 5 | 6 | body { 7 | color: var(--main-color); 8 | background: var(--main-bg); 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/expected/usage-outside-selector-sass.expected.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: green; 3 | background: yellow; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/expected/usage-outside-selector.scss: -------------------------------------------------------------------------------- 1 | @include css-vars(( 2 | --main-color:green, 3 | --main-bg:yellow 4 | )); 5 | 6 | body { 7 | color: var(--main-color); 8 | background: var(--main-bg); 9 | } -------------------------------------------------------------------------------- /test/fixtures/expected/use-default-native.expected.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: var(--main-color, yellow); 3 | background: var(--main-bg, green); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/expected/use-default-sass.expected.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: yellow; 3 | background: green; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/expected/use-default.scss: -------------------------------------------------------------------------------- 1 | body { 2 | color: var(--main-color, yellow); 3 | background: var(--main-bg, green); 4 | } -------------------------------------------------------------------------------- /test/fixtures/expected/with-sass-variables-native.expected.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-color: #000; 3 | --main-bg: #fff; 4 | --main-font-size: 15px; 5 | --padding-top: calc(2vh + 20px); 6 | } 7 | 8 | body { 9 | color: var(--main-color); 10 | background: var(--main-bg, #f00); 11 | font-size: var(--main-font-size); 12 | padding: var(--padding-top) 0 10px; 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/expected/with-sass-variables-sass.expected.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #000; 3 | background: #fff; 4 | font-size: 15px; 5 | padding: calc(2vh + 20px) 0 10px; 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/expected/with-sass-variables.scss: -------------------------------------------------------------------------------- 1 | $white-color: #fff; 2 | $base-font-size: 10px; 3 | 4 | @include css-vars(( 5 | --main-color: #000, 6 | --main-bg: $white-color, 7 | --main-font-size: 1.5*$base-font-size, 8 | --padding-top: calc(2vh + 20px) 9 | )); 10 | 11 | body { 12 | color: var(--main-color); 13 | background: var(--main-bg, #f00); 14 | font-size: var(--main-font-size); 15 | padding: var(--padding-top) 0 10px; 16 | } -------------------------------------------------------------------------------- /test/fixtures/non-existing-usage-native.scss: -------------------------------------------------------------------------------- 1 | $css-vars-use-native: true; 2 | @import "../../css-vars"; 3 | @import "expected/non-existing-usage"; -------------------------------------------------------------------------------- /test/fixtures/non-existing-usage-sass.scss: -------------------------------------------------------------------------------- 1 | @import "../../css-vars"; 2 | @import "expected/non-existing-usage"; -------------------------------------------------------------------------------- /test/fixtures/redefine-native.scss: -------------------------------------------------------------------------------- 1 | $css-vars-use-native: true; 2 | @import "../../css-vars"; 3 | @import "expected/redefine"; -------------------------------------------------------------------------------- /test/fixtures/redefine-sass.scss: -------------------------------------------------------------------------------- 1 | @import "../../css-vars"; 2 | @import "expected/redefine"; -------------------------------------------------------------------------------- /test/fixtures/usage-outside-selector-native.scss: -------------------------------------------------------------------------------- 1 | $css-vars-use-native: true; 2 | @import "../../css-vars"; 3 | @import "expected/usage-outside-selector"; -------------------------------------------------------------------------------- /test/fixtures/usage-outside-selector-sass.scss: -------------------------------------------------------------------------------- 1 | @import "../../css-vars"; 2 | @import "expected/usage-outside-selector"; -------------------------------------------------------------------------------- /test/fixtures/use-default-native.scss: -------------------------------------------------------------------------------- 1 | $css-vars-use-native: true; 2 | @import "../../css-vars"; 3 | @import "expected/use-default"; -------------------------------------------------------------------------------- /test/fixtures/use-default-sass.scss: -------------------------------------------------------------------------------- 1 | @import "../../css-vars"; 2 | @import "expected/use-default"; -------------------------------------------------------------------------------- /test/fixtures/with-sass-variables-native.scss: -------------------------------------------------------------------------------- 1 | $css-vars-use-native: true; 2 | @import "../../css-vars"; 3 | @import "expected/with-sass-variables"; -------------------------------------------------------------------------------- /test/fixtures/with-sass-variables-sass.scss: -------------------------------------------------------------------------------- 1 | @import "../../css-vars"; 2 | @import "expected/with-sass-variables"; -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test compares that all generated files by Sass (actual) 3 | * are equal to the expected ones 4 | * 5 | * In short: 6 | * test/fixtures/expected/*.expected.css === test/fixtures/*.css 7 | */ 8 | 9 | /** REQUIRES **/ 10 | const fs = require('fs'); 11 | const tape = require('tape'); 12 | const glob = require('glob'); 13 | 14 | /** METHODS **/ 15 | 16 | /** 17 | * Gets the expected and actual files and runs tests to compare them 18 | */ 19 | function compareFixtures() { 20 | const expectedDir = 'test/fixtures/expected/'; 21 | 22 | // get corresponding lists of expected and actual files 23 | const expectedFiles = glob.sync(`${expectedDir}*.expected.css`); 24 | const actualFiles = getActualFilesFromExpected(expectedFiles); 25 | 26 | expectedFiles.forEach((expectedFile, i) => {// run test for the each pair 27 | testFilesAreEqual(expectedFile, actualFiles[i]) 28 | }); 29 | if (!expectedFiles.length) { 30 | console.log('No files found to be tested'); 31 | } 32 | } 33 | 34 | /** 35 | * Read files and compares them via test 36 | * @param expectedPath {Array} 37 | * @param actualPath {Array} 38 | */ 39 | function testFilesAreEqual(expectedPath, actualPath) { 40 | const expected = fs.readFileSync(expectedPath, 'utf8'); 41 | const actual = fs.readFileSync(actualPath, 'utf8'); 42 | 43 | tape('expected === scss output', (t) => { 44 | t.equal(expected, actual, actualPath); 45 | t.end(); 46 | }); 47 | } 48 | 49 | /** 50 | * Helper method to get an array of actual files paths 51 | * @param expectedFiles {Array} 52 | * @returns {Array} 53 | */ 54 | function getActualFilesFromExpected(expectedFiles) { 55 | return expectedFiles.map((expectedFile) => { 56 | return expectedFile 57 | .replace('/expected', '')// remove '/expected' form path 58 | .replace('.expected', '');// remove '.expected' 59 | }); 60 | } 61 | 62 | /** RUN **/ 63 | compareFixtures(); --------------------------------------------------------------------------------