├── .travis.yml ├── .npmignore ├── .editorconfig ├── CHANGELOG.md ├── package.json ├── LICENSE ├── test.js ├── README.md ├── .gitignore └── index.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5" 4 | - "4" 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .idea 3 | postcss_vars* 4 | 5 | node_modules/ 6 | npm-debug.log 7 | 8 | test.js 9 | .travis.yml 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{json,yml}] 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.0 2 | *2016.04.27* 3 | 4 | - Move variable collection from array to object. That prevent duplicates entries on css mistakes. 5 | - Escape strings. 6 | - Add 'use strict' for js export. 7 | 8 | # 0.1.0 9 | *2016.04.13* 10 | 11 | - Initial version 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-export-vars", 3 | "version": "0.2.0", 4 | "description": "PostCSS plugin to export variables as JS constants or JSON.", 5 | "keywords": [ 6 | "postcss", 7 | "css", 8 | "postcss-plugin", 9 | "javascript", 10 | "vars", 11 | "export", 12 | "constant" 13 | ], 14 | "author": "Jan Nahody ", 15 | "license": "MIT", 16 | "repository": "nahody/postcss-export-vars", 17 | "bugs": { 18 | "url": "https://github.com/nahody/postcss-export-vars/issues" 19 | }, 20 | "homepage": "https://github.com/nahody/postcss-export-vars", 21 | "dependencies": { 22 | "lodash": "^4.10.0", 23 | "postcss": "^5.0.16" 24 | }, 25 | "devDependencies": { 26 | "ava": "^0.12.0", 27 | "eslint": "^2.1.0", 28 | "eslint-config-postcss": "^2.0.0" 29 | }, 30 | "scripts": { 31 | "test": "ava && eslint *.js" 32 | }, 33 | "eslintConfig": { 34 | "extends": "eslint-config-postcss/es5", 35 | "rules": { 36 | "max-len": "off", 37 | "no-unused-vars": "off", 38 | "no-cond-assign": "off", 39 | "quotes": "off" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2016 Jan Nahody 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | import test from 'ava'; 3 | import fs from 'fs'; 4 | 5 | import plugin from './'; 6 | 7 | const dummyCSS = ` 8 | :root { 9 | --var-color1: rgb(255, 255, 200); 10 | --var-padding: 10px; 11 | --var-padding2: var(--var-padding); 12 | } 13 | 14 | $var-color2: rgb(30, 100, 255); 15 | $var-margin: 20px; 16 | $var-margin2: $var-margin; 17 | `; 18 | 19 | const dummyJSON = { varColor1: 'rgb(255, 255, 200)', varPadding :'10px', varPadding2 :'10px', varColor2: 'rgb(30, 100, 255)', varMargin :'20px', varMargin2 :'20px' }; 20 | const dummyLimitedJSON = { varColor1: 'rgb(255, 255, 200)', varColor2: 'rgb(30, 100, 255)' }; 21 | const dummyJS = `'use strict';\nconst varColor1 = 'rgb(255, 255, 200)';\nconst varPadding = '10px';\nconst varPadding2 = '10px';\nconst varColor2 = 'rgb(30, 100, 255)';\nconst varMargin = '20px';\nconst varMargin2 = '20px';\n`; 22 | 23 | function run(t, input, output, opts = { }) { 24 | return postcss([ plugin(opts) ]).process(input) 25 | .then( result => { 26 | t.same(fs.readFileSync(opts.file, 'utf8'), output); 27 | t.same(result.warnings().length, 0); 28 | }); 29 | } 30 | 31 | /* Write tests here */ 32 | test('Test JSON output', t => { 33 | return run(t, dummyCSS, JSON.stringify(dummyJSON), { file: './postcss_vars.json', type: 'json' }); 34 | }); 35 | 36 | test('Test JS output', t => { 37 | return run(t, dummyCSS, dummyJS, { file: './postcss_vars.js', type: 'js' }); 38 | }); 39 | 40 | test('Test limited JSON output', t => { 41 | return run(t, dummyCSS, JSON.stringify(dummyLimitedJSON), { file: './postcss_vars_limited.json', type: 'json', match: ['color'] }); 42 | }); 43 | 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PostCSS Export Vars [![Build Status][ci-img]][ci] 2 | 3 | [PostCSS] plugin to export variables definitions as JSON or JS constants. 4 | It detect "custom variables" and SASS style variables and export them to JSON or JS file. 5 | Variable that will refer to other variables are also dissolved and only the value is adopted. 6 | 7 | This plugin was created for a project, witch should use defined css colors also for highcharts. 8 | All suggestions are welcome! 9 | 10 | [PostCSS]: https://github.com/postcss/postcss 11 | [ci-img]: https://travis-ci.org/nahody/postcss-export-vars.svg 12 | [ci]: https://travis-ci.org/nahody/postcss-export-vars 13 | 14 | ## Options 15 | All options are mandatory. 16 | 17 | **file** 18 | Export file name. Extension is not required and will be set automatically. 19 | Default file name is ```postcss_vars``` 20 | 21 | **type** 22 | Export format. Default is JSON. Possible values are ```json``` and ```js```. 23 | 24 | **match** 25 | The match option is an array of string the property name / variable must contain to limit the result for a specific group of variables. 26 | When option is missing or an empty array, all properties / variables are taken. 27 | 28 | ```json 29 | { 30 | file: 'my-vars', 31 | type: 'js', 32 | match: ['-color', 'font'] 33 | } 34 | ``` 35 | 36 | ## Example 37 | 38 | **CSS Source:** 39 | ```css 40 | :root { 41 | --var-color1: rgb(255, 255, 200); 42 | --var-padding: 10px; 43 | --var-padding-sub: var(--var-padding); 44 | } 45 | 46 | $var-color2: rgb(30, 100, 255); 47 | $var-margin: 20px; 48 | ``` 49 | 50 | **JSON Result:** 51 | ```JSON 52 | { 53 | "varColor1": "rgb(255, 255, 200)", 54 | "varPadding": "10px", 55 | "varPadding-sub": "10px", 56 | "varColor2": "rgb(30, 100, 255)", 57 | "varMargin": "20px" 58 | } 59 | ``` 60 | 61 | **JavaScript Result:** 62 | ```JS 63 | const varColor1 = 'rgb(255, 255, 200)'; 64 | const varPadding = '10px'; 65 | const varPadding-sub = '10px'; 66 | const varColor2 = 'rgb(30, 100, 255)'; 67 | const varMargin = '20px'; 68 | ``` 69 | 70 | ## Usage 71 | 72 | ```js 73 | postcss([ require('postcss-export-vars') ]) 74 | ``` 75 | 76 | See [PostCSS] docs for examples for your environment. 77 | 78 | ## Notice 79 | The plugin DO NOT tranform your PostCSS. It collects all definition and converts them to the required format. Therefore, the export to a file must be made and can not stream as PostCSS be returned. 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/osx,linux,windows,node,webstorm,visualstudiocode,sublimetext 2 | postcss_vars* 3 | 4 | ### OSX ### 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | 13 | # Thumbnails 14 | ._* 15 | 16 | # Files that might appear in the root of a volume 17 | .DocumentRevisions-V100 18 | .fseventsd 19 | .Spotlight-V100 20 | .TemporaryItems 21 | .Trashes 22 | .VolumeIcon.icns 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | 32 | ### Linux ### 33 | *~ 34 | 35 | # temporary files which can be created if a process still has a handle open of a deleted file 36 | .fuse_hidden* 37 | 38 | # KDE directory preferences 39 | .directory 40 | 41 | # Linux trash folder which might appear on any partition or disk 42 | .Trash-* 43 | 44 | 45 | ### Windows ### 46 | # Windows image file caches 47 | Thumbs.db 48 | ehthumbs.db 49 | 50 | # Folder config file 51 | Desktop.ini 52 | 53 | # Recycle Bin used on file shares 54 | $RECYCLE.BIN/ 55 | 56 | # Windows Installer files 57 | *.cab 58 | *.msi 59 | *.msm 60 | *.msp 61 | 62 | # Windows shortcuts 63 | *.lnk 64 | 65 | 66 | ### Node ### 67 | # Logs 68 | logs 69 | *.log 70 | npm-debug.log* 71 | 72 | # Runtime data 73 | pids 74 | *.pid 75 | *.seed 76 | 77 | # Directory for instrumented libs generated by jscoverage/JSCover 78 | lib-cov 79 | 80 | # Coverage directory used by tools like istanbul 81 | coverage 82 | 83 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 84 | .grunt 85 | 86 | # node-waf configuration 87 | .lock-wscript 88 | 89 | # Compiled binary addons (http://nodejs.org/api/addons.html) 90 | build/Release 91 | 92 | # Dependency directories 93 | node_modules 94 | jspm_packages 95 | 96 | # Optional npm cache directory 97 | .npm 98 | 99 | # Optional REPL history 100 | .node_repl_history 101 | 102 | 103 | ### WebStorm ### 104 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 105 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 106 | .idea/ 107 | # User-specific stuff: 108 | .idea/workspace.xml 109 | .idea/tasks.xml 110 | .idea/dictionaries 111 | .idea/vcs.xml 112 | .idea/jsLibraryMappings.xml 113 | 114 | # Sensitive or high-churn files: 115 | .idea/dataSources.ids 116 | .idea/dataSources.xml 117 | .idea/dataSources.local.xml 118 | .idea/sqlDataSources.xml 119 | .idea/dynamic.xml 120 | .idea/uiDesigner.xml 121 | 122 | # Gradle: 123 | .idea/gradle.xml 124 | .idea/libraries 125 | 126 | # Mongo Explorer plugin: 127 | .idea/mongoSettings.xml 128 | 129 | ## File-based project format: 130 | *.iws 131 | 132 | ## Plugin-specific files: 133 | 134 | # IntelliJ 135 | /out/ 136 | 137 | # mpeltonen/sbt-idea plugin 138 | .idea_modules/ 139 | 140 | # JIRA plugin 141 | atlassian-ide-plugin.xml 142 | 143 | # Crashlytics plugin (for Android Studio and IntelliJ) 144 | com_crashlytics_export_strings.xml 145 | crashlytics.properties 146 | crashlytics-build.properties 147 | fabric.properties 148 | 149 | 150 | ### VisualStudioCode ### 151 | .vscode 152 | 153 | 154 | 155 | ### SublimeText ### 156 | # cache files for sublime text 157 | *.tmlanguage.cache 158 | *.tmPreferences.cache 159 | *.stTheme.cache 160 | 161 | # workspace files are user-specific 162 | *.sublime-workspace 163 | 164 | # project files should be checked into the repository, unless a significant 165 | # proportion of contributors will probably not be using SublimeText 166 | # *.sublime-project 167 | 168 | # sftp configuration file 169 | sftp-config.json 170 | 171 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var postcss = require('postcss'); 4 | var _ = require('lodash'); 5 | var fs = require('fs'); 6 | 7 | /** 8 | * Extract all variables used in PostCSS files. 9 | * Detect custom properties and SASS like variables. 10 | * 11 | * options {object} 12 | * file: {string} File name with path 13 | * type: {string} File type. JSON and JS are supported. JSON ist default. 14 | * match: {string array} String array property name should contain 15 | * 16 | * @type {Plugin} 17 | */ 18 | module.exports = postcss.plugin('postcss-export-vars', function (options) { 19 | var _variableCollection = {}; 20 | 21 | options = options || {}; 22 | 23 | if (_.isEmpty(options.file)) options.file = 'postcss_vars'; 24 | if (_.isEmpty(options.match) || _.isArray(options.match) === false) options.match = []; 25 | 26 | /** 27 | * Create file. 28 | */ 29 | function createFile() { 30 | let fileContent = ''; 31 | 32 | /* 33 | * Customize data by type 34 | */ 35 | switch (options.type) { 36 | case 'js': 37 | fileContent += `'use strict';` + '\n'; 38 | for (let collectionKey in _variableCollection) { 39 | fileContent += `const ${collectionKey} = '${_variableCollection[collectionKey]}';` + '\n'; 40 | } 41 | 42 | if (_.endsWith(options.file, 'js') === false) options.file += '.js'; 43 | 44 | break; 45 | default: 46 | /* json */ 47 | fileContent = JSON.stringify(_variableCollection); 48 | 49 | if (_.endsWith(options.file, 'json') === false) options.file += '.json'; 50 | } 51 | 52 | /* 53 | * Write file 54 | */ 55 | return fs.writeFileSync(options.file, fileContent, 'utf8'); 56 | } 57 | 58 | /** 59 | * Detect if property fulfill one matching value. 60 | * 61 | * @param property 62 | * @returns {boolean} 63 | */ 64 | function propertyMatch(property) { 65 | for (let count = 0; count < options.match.length; count++) { 66 | if (property.indexOf(options.match[count]) > -1) { 67 | return true; 68 | } 69 | } 70 | 71 | return false; 72 | } 73 | 74 | /** 75 | * Extract custom properties and sass like variables from value. 76 | * Return each found variable as array with objects. 77 | * 78 | * @example 'test vars(--var1) + $width' 79 | * result in array with objects: 80 | * [{origin:'vars(--var1)', variable: '--var1'},{origin:'$width', variable: 'width'}] 81 | * @param value 82 | * @returns {Array} 83 | */ 84 | function extractVariables(value) { 85 | let regex = [/var\((.*?)\)/g, /\$([a-zA-Z0-9_\-]*)/g ], 86 | result = [], 87 | matchResult; 88 | 89 | regex.forEach(expression => { 90 | while (matchResult = expression.exec(value)) { 91 | result.push({ origin: matchResult[0], variable: matchResult[1] }); 92 | } 93 | }); 94 | 95 | return result; 96 | } 97 | 98 | /** 99 | * Resolve references on variable values to other variables. 100 | */ 101 | function resolveReferences() { 102 | 103 | for (let key in _variableCollection) { 104 | 105 | let referenceVariables = extractVariables(_variableCollection[key]); 106 | 107 | for (let current = 0; current < referenceVariables.length; current++) { 108 | if (_.isEmpty(_variableCollection[_.camelCase(referenceVariables[current].variable)]) === false) { 109 | _variableCollection[key] = _variableCollection[key].replace(referenceVariables[current].origin, _variableCollection[_.camelCase(referenceVariables[current].variable)]); 110 | } 111 | } 112 | } 113 | } 114 | 115 | /** 116 | * Escape values depends on type. 117 | * 118 | * For JS escape single quote, for json double quotes. 119 | * For everything else return origin value. 120 | * 121 | * @param value {string} 122 | * @returns {string} 123 | */ 124 | function escapeValue(value) { 125 | switch (options.type) { 126 | case 'js': 127 | return value.replace(/'/g, '\\\''); 128 | case 'json': 129 | return value.replace(/"/g, '\\"'); 130 | default: 131 | return value; 132 | } 133 | } 134 | 135 | /** 136 | * Plugin return 137 | * 138 | * @returns {function} 139 | */ 140 | return function (css) { 141 | 142 | css.walkDecls(decl => { 143 | if ((decl.prop.match(/^--/) || decl.prop.match(/^\$/)) && 144 | (_.isEmpty(options.match) || propertyMatch(decl.prop)) ) { 145 | _variableCollection[_.camelCase(decl.prop)] = escapeValue(decl.value); 146 | } 147 | }); 148 | 149 | /* 150 | * Resolve references on variable values 151 | */ 152 | resolveReferences(); 153 | 154 | createFile(); 155 | }; 156 | }); 157 | --------------------------------------------------------------------------------