├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── package.json ├── src ├── get-variables.js ├── index.js └── parse-variables.js └── test └── index.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | dist 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # v 0.1.2 4 | * fix loader-utils deprecation #11 5 | 6 | # v 0.1.1 7 | * ? 8 | 9 | ## v 0.1.0 10 | * Added support for .sass fileformat variables. 11 | * Loader should not break on empty sass-files. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sass variable loader for webpack 2 | 3 | > Parses your Sass variables and returns an object containing each variable camelCased and the end value as it would be in CSS. 4 | > 5 | > That means full support for Sass' lighten, darken, mix etc. 6 | 7 | **Input:** 8 | ``` scss 9 | $gray-base: #000 !default; 10 | $gray-darker: lighten($gray-base, 13.5%) !default; // #222 11 | $gray-dark: lighten($gray-base, 20%) !default; // #333 12 | $gray: lighten($gray-base, 33.5%) !default; // #555 13 | $gray-light: lighten($gray-base, 46.7%) !default; // #777 14 | $gray-lighter: lighten($gray-base, 93.5%) !default; // #eee 15 | ``` 16 | 17 | **Result:** 18 | ``` javascript 19 | { 20 | grayBase: '#000', 21 | grayDarker: '#222222', 22 | grayDark: '#333333', 23 | gray: '#555555', 24 | grayLight: '#777777', 25 | grayLighter: '#eeeeee' 26 | } 27 | ``` 28 | 29 | ## Installation 30 | 31 | `npm install --save-dev sass-variable-loader` 32 | 33 | ## Usage 34 | 35 | ``` javascript 36 | import variables from 'sass-variable-loader!./_variables.scss'; 37 | // => returns all the variables in _variables.scss as an object with each variable name camelCased 38 | ``` 39 | **Note:** If you've already defined loaders for Sass files in the configuration, you can override the [loader order](https://webpack.github.io/docs/loaders.html#loader-order) by writing `!!sass-variable-loader!./_variables.scss` to disable all loaders specified in the configuration for that module request. 40 | 41 | ## Options 42 | 43 | You can pass options to the loader via [query parameters](http://webpack.github.io/docs/using-loaders.html#query-parameters). 44 | 45 | ### preserveVariableNames 46 | 47 | ``` javascript 48 | import variables from 'sass-variable-loader?preserveVariableNames!./_variables.scss'; 49 | // => returns all the variables in _variables.scss as an object with each variable name left intact 50 | ``` 51 | 52 | ## License 53 | 54 | MIT (http://www.opensource.org/licenses/mit-license.php) 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sass-variable-loader", 3 | "version": "0.1.2", 4 | "description": "Sass variable loader module for webpack", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "clean": "rimraf dist", 8 | "build": "babel ./src -d ./dist", 9 | "test": "mocha --compilers js:babel-core/register", 10 | "lint": "eslint --ext=.js ./src", 11 | "prepush": "npm run lint && npm run test", 12 | "prepublish": "npm run build", 13 | "postpublish": "rimraf dist && git push --follow-tags" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/nordnet/sass-variable-loader" 18 | }, 19 | "keywords": [ 20 | "nordnet", 21 | "sass", 22 | "variable", 23 | "loader" 24 | ], 25 | "author": "Gustaf Zetterlund ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/nordnet/sass-variable-loader/issues" 29 | }, 30 | "homepage": "https://github.com/nordnet/sass-variable-loader/#readme", 31 | "dependencies": { 32 | "loader-utils": "^1.0.3", 33 | "lodash.camelcase": "^4.1.1", 34 | "node-sass": "^4.5.0", 35 | "strip-json-comments": "^2.0.1" 36 | }, 37 | "devDependencies": { 38 | "babel": "^6.3.26", 39 | "babel-cli": "^6.4.0", 40 | "babel-core": "^6.4.0", 41 | "babel-preset-es2015": "^6.3.13", 42 | "chai": "^3.4.1", 43 | "eslint": "^3.1.1", 44 | "eslint-config-airbnb-base": "^5.0.0", 45 | "eslint-plugin-import": "^1.11.1", 46 | "husky": "^0.10.2", 47 | "mocha": "^2.3.4", 48 | "rimraf": "^2.5.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/get-variables.js: -------------------------------------------------------------------------------- 1 | import stripComments from 'strip-json-comments'; 2 | 3 | export default function getVariables(content) { 4 | const variableRegex = /\$(.+):\s+(.+);?/; 5 | const variables = []; 6 | 7 | stripComments(content).split('\n').forEach(line => { 8 | const variable = variableRegex.exec(line); 9 | if (!variable) return; 10 | 11 | const name = variable[1].trim(); 12 | const value = variable[2].replace(/!default|!important/g, '').trim(); 13 | 14 | variables.push({ name, value }); 15 | return; 16 | }); 17 | 18 | return variables; 19 | } 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import loaderUtils from 'loader-utils'; 2 | import getVariables from './get-variables'; 3 | import parseVariables from './parse-variables'; 4 | 5 | module.exports = function sassVariableLoader(content) { 6 | this.cacheable(); // Flag loader as cacheable 7 | const opts = Object.assign({}, loaderUtils.getOptions(this)); 8 | const variables = parseVariables(getVariables(content), opts); 9 | 10 | return `module.exports = ${JSON.stringify(variables)};`; 11 | }; 12 | -------------------------------------------------------------------------------- /src/parse-variables.js: -------------------------------------------------------------------------------- 1 | import sass from 'node-sass'; 2 | import camelCase from 'lodash.camelcase'; 3 | 4 | function constructSassString(variables) { 5 | const asVariables = variables 6 | .map(variable => `$${variable.name}: ${variable.value};`) 7 | .join('\n'); 8 | const asClasses = variables 9 | .map(variable => `.${variable.name} { value: ${variable.value} }`) 10 | .join('\n'); 11 | 12 | return `${asVariables}\n${asClasses}`; 13 | } 14 | 15 | export default function parseVariables(variables, opts = {}) { 16 | const result = sass.renderSync({ 17 | data: constructSassString(variables), 18 | outputStyle: 'compact', 19 | }).css.toString(); 20 | 21 | const parsedVariables = result.split(/\n/) 22 | .filter(line => line && line.length) 23 | .map(variable => { 24 | const [, name, value] = /\.(.+) { value: (.+); }/.exec(variable); 25 | const obj = {}; 26 | 27 | if (opts.preserveVariableNames) { 28 | obj[name] = value; 29 | return obj; 30 | } 31 | 32 | obj[camelCase(name)] = value; 33 | return obj; 34 | }); 35 | 36 | if (!parsedVariables.length) { 37 | return {}; 38 | } 39 | return Object.assign.apply(this, parsedVariables); 40 | } 41 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import getVariables from '../src/get-variables'; 3 | import parseVariables from '../src/parse-variables'; 4 | 5 | context('without comments', function() { 6 | const sass = '$gray-base: #000 !default;\n$gray-darker: lighten($gray-base, 13.5%) !default; // #222\n$gray-dark: lighten($gray-base, 20%) !default; // #333\n$gray: lighten($gray-base, 33.5%) !default; // #555\n$gray-light: lighten($gray-base, 46.7%) !default; // #777\n$gray-lighter: lighten($gray-base, 93.5%) !default; // #eee'; 7 | const variables = getVariables(sass); 8 | 9 | describe('getVariables()', function() { 10 | it('should return an array with 6 items', function() { 11 | expect(variables).to.be.a('array'); 12 | expect(variables).to.have.length(6); 13 | }); 14 | }); 15 | 16 | describe('parseVariables()', function() { 17 | it('should return an object with the key grayBase', function() { 18 | const result = parseVariables(variables); 19 | expect(result).to.be.a('object'); 20 | expect(result).to.include.keys('grayBase'); 21 | }); 22 | }); 23 | 24 | describe('parseVariables({ preserveVariableNames: true })', function() { 25 | it('should return an object with the key gray-base', function() { 26 | const result = parseVariables(variables, { preserveVariableNames: true }); 27 | expect(result).to.be.a('object'); 28 | expect(result).to.include.keys('gray-base'); 29 | }); 30 | }); 31 | }) 32 | 33 | context('with comments', function() { 34 | const sass = `$one: 123; 35 | $x: $one; 36 | // $y: $two; // ERROR - $two not existed, but it's commented` 37 | const variables = getVariables(sass); 38 | 39 | describe('getVariables()', function() { 40 | it('should return an array with 2 items', function() { 41 | expect(variables).to.be.a('array'); 42 | expect(variables).to.have.length(2); 43 | }); 44 | }); 45 | 46 | describe('parseVariables()', function() { 47 | it('should return an object with the key one', function() { 48 | const result = parseVariables(variables); 49 | expect(result).to.be.a('object'); 50 | expect(result).to.include.keys('one'); 51 | }); 52 | it('should not return an object with the key y', function() { 53 | const result = parseVariables(variables); 54 | expect(result).to.be.a('object'); 55 | expect(result).to.not.include.keys('y'); 56 | }); 57 | }); 58 | }) 59 | 60 | context('.sass format', function() { 61 | const sass = `$one: 123 62 | $x: $one 63 | ` 64 | const variables = getVariables(sass); 65 | 66 | describe('getVariables()', function() { 67 | it('should return an array with 2 items', function() { 68 | expect(variables).to.be.a('array'); 69 | expect(variables).to.have.length(2); 70 | }); 71 | }); 72 | 73 | describe('parseVariables()', function() { 74 | it('should return an object with the key one', function() { 75 | const result = parseVariables(variables); 76 | expect(result).to.be.a('object'); 77 | expect(result).to.include.keys('one'); 78 | }); 79 | }); 80 | }) 81 | 82 | context('empty sass-file', function() { 83 | describe('getVariables()', function() { 84 | function testFn() { 85 | const sass = ''; 86 | return parseVariables(getVariables(sass)); 87 | } 88 | 89 | it('should not throw', function() { 90 | expect(testFn).to.not.throw(TypeError); 91 | }); 92 | 93 | it('should be an empty object', function(){ 94 | const variables = testFn(); 95 | expect(variables).to.be.a('object'); 96 | expect(Object.keys(variables)).to.have.length(0); 97 | }) 98 | }); 99 | }) 100 | --------------------------------------------------------------------------------