├── package.json ├── index.js ├── .gitignore ├── README.md └── test.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-to-scss", 3 | "version": "1.1.0", 4 | "description": "Converts js objects, like configuration ones, in scss variables, ready to be injected inside your Sass engine via the data option", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/MakhBeth/js-to-scss.git" 12 | }, 13 | "keywords": [ 14 | "sass", 15 | "javascript", 16 | "scss", 17 | "convert" 18 | ], 19 | "author": "Davide Di Pumpo (makhbeth)", 20 | "license": "ISC", 21 | "dependencies": { 22 | "@riim/object-entries-polyfill": "github:riim/object-entries-polyfill" 23 | }, 24 | "devDependencies": { 25 | "tape": "^4.8.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var polyfill = require("@riim/object-entries-polyfill"); 2 | 3 | const escapeSpecialChars = (string) => 4 | string.replace(/(??@\[\]^{}|~]/g, "\\$&"); 5 | 6 | function flattenObjSass( 7 | obj, 8 | prefix = "$", 9 | transform = (_derivedKey, val) => val 10 | ) { 11 | return Object.entries(obj).reduce((accumulated, [key, value]) => { 12 | const derivedKey = `${prefix}${escapeSpecialChars(key)}`; 13 | if (typeof value === "object" && !Array.isArray(value) && value) { 14 | const processedObject = flattenObjSass( 15 | value, 16 | `${derivedKey}-`, 17 | transform 18 | ); 19 | return accumulated + processedObject; 20 | } else { 21 | const processedValue = Array.isArray(value) 22 | ? `(${transform(derivedKey, value)})` 23 | : transform(derivedKey, value); 24 | return accumulated + `${derivedKey}: ${processedValue}; `; 25 | } 26 | }, ""); 27 | } 28 | 29 | module.exports = flattenObjSass; 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################ 2 | ############### .gitignore ################## 3 | ################################################ 4 | # 5 | # This file is only relevant if you are using git. 6 | # 7 | # Files which match the splat patterns below will 8 | # be ignored by git. This keeps random crap and 9 | # sensitive credentials from being uploaded to 10 | # your repository. It allows you to configure your 11 | # app for your machine without accidentally 12 | # committing settings which will smash the local 13 | # settings of other developers on your team. 14 | # 15 | # Some reasonable defaults are included below, 16 | # but, of course, you should modify/extend/prune 17 | # to fit your needs! 18 | ################################################ 19 | *# 20 | *~ 21 | *.log 22 | .DS_STORE 23 | .DS_Store 24 | .history/ 25 | .idea 26 | .netbeans 27 | .node-version 28 | .node_history/ 29 | .vscode/ 30 | Thumbs.db 31 | logs 32 | node_modules/ 33 | 34 | 35 | ################################################ 36 | # Local Configuration 37 | # 38 | # Explicitly ignore files which contain: 39 | # 40 | # 1. Sensitive information you'd rather not push to 41 | # your git repository. 42 | # e.g., your personal API keys or passwords. 43 | # 44 | # 2. Environment-specific configuration 45 | # Basically, anything that would be annoying 46 | # to have to change every time you do a 47 | # `git pull` 48 | # e.g., your local development database, or 49 | # the S3 bucket you're using for file uploads 50 | # development. 51 | # 52 | ################################################ 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ################################################ 61 | # Dependencies 62 | # 63 | # When releasing a production app, you may 64 | # consider including your node_modules and 65 | # bower_components directory in your git repo, 66 | # but during development, its best to exclude it, 67 | # since different developers may be working on 68 | # different kernels, where dependencies would 69 | # need to be recompiled anyway. 70 | # 71 | # More on that here about node_modules dir: 72 | # http://www.futurealoof.com/posts/nodemodules-in-git.html 73 | # (credit Mikeal Rogers, @mikeal) 74 | # 75 | # About bower_components dir, you can see this: 76 | # http://addyosmani.com/blog/checking-in-front-end-dependencies/ 77 | # (credit Addy Osmani, @addyosmani) 78 | # 79 | ################################################ 80 | 81 | 82 | 83 | ################################################ 84 | # Temporary files generated by frameworks. 85 | ################################################ 86 | 87 | .tmp 88 | tmp 89 | dump.rdb 90 | 91 | 92 | 93 | 94 | ################################################ 95 | # Node.js / NPM 96 | # 97 | # Common files generated by Node, NPM, and the 98 | # related ecosystem. 99 | ################################################ 100 | 101 | *.seed 102 | *.log 103 | *.out 104 | *.pid 105 | npm-debug.log 106 | 107 | 108 | 109 | ################################################ 110 | # BUILD and optimisation results/ 111 | ################################################ 112 | 113 | 114 | 115 | 116 | ################################################ 117 | # Miscellaneous 118 | # 119 | # Common files generated by text editors, 120 | # operating systems, file systems, etc. 121 | ################################################ 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-to-scss 2 | 3 | `npm install js-to-scss -D` 4 | 5 | This is a simple module that will flatten down a javascript object into a 6 | string. You can inject that string inside your Sass engine, via the 7 | [data option](https://github.com/sass/node-sass#data) 8 | 9 | ## Example: 10 | 11 | ```js 12 | import flattenObjSass from "js-to-scss"; 13 | 14 | const color= { 15 | black: '#000000', 16 | white: '#ffffff' 17 | } 18 | flattenObjSass(color); 19 | ``` 20 | 21 | will return: 22 | 23 | ```scss 24 | $black: #000000; 25 | $white: #ffffff; 26 | ``` 27 | 28 | ## Behavior 29 | 30 | This module will convert arrays in Sass-list-like string and will go recursively 31 | down to your object. 32 | 33 | Example: 34 | 35 | ```js 36 | const settings = { 37 | color: { 38 | primary: { 39 | light: "#2ecc71", 40 | normal: "#27ae60", 41 | dark: "#16a085" 42 | } 43 | }, 44 | remSize: ["14px", "16px", "18px"] 45 | }; 46 | ``` 47 | 48 | ```scss 49 | $color-primary-light: #2ecc71; 50 | $color-primary-normal: #27ae60; 51 | $color-primary-dark: #16a085; 52 | $remSize: ( 53 | 14px, 54 | 16px, 55 | 18px 56 | ); 57 | ``` 58 | 59 | ### Quoted and Unquoted values 60 | 61 | You have to add an extra pair of quotes around any `string` values 62 | when you need them to be _quoted strings_ in the resulting SCSS. 63 | `js-to-scss` does not add or remove any quotes, because there are valid use cases for both scenarios: 64 | 65 | ```js 66 | const settings = { 67 | color: "rgb(204, 102, 153)", 68 | font: "'Helvetica Neue'", 69 | }; 70 | ``` 71 | 72 | ```scss 73 | $color: rgb(204, 102, 153); 74 | $font: 'Helvetica Neue'; 75 | ``` 76 | 77 | ### Special Characters 78 | 79 | SASS variable names can contain a wide range of ASCII and Unicode characters; 80 | even punctuation and parentheses are allowed 81 | as long as they are escaped with a preceding backslash. 82 | `js-to-scss` automatically escapes the following characters 83 | when found (unescaped) in property names: `!"#$%&'()*+,./:;<=>?@[]^{|}~` 84 | 85 | ```js 86 | const color = { 87 | primary: { 88 | light: "#2ecc71", 89 | normal: "#27ae60", 90 | dark: "#16a085", 91 | '30%': "#27ae604c", 92 | '60%': "#27ae6099", 93 | }, 94 | icon: { 95 | '\\@': "#000000", 96 | '\\$': "#dddddd", 97 | '\\+': "#eeeeee", 98 | }, 99 | }; 100 | ``` 101 | 102 | ```scss 103 | $primary-light: #2ecc71; 104 | $primary-normal: #27ae60; 105 | $primary-dark: #16a085; 106 | $primary-30\%: #27ae604c; 107 | $primary-60\%: #27ae6099; 108 | $icon-\@: #000000; 109 | $icon-\$: #dddddd; 110 | $icon-\+: #eeeeee; 111 | ``` 112 | 113 | ## Options 114 | 115 | ```js 116 | flattenObjSass( 117 | obj, 118 | prefix = "$", 119 | transform = (prop, val) => val 120 | ) 121 | ``` 122 | 123 | * `obj`: The object that will be transformed 124 | * `prefix` = String prepended to every "property", default is `$` to mimic Sass 125 | variables 126 | * `transform`: you can manipulate the value, the key is passed to check purposes. 127 | 128 | Example: 129 | 130 | ```js 131 | flattenObjSass( 132 | { 133 | transform: 1, 134 | thatKey: 2, 135 | complex: { 136 | a: [1, 2, 3], 137 | b: "#fff" 138 | } 139 | }, 140 | 141 | "$transform-", 142 | 143 | (key, val) => { 144 | if (typeof val === "number") { 145 | val = val * 100; 146 | } 147 | 148 | if (key === "thatKey") { 149 | val = val / 100; 150 | } 151 | 152 | if (Array.isArray(val)) { 153 | val = val.map(n => n * 100); 154 | } 155 | 156 | return val; 157 | } 158 | ); 159 | ``` 160 | 161 | ```scss 162 | $transform-transform: 100; 163 | $transform-thatKey: 200; 164 | $transform-complex-a: (100,200,300); 165 | $transform-complex-b: #fff; 166 | ``` 167 | 168 | ### Why that? 169 | 170 | I like to have a config file with all the design option configuration: color, 171 | breakpoints, typography etc... So I can use that across all the application 172 | (breakpoint are really useful in your template engine if you use Elements 173 | Queries) 174 | 175 | ### Why not a Webpack loader / Gulp plugin / Whatelse? 176 | 177 | I wanted something simple importable in every node based project I have around 178 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const test = require("tape"); 2 | const flattenObjSass = require("./index"); 3 | 4 | test("empty object yields empty string", function (t) { 5 | t.equal(flattenObjSass({}), ""); 6 | t.end(); 7 | }); 8 | 9 | test("single property with numeric value", function (t) { 10 | t.equal(flattenObjSass({ single: 1 }), "$single: 1; "); 11 | t.end(); 12 | }); 13 | 14 | test("single property with boolean value", function (t) { 15 | t.equal(flattenObjSass({ single: true }), "$single: true; "); 16 | t.equal(flattenObjSass({ single: false }), "$single: false; "); 17 | t.end(); 18 | }); 19 | 20 | test("single property with string value", function (t) { 21 | t.equal(flattenObjSass({ single: "foo" }), "$single: foo; "); 22 | t.equal(flattenObjSass({ single: "15px" }), "$single: 15px; "); 23 | t.end(); 24 | }); 25 | 26 | test("unquoted string value containing whitespace yields unquoted string value", function (t) { 27 | t.equal( 28 | flattenObjSass({ color: "rgb(204, 102, 153)" }), 29 | "$color: rgb(204, 102, 153); " 30 | ); 31 | t.end(); 32 | }); 33 | 34 | test("quoted string value containing whitespace yields quoted string value", function (t) { 35 | t.equal( 36 | flattenObjSass({ single: "'hello world'" }), 37 | "$single: 'hello world'; " 38 | ); 39 | t.equal( 40 | flattenObjSass({ single: '"hello world"' }), 41 | '$single: "hello world"; ' 42 | ); 43 | t.end(); 44 | }); 45 | 46 | test("multiple properties", function (t) { 47 | t.equal(flattenObjSass({ first: 1, second: 2 }), "$first: 1; $second: 2; "); 48 | t.end(); 49 | }); 50 | 51 | test("nested object", function (t) { 52 | t.equal(flattenObjSass({ object: { one: 1 } }), "$object-one: 1; "); 53 | t.equal( 54 | flattenObjSass({ object: { one: { two: 2 } } }), 55 | "$object-one-two: 2; " 56 | ); 57 | t.equal( 58 | flattenObjSass({ object: { one: { two: { three: 3 } } } }), 59 | "$object-one-two-three: 3; " 60 | ); 61 | t.end(); 62 | }); 63 | 64 | test("nested empty object yields empty string", function (t) { 65 | t.equal(flattenObjSass({ object: {} }), ""); 66 | t.end(); 67 | }); 68 | 69 | test("nested array", function (t) { 70 | t.equal(flattenObjSass({ array: [1, 2, 3] }), "$array: (1,2,3); "); 71 | t.end(); 72 | }); 73 | 74 | test("nested empty array", function (t) { 75 | t.equal(flattenObjSass({ array: [] }), "$array: (); "); 76 | t.end(); 77 | }); 78 | 79 | test("mixed array", function (t) { 80 | t.equal( 81 | flattenObjSass({ array: ["1rem", 2, "1px solid red"] }), 82 | "$array: (1rem,2,1px solid red); " 83 | ); 84 | t.end(); 85 | }); 86 | 87 | test("prefix option", function (t) { 88 | t.equal( 89 | flattenObjSass({ black: "#000", brand: "#6699cc" }, "$color-"), 90 | "$color-black: #000; $color-brand: #6699cc; " 91 | ); 92 | t.end(); 93 | }); 94 | 95 | test("transform option", function (t) { 96 | const actual = flattenObjSass( 97 | { 98 | transform: 1, 99 | thatKey: 2, 100 | complex: { 101 | a: [1, 2, 3], 102 | b: "#fff", 103 | }, 104 | }, 105 | "$transform-", 106 | (key, val) => { 107 | if (typeof val === "number") { 108 | val = val * 100; 109 | } 110 | 111 | if (key === "thatKey") { 112 | val = val / 100; 113 | } 114 | 115 | if (Array.isArray(val)) { 116 | val = val.map((n) => n * 100); 117 | } 118 | 119 | return val; 120 | } 121 | ); 122 | const expected = 123 | "$transform-transform: 100; $transform-thatKey: 200; $transform-complex-a: (100,200,300); $transform-complex-b: #fff; "; 124 | t.equal(actual, expected); 125 | t.end(); 126 | }); 127 | 128 | test("unescaped special characters in property names are escaped", function (t) { 129 | const actual = flattenObjSass({ 130 | primary: { 131 | "30%": "#27ae604c", 132 | "60%": "#27ae6099", 133 | }, 134 | }); 135 | const expected = "$primary-30\\%: #27ae604c; $primary-60\\%: #27ae6099; "; 136 | t.equal(actual, expected); 137 | t.end(); 138 | }); 139 | 140 | test("already escaped special characters in property names are preserved", function (t) { 141 | const actual = flattenObjSass({ 142 | icon: { 143 | "\\@": "#000000", 144 | "\\$": "#dddddd", 145 | "\\+": "#eeeeee", 146 | }, 147 | }); 148 | const expected = 149 | "$icon-\\@: #000000; $icon-\\$: #dddddd; $icon-\\+: #eeeeee; "; 150 | t.equal(actual, expected); 151 | t.end(); 152 | }); 153 | --------------------------------------------------------------------------------