├── .eslintrc
├── .gitignore
├── .npmignore
├── .nvmrc
├── .prettierrc
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── example
├── .eslinrc.js
├── .gitignore
├── README.md
├── config
│ ├── sassVars.js
│ ├── sassVars.json
│ └── utils.scss
├── demo.png
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── index.js
│ └── styles.scss
└── webpack.config.js
├── jest.config.js
├── package-lock.json
├── package.json
└── src
├── __mocks__
├── jsVars1.js
├── jsVars2.js
├── jsonVars1.json
├── jsonVars2.json
└── tsVars1.ts
├── __snapshots__
└── sassVarsLoader.test.js.snap
├── sassVarsLoader.js
├── sassVarsLoader.test.js
└── utils
├── __snapshots__
└── convertJsToSass.test.js.snap
├── convertJsToSass.js
├── convertJsToSass.test.js
├── isModule.js
├── isModule.test.js
├── readSassFiles.js
├── readVarsFromJSONFiles.js
├── readVarsFromJSONFiles.test.js
├── readVarsFromJavascriptFiles.js
├── readVarsFromJavascriptFiles.test.js
├── readVarsFromTypescriptFiles.js
├── readVarsFromTypescriptFiles.test.js
├── transformKeys.js
├── transformKeys.test.js
├── transformObject.js
├── transformObject.test.js
├── watchFileForChanges.js
├── watchFileForChanges.test.js
├── watchFilesForChanges.js
├── watchFilesForChanges.test.js
├── watchModuleForChanges.js
└── watchModuleForChanges.test.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "plugin:jest/recommended"
4 | ],
5 | "plugins": [
6 | "jest"
7 | ],
8 | "parserOptions": {
9 | "ecmaVersion": 2018,
10 | "sourceType": "module",
11 | "ecmaFeatures": {
12 | "impliedStrict": true,
13 | "experimentalObjectRestSpread": true
14 | }
15 | },
16 | "env": {
17 | "jest/globals": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Webstorm
2 | .idea
3 |
4 | # VS Code
5 | .history
6 | .vscode
7 |
8 | # OSX
9 | .DS_Store
10 |
11 | # VIM
12 | *.swp
13 | *.un~
14 |
15 | # NPM
16 | npm-debug.log
17 | node_modules
18 | .npmrc
19 | *.log
20 |
21 | # YARN
22 | yarn.lock
23 |
24 | # coverage
25 | coverage
26 |
27 | #dist
28 | dist
29 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Webstorm
2 | .idea
3 |
4 | # OSX
5 | .DS_Store
6 |
7 | # VIM
8 | *.swp
9 | *.un~
10 |
11 | # NPM
12 | npm-debug.log
13 | node_modules
14 | .npmrc
15 | *.log
16 |
17 | # coverage
18 | coverage
19 |
20 | # non dist files
21 | coverage
22 | example
23 | src
24 | .travis.yml
25 | .eslintrc
26 | .nvmrc
27 | .gitignore
28 |
29 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 10
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": false,
3 | "printWidth": 120,
4 | "tabWidth": 2,
5 | "singleQuote": true,
6 | "trailingComma": "es5",
7 | "jsxBracketSameLine": false,
8 | "semi": false,
9 | "rcVerbose": true
10 | }
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | codecov: true
4 | cache:
5 | directories:
6 | - node_modules
7 | env:
8 | - NODE_ENV=development
9 | matrix:
10 | fast_finish: true
11 | install:
12 | - NODE_ENV=development npm install
13 | script:
14 | - npm run lint
15 | - npm run test -- --runInBand
16 | - npm run coverage
17 | deploy:
18 | skip_cleanup: true
19 | provider: npm
20 | email: epegzz@gmail.com
21 | api_key:
22 | secure: mOTInY08VZzX2wajhu3JCxvLKVqa3RSG2PMy1u8vVfsNzh6oPezYR1FLCTjy5+WgEnrv5QHFzlRvv3yPeod7cqQ1mjQZGyx46jHu0BJq8xFSjtcD7woHWRpssZce/R1SmUr+hX8SNwK2x8c19TKsfn3VOeWevZraXSGXhVfypnDc+SakaPQXzBRzFDjHC2b1Pg+jEb4Z6dhT2bTcsOMD27Sp5ZM2O8OHL+SkVuDoyviWBIP8pJEX6R88zJRmxBHX0rNQ+83EC0EUn/iUbMd60Pi8A4pJXLthhrnKAWWdEDoVbYN/x5IB085zaRALIjM56FbxN019aDq69nKxI+GRx6iTKp07/yARGAHaTqXH6jbfluLXU2fzOYPq52zdzGCzkzny0Wt7m+JuMC2YDiXMTm3BgsfcntiGlQaH88XVUOqbko8x6Ra2/NIMBynLoipaFCLRwtC6dvcukCjMUdwe+/7KFzMWeYX2cLPBCIZiLHhZOuSwN0cKAPJ1woT6NwGzPUrktTZC+cU9yhefVURr7l0L5Wr/evYl2CZ86PawKim6lRKFGfX8EaOSgzfy1Z58C989swu74ICTRfjDvlDE/RpHY4JFrWrA7IZspkaVAAxtQxJeiO2OvgI3fcGr2ouMvmp5d2S1GU1KH4qDN39EgmoDX4ALdlfxZ0fpl3xhkmw=
23 | on:
24 | tags: true
25 | repo: epegzz/sass-vars-loader
26 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [6.0.0]
2 | ### Changed
3 | - Now respects the order in which the files are specified in the config when loading sass vars.
4 |
5 | ## [5.1.0]
6 | ### Added
7 | - Added `transformKeys` option to transform the variable / key names in Sass.
8 |
9 | ## [5.0.0]
10 | ### Changed
11 | - Strings are not quoted anymore. This might be a breaking change for some: Until now, all
12 | variable values that started with a "0" or a whitespace got automatically quoted.
13 | From now on you need to add the quotes yourself if you need them (i.e. "'0123'").
14 |
15 | ## [4.4.0]
16 | ### Changed
17 | - Arguments passed to the `files` option are now getting resolved, which allows passing files
18 | that are installed as node_modules without loosing HMR functionality.
19 |
20 | ## [4.0.0]
21 | ### Changed
22 | - Dropping Babel.
23 | Because `sass-vars-loader` is a NodeJS only project, there is not enough justification to use
24 | Babel as a compiler. Therefore, starting with this release, the NPM package will use the source
25 | code directly instead of a compiled version.
26 | The downside: `sass-vars-loader` now requires NodeJS version `8` or greater.
27 | The benefit: No unnecessary polyfills anymore when using a recent Node version.
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Daniel Schäfer
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 |
Sass Vars Loader
2 | Import Sass vars from Webpack config or from JS/JSON files
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ##### This loader allows you to use Sass variables defined in:
29 |
30 | ✅ JSON Files
31 | ✅ JavaScript Files
32 | ✅ Inlined in Webpack Config
33 |
34 |
35 |
36 | ##### Supports both syntax types:
37 |
38 | ✅ SASS Syntax
39 | ✅ SCSS Syntax
40 |
41 |
42 |
43 | ##### Supports hot reload:
44 |
45 | ✅ HMR Enabled
46 |
47 |
48 |
49 | ## Install
50 |
51 | using npm
52 | ```sh
53 | npm install @epegzz/sass-vars-loader --save-dev
54 | ```
55 | using yarn
56 | ```sh
57 | yarn add @epegzz/sass-vars-loader --dev
58 | ```
59 |
60 |
61 | ## Usage
62 |
63 | Look at the [Example Webpack Config File](./example/webpack.config.js) to see how to use this
64 | loader in conjunction with [style-loader](https://github.com/webpack-contrib/style-loader) and
65 | [css-loader](https://github.com/webpack-contrib/css-loader)
66 |
67 | ### Option 1: Inline Sass vars in the webpack config
68 |
69 | ```scss
70 | // styles.css:
71 |
72 | .some-class {
73 | background: $greenFromWebpackConfig;
74 | }
75 | ```
76 |
77 | ```js
78 | // webpack.config.js
79 |
80 | var path = require('path');
81 |
82 | module.exports = {
83 | entry: './src/index.js',
84 | module: {
85 | rules: [{
86 | test: /\.scss$/,
87 | use: [
88 | // Inserts all imported styles into the html document
89 | { loader: "style-loader" },
90 |
91 | // Translates CSS into CommonJS
92 | { loader: "css-loader" },
93 |
94 | // Compiles Sass to CSS
95 | { loader: "sass-loader", options: { includePaths: ["app/styles.scss"] } },
96 |
97 | // Reads Sass vars from files or inlined in the options property
98 | { loader: "@epegzz/sass-vars-loader", options: {
99 | syntax: 'scss',
100 | // Option 1) Specify vars here
101 | vars: {
102 | greenFromWebpackConfig: '#0f0'
103 | }
104 | }
105 | }]
106 | }]
107 | },
108 | output: {
109 | filename: 'bundle.js',
110 | path: path.resolve(__dirname, 'dist')
111 | }
112 | };
113 | ```
114 |
115 | ### Option 2: Load Sass vars from JSON file
116 |
117 | ```js
118 | // config/sassVars.json
119 |
120 | {
121 | "purpleFromJSON": "purple"
122 | }
123 | ```
124 |
125 | ```scss
126 | // styles.css:
127 |
128 | .some-class {
129 | background: $purpleFromJSON;
130 | }
131 | ```
132 |
133 | ```js
134 | // webpack.config.js
135 |
136 | var path = require('path');
137 |
138 | module.exports = {
139 | entry: './src/index.js',
140 | module: {
141 | rules: [{
142 | test: /\.scss$/,
143 | use: [
144 | // Inserts all imported styles into the html document
145 | { loader: "style-loader" },
146 |
147 | // Translates CSS into CommonJS
148 | { loader: "css-loader" },
149 |
150 | // Compiles Sass to CSS
151 | { loader: "sass-loader", options: { includePaths: ["app/styles.scss"] } },
152 |
153 | // Reads Sass vars from files or inlined in the options property
154 | { loader: "@epegzz/sass-vars-loader", options: {
155 | syntax: 'scss',
156 | files: [
157 | // Option 2) Load vars from JSON file
158 | path.resolve(__dirname, 'config/sassVars.json')
159 | ]
160 | }
161 | }]
162 | }]
163 | },
164 | output: {
165 | filename: 'bundle.js',
166 | path: path.resolve(__dirname, 'dist')
167 | }
168 | };
169 | ```
170 |
171 |
172 | ### Option 3: Load Sass vars from JavaScript file
173 |
174 | ```js
175 | // config/sassVars.js
176 |
177 | module.exports = {
178 | blueFromJavaScript: 'blue'
179 | };
180 | ```
181 |
182 | ```scss
183 | // styles.css:
184 |
185 | .some-class {
186 | background: $blueFromJavaScript;
187 | }
188 | ```
189 |
190 | ```js
191 | // webpack.config.js
192 |
193 | var path = require('path');
194 |
195 | module.exports = {
196 | entry: './src/index.js',
197 | module: {
198 | rules: [{
199 | test: /\.scss$/,
200 | use: [
201 | // Inserts all imported styles into the html document
202 | { loader: "style-loader" },
203 |
204 | // Translates CSS into CommonJS
205 | { loader: "css-loader" },
206 |
207 | // Compiles Sass to CSS
208 | { loader: "sass-loader", options: { includePaths: ["app/styles.scss"] } },
209 |
210 | // Reads Sass vars from files or inlined in the options property
211 | { loader: "@epegzz/sass-vars-loader", options: {
212 | syntax: 'scss',
213 | files: [
214 | // Option 3) Load vars from JavaScript file
215 | path.resolve(__dirname, 'config/sassVars.js')
216 | ]
217 | }
218 | }]
219 | }]
220 | },
221 | output: {
222 | filename: 'bundle.js',
223 | path: path.resolve(__dirname, 'dist')
224 | }
225 | };
226 | ```
227 |
228 |
229 | ### Pro Tip: Using objects as Sass vars!
230 |
231 | Use [map_get](http://sass-lang.com/documentation/Sass/Script/Functions.html#map_get-instance_method)
232 | in order to use objects as Sass vars:
233 |
234 | ```js
235 | // config/sassVars.js
236 |
237 | module.exports = {
238 | lightTheme: {
239 | background: 'white',
240 | color: 'black'
241 | },
242 | darkTheme: {
243 | background: 'black',
244 | color: 'gray'
245 | }
246 | };
247 | ```
248 |
249 | ```scss
250 | // styles.css:
251 |
252 | $theme: $lightTheme;
253 |
254 | .some-class {
255 | background: map_get($theme, background);
256 | color: map_get($theme, color);
257 | }
258 | ```
259 |
--------------------------------------------------------------------------------
/example/.eslinrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | sourceType: "module"
3 | };
4 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # sass-vars-loader usage example
2 |
3 | This is a simple example project using webpack2 and sass-vars-loader.
4 |
5 | The Sass vars in `src/styles.scss` are read from `webpack.config.js`, `config/sassVars.js`
6 | and `config/sassVars.json`.
7 |
8 | Open those files and play around! :)
9 |
10 |
11 | ## Installing
12 |
13 | ```
14 | npm install
15 | ```
16 |
17 | ## Running
18 |
19 | First build the `bundle.js`
20 | ```
21 | npm run build
22 | ```
23 |
24 | and then open `index.html` in your browser.
25 |
26 |
27 | It should look like this:
28 |
29 | 
--------------------------------------------------------------------------------
/example/config/sassVars.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | blueFromJS: 'blue',
3 | }
4 |
--------------------------------------------------------------------------------
/example/config/sassVars.json:
--------------------------------------------------------------------------------
1 | {
2 | "purpleFromJSON": "purple"
3 | }
--------------------------------------------------------------------------------
/example/config/utils.scss:
--------------------------------------------------------------------------------
1 | @function opaque($color, $opacity) {
2 | @return rgba($color, $opacity);
3 | }
--------------------------------------------------------------------------------
/example/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dsc8x/sass-vars-loader/71dbe697ca6f212b7fc0df37b5f0fa038c3d739b/example/demo.png
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | sass-vars-loader demo
4 |
5 |
6 |
7 |
8 | Green from webpack config
9 |
10 |
11 |
12 | Purple from JSON file
13 |
14 |
15 |
16 | Blue from JS file
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sass-vars-loader-demo",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "@epegzz/sass-vars-loader": "file:..",
15 | "@webpack-cli/migrate": "^0.1.2",
16 | "@webpack-cli/serve": "^0.1.2",
17 | "babel-preset-env": "^1.7.0",
18 | "css-loader": "^2.1.0",
19 | "node-sass": "^4.11.0",
20 | "sass-loader": "^7.1.0",
21 | "style-loader": "^0.23.1",
22 | "webpack": "^4.28.2",
23 | "webpack-cli": "^3.1.2"
24 | },
25 | "dependencies": {}
26 | }
27 |
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | // Import your Sass file anywhere in your app and webpack will make sure it
2 | // ends up as CSS in your HTML document.
3 | import './styles.scss'
4 |
--------------------------------------------------------------------------------
/example/src/styles.scss:
--------------------------------------------------------------------------------
1 | body {
2 |
3 | // Option 1) Load vars from webpack config (from webpack.config.js)
4 | .styles-from-webpack-config {
5 | background: $greenFromWebpackConfig;
6 | }
7 |
8 | // Option 2) Load vars from JSON file (from config/sassVars.json)
9 | .styles-from-json-file {
10 | background: $purpleFromJSON;
11 | }
12 |
13 | // Option 3) Load vars from Javascript file (from config/sassVars.json)
14 | .styles-from-js-file {
15 | background: $blueFromJS;
16 | }
17 |
18 |
19 |
20 | div {
21 | padding: 20px;
22 | margin: 20px;
23 | }
24 | pre {
25 | background: #ffffff3b;
26 | padding: 20px;
27 | }
28 | h3 {
29 | background: #ffffff69;
30 | padding: 10px;
31 | }
32 | }
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | entry: './src/index.js',
5 | watch: true,
6 | mode: 'production',
7 | module: {
8 | rules: [
9 | {
10 | test: /\.scss$/,
11 | use: [
12 | // Inserts all imported styles into the html document
13 | { loader: 'style-loader' },
14 |
15 | // Translates CSS into CommonJS
16 | { loader: 'css-loader' },
17 |
18 | // Compiles Sass to CSS
19 | {
20 | loader: 'sass-loader',
21 | options: { includePaths: ['app/styles.scss'] },
22 | },
23 |
24 | // Reads Sass vars from files or inlined in the options property
25 | {
26 | loader: '@epegzz/sass-vars-loader',
27 | options: {
28 | // You can specify vars here
29 | vars: {
30 | greenFromWebpackConfig: 'opaque(green, 0.5)', // `opaque` is defined in `config/utils.scss` which gets loaded below
31 | },
32 | files: [
33 | // You can include sass files
34 | path.resolve(__dirname, 'config/utils.scss'),
35 | // You can include JSON files
36 | path.resolve(__dirname, 'config/sassVars.json'),
37 | // You can include JavaScript files
38 | path.resolve(__dirname, 'config/sassVars.js'),
39 | ],
40 | },
41 | },
42 | ],
43 | },
44 | ],
45 | },
46 | output: {
47 | filename: 'bundle.js',
48 | path: path.resolve(__dirname, 'dist'),
49 | },
50 | }
51 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | coverageDirectory: './coverage/',
3 | collectCoverage: true,
4 | }
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@epegzz/sass-vars-loader",
3 | "version": "6.1.0",
4 | "author": "Daniel Schäfer ",
5 | "description": "A SASS vars loader for Webpack. Load global SASS vars from JS/JSON/Typescript files or from Webpack config.",
6 | "keywords": [
7 | "scss",
8 | "sass",
9 | "js",
10 | "json",
11 | "vars",
12 | "ts",
13 | "typescript",
14 | "variables",
15 | "webpack",
16 | "loader"
17 | ],
18 | "license": "MIT",
19 | "engines": {
20 | "node": ">=8"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/epegzz/sass-vars-loader"
25 | },
26 | "engineStrict": true,
27 | "main": "src/sassVarsLoader.js",
28 | "scripts": {
29 | "test": "NODE_ENV=testing jest --verbose",
30 | "precommit": "lint-staged",
31 | "watch-test": "NODE_ENV=testing jest --watch",
32 | "coverage": "NODE_ENV=testing jest && codecov",
33 | "lint": "eslint src",
34 | "format": "prettier-eslint --write \"src/**/*.js\""
35 | },
36 | "lint-staged": {
37 | "*.js": [
38 | "npm run format",
39 | "git add"
40 | ]
41 | },
42 | "dependencies": {
43 | "loader-utils": "^1.2.3",
44 | "require-from-string": "^2.0.2",
45 | "typescript": "^3.5.3"
46 | },
47 | "devDependencies": {
48 | "codecov": "^3.5.0",
49 | "eslint": "^6.1.0",
50 | "eslint-plugin-jest": "^22.13.0",
51 | "husky": "^3.0.1",
52 | "jest": "^24.8.0",
53 | "lint-staged": "^9.2.0",
54 | "prettier": "^1.18.2",
55 | "prettier-eslint-cli": "^5.0.0"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/__mocks__/jsVars1.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | value1FromJs: 'foo',
3 | loadingOrderTest2: 'fromJS',
4 | loadingOrderTest3: 'fromJS',
5 | }
6 |
--------------------------------------------------------------------------------
/src/__mocks__/jsVars2.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | value2FromJs: 'foo',
3 | }
4 |
--------------------------------------------------------------------------------
/src/__mocks__/jsonVars1.json:
--------------------------------------------------------------------------------
1 | {
2 | "value1FromJson": "foo",
3 | "loadingOrderTest1": "fromJSON",
4 | "loadingOrderTest2": "fromJSON"
5 | }
--------------------------------------------------------------------------------
/src/__mocks__/jsonVars2.json:
--------------------------------------------------------------------------------
1 | {
2 | "value2FromJson": "foo"
3 | }
--------------------------------------------------------------------------------
/src/__mocks__/tsVars1.ts:
--------------------------------------------------------------------------------
1 | const lol: any = {
2 | value1FromTs: 'tsFoo',
3 | loadingOrderTest4: 'fromTS',
4 | loadingOrderTest5: 'fromTS',
5 | }
6 |
7 | export default lol;
8 |
--------------------------------------------------------------------------------
/src/__snapshots__/sassVarsLoader.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`With multi post-processing Returns expected Sass contents 1`] = `"sassFileContents"`;
4 |
5 | exports[`With sass syntax Returns expected Sass contents 1`] = `
6 | "// Vars from Webpack config
7 | $value1FromWebpack: foo
8 | $nested: (works: (veryWell: true, withoutProblems: indeed))
9 |
10 | sassFileContents"
11 | `;
12 |
13 | exports[`With single post-processing Returns expected Sass contents 1`] = `"sassFileContents"`;
14 |
15 | exports[`With vars from JSON, JS and config Returns expected Sass contents 1`] = `
16 | "// Vars from jsonVars1.json
17 | $value1FromJson: foo;
18 | $loadingOrderTest1: fromJSON;
19 | $loadingOrderTest2: fromJSON;
20 |
21 | // Vars from jsVars1.js
22 | $value1FromJs: foo;
23 | $loadingOrderTest2: fromJS;
24 | $loadingOrderTest3: fromJS;
25 |
26 | // Vars from tsVars1.ts
27 | $value1FromTs: tsFoo;
28 | $loadingOrderTest4: fromTS;
29 | $loadingOrderTest5: fromTS;
30 |
31 | // Vars from Webpack config
32 | $loadingOrderTest3: fromConfig;
33 |
34 | sassFileContents"
35 | `;
36 |
37 | exports[`With vars from files Returns expected Sass contents 1`] = `
38 | "// Vars from jsonVars1.json
39 | $value1FromJson: foo;
40 | $loadingOrderTest1: fromJSON;
41 | $loadingOrderTest2: fromJSON;
42 |
43 | // Vars from jsVars1.js
44 | $value1FromJs: foo;
45 | $loadingOrderTest2: fromJS;
46 | $loadingOrderTest3: fromJS;
47 |
48 | // Vars from tsVars1.ts
49 | $value1FromTs: tsFoo;
50 | $loadingOrderTest4: fromTS;
51 | $loadingOrderTest5: fromTS;
52 |
53 | // Vars from jsonVars2.json
54 | $value2FromJson: foo;
55 |
56 | sassFileContents"
57 | `;
58 |
59 | exports[`With vars from webpack config Returns expected Sass contents 1`] = `
60 | "// Vars from Webpack config
61 | $value1FromWebpack: foo;
62 | $nested: (works: (veryWell: true, withoutProblems: indeed));
63 |
64 | sassFileContents"
65 | `;
66 |
67 | exports[`Without options Returns expected Sass contents 1`] = `"sassFileContents"`;
68 |
--------------------------------------------------------------------------------
/src/sassVarsLoader.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const loaderUtils = require('loader-utils')
3 | const readVarsFromJSONFiles = require('./utils/readVarsFromJSONFiles')
4 | const readVarsFromJavascriptFiles = require('./utils/readVarsFromJavascriptFiles')
5 | const readVarsFromTypescriptFiles = require('./utils/readVarsFromTypescriptFiles')
6 | const readSassFiles = require('./utils/readSassFiles')
7 | const watchFilesForChanges = require('./utils/watchFilesForChanges')
8 | const convertJsToSass = require('./utils/convertJsToSass')
9 | const transformKeys = require('./utils/transformKeys')
10 | const transformObject = require('./utils/transformObject')
11 | module.exports = async function(content) {
12 | this.cacheable()
13 | const callback = this.async()
14 | try {
15 | const options = loaderUtils.getOptions(this) || {}
16 |
17 | const files = options.files || []
18 | const syntax = options.syntax || 'scss'
19 | const transformFileContent = options.transformFileContent
20 | let transformKeysCallbacks = options.transformKeys
21 | if (transformKeysCallbacks && !Array.isArray(transformKeysCallbacks)) {
22 | transformKeysCallbacks = [transformKeysCallbacks]
23 | }
24 |
25 | await watchFilesForChanges(this, files)
26 |
27 | const vars = []
28 | for (const file of files) {
29 | // Javascript
30 | if (file.match(/\.js$/i)) {
31 | vars.push({ file, object: transformObject(readVarsFromJavascriptFiles([file]), transformFileContent) })
32 | }
33 |
34 | // Typescript
35 | if (file.match(/\.ts$/i)) {
36 | vars.push({ file, object: transformObject(readVarsFromTypescriptFiles([file]), transformFileContent) })
37 | }
38 |
39 | // JSON
40 | if (file.match(/\.json$/i)) {
41 | vars.push({ file, object: transformObject(readVarsFromJSONFiles([file]), transformFileContent) })
42 | }
43 |
44 | // Sass/Scss
45 | if (file.match(/\.s[ac]ss$/i)) {
46 | vars.push({ file, string: readSassFiles([file]) })
47 | }
48 | }
49 |
50 | // Vars from Webpack config
51 | if (options.vars) {
52 | vars.push({ object: transformObject(options.vars, transformFileContent) })
53 | }
54 |
55 | const varsString = vars.reduce((result, { file, object, string }) => {
56 | if (object) {
57 | if (transformKeysCallbacks) {
58 | object = transformKeysCallbacks.reduce((res, fn) => transformKeys(res, fn), {})
59 | }
60 | string = convertJsToSass(object, syntax)
61 | }
62 |
63 | if (string && !/^[\s\n]*$/.test(string)) {
64 | const comment = file ? `Vars from ${path.parse(file).base}` : 'Vars from Webpack config'
65 |
66 | return `${result}// ${comment}\n${string}\n\n`
67 | }
68 |
69 | return result
70 | }, '')
71 |
72 | callback(null, `${varsString}${content}`)
73 | } catch (err) {
74 | callback(err)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/sassVarsLoader.test.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const sassVarsLoader = require('./sassVarsLoader')
3 |
4 | const mockSassFileContents = `sassFileContents`
5 | let result, error, mockOptions
6 | const loaderContext = {
7 | cacheable: jest.fn(),
8 | addDependency: jest.fn(),
9 | resolve: jest.fn((context, file, callback) => {
10 | callback(null, file)
11 | }),
12 | }
13 |
14 | jest.mock('loader-utils', () => ({
15 | getOptions: () => mockOptions,
16 | }))
17 |
18 | describe('With vars from webpack config', () => {
19 | beforeAll(async () => {
20 | await setup({
21 | vars: {
22 | value1FromWebpack: 'foo',
23 | nested: {
24 | works: {
25 | veryWell: true,
26 | withoutProblems: 'indeed',
27 | },
28 | },
29 | },
30 | })
31 | })
32 | expectCorrectResult()
33 | expectMarksItselfAsCacheable()
34 | })
35 |
36 | describe('With vars from files', () => {
37 | beforeAll(async () => {
38 | await setup({
39 | files: [
40 | path.resolve(__dirname, '__mocks__/jsonVars1.json'),
41 | path.resolve(__dirname, '__mocks__/jsVars1.js'),
42 | path.resolve(__dirname, '__mocks__/tsVars1.ts'),
43 | path.resolve(__dirname, '__mocks__/jsonVars2.json'),
44 | ],
45 | })
46 | })
47 | expectCorrectResult()
48 | expectMarksItselfAsCacheable()
49 | expectWatchesFilesForChanges()
50 | })
51 |
52 | describe('With vars from JSON, JS and config', () => {
53 | beforeAll(async () => {
54 | await setup({
55 | vars: {
56 | loadingOrderTest3: 'fromConfig',
57 | },
58 | files: [
59 | path.resolve(__dirname, '__mocks__/jsonVars1.json'),
60 | path.resolve(__dirname, '__mocks__/jsVars1.js'),
61 | path.resolve(__dirname, '__mocks__/tsVars1.ts'),
62 | ],
63 | })
64 | })
65 | expectCorrectResult()
66 | })
67 |
68 | describe('Without options', () => {
69 | beforeAll(async () => {
70 | await setup()
71 | })
72 | expectCorrectResult()
73 | expectMarksItselfAsCacheable()
74 | })
75 |
76 | describe('With sass syntax', () => {
77 | beforeAll(async () => {
78 | await setup({
79 | syntax: 'sass',
80 | vars: {
81 | value1FromWebpack: 'foo',
82 | nested: {
83 | works: {
84 | veryWell: true,
85 | withoutProblems: 'indeed',
86 | },
87 | },
88 | },
89 | })
90 | })
91 | expectCorrectResult()
92 | })
93 |
94 | describe('With invalid file', () => {
95 | beforeAll(async () => {
96 | await setup({
97 | syntax: 'sass',
98 | files: ['~invalid~'],
99 | })
100 | })
101 | expectError(`Invalid file: "~invalid~". Consider using "path.resolve" in your config.`)
102 | })
103 |
104 | describe('With single post-processing', () => {
105 | beforeAll(async () => {
106 | await setup({
107 | transformKeys: key => `transformed-${key}`,
108 | vars: {
109 | 'transformed-valueToTransform': 'foo',
110 | 'transformed-nested': {
111 | 'transformed-works': {
112 | 'transformed-Complete': true,
113 | 'transformed-veryWellResult': true,
114 | 'transformed-withoutProblems': 'indeed',
115 | },
116 | },
117 | },
118 | })
119 | })
120 | expectCorrectResult()
121 | expectMarksItselfAsCacheable()
122 | })
123 |
124 | describe('With multi post-processing', () => {
125 | beforeAll(async () => {
126 | await setup({
127 | transformKeys: [key => `transformed-${key}`, key => key.toUpperCase()],
128 | vars: {
129 | 'TRANSFORMED-VALUETOTRANSFORM': 'foo',
130 | 'TRANSFORMED-NESTED': {
131 | 'TRANSFORMED-WORKS': {
132 | 'TRANSFORMED-COMPLETE': true,
133 | 'TRANSFORMED-VERYWELLRESULT': true,
134 | 'TRANSFORMED-WITHOUTPROBLEMS': 'indeed',
135 | },
136 | },
137 | },
138 | })
139 | })
140 | expectCorrectResult()
141 | expectMarksItselfAsCacheable()
142 | })
143 |
144 | async function setup(options) {
145 | result = null
146 | error = null
147 | mockOptions = options
148 | loaderContext.addDependency.mockClear()
149 | loaderContext.cacheable.mockClear()
150 | loaderContext.async = () => (err, res) => {
151 | error = err
152 | result = res
153 | }
154 | await sassVarsLoader.call(loaderContext, mockSassFileContents)
155 | }
156 |
157 | function expectCorrectResult() {
158 | it('Returns expected Sass contents', () => {
159 | expect(result).toMatchSnapshot()
160 | })
161 | }
162 |
163 | function expectError(message) {
164 | it('Returns an error', () => {
165 | expect(error && error.message).toEqual(message)
166 | })
167 | }
168 |
169 | function expectMarksItselfAsCacheable() {
170 | it('Marks itself as cacheable', () => {
171 | expect(loaderContext.cacheable).toHaveBeenCalled()
172 | })
173 | }
174 |
175 | function expectWatchesFilesForChanges() {
176 | it('Watches files for changes', () => {
177 | const { files } = mockOptions
178 | expect(loaderContext.addDependency).toHaveBeenCalledTimes(files.length)
179 | })
180 | }
181 |
--------------------------------------------------------------------------------
/src/utils/__snapshots__/convertJsToSass.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`With sass syntax converts array with nested arrays 1`] = `"$it: ((list, 1), (list, 2))"`;
4 |
5 | exports[`With sass syntax converts empty array 1`] = `"$it: ()"`;
6 |
7 | exports[`With sass syntax converts empty object 1`] = `""`;
8 |
9 | exports[`With sass syntax converts multi line string 1`] = `
10 | "$it: line1
11 | line2"
12 | `;
13 |
14 | exports[`With sass syntax converts multi line string with double quotes 1`] = `
15 | "$it: \\"line1
16 | line2\\""
17 | `;
18 |
19 | exports[`With sass syntax converts multi line string with single quotes 1`] = `
20 | "$it: 'line1
21 | line2'"
22 | `;
23 |
24 | exports[`With sass syntax converts multi value object 1`] = `"$it: (a: 1, b: 2)"`;
25 |
26 | exports[`With sass syntax converts multiple vars 1`] = `
27 | "$it: value
28 | $also: this"
29 | `;
30 |
31 | exports[`With sass syntax converts nested object with array 1`] = `"$it: (a: 1, b: 2, c: (d: (4, px, em)))"`;
32 |
33 | exports[`With sass syntax converts nested object: nested 1`] = `"$it: (a: 1, b: 2, c: (d: 4))"`;
34 |
35 | exports[`With sass syntax converts number 1`] = `"$it: 5"`;
36 |
37 | exports[`With sass syntax converts object with nested array 1`] = `"$it: (15, px, (nested: (oh, no)))"`;
38 |
39 | exports[`With sass syntax converts simple object 1`] = `"$it: (a: 1)"`;
40 |
41 | exports[`With sass syntax converts simple simple 1`] = `"$it: (1, 2)"`;
42 |
43 | exports[`With sass syntax converts string 1`] = `"$it: value"`;
44 |
45 | exports[`With sass syntax converts string with double quotes 1`] = `"$it: 'value'"`;
46 |
47 | exports[`With sass syntax converts string with single quotes 1`] = `"$it: \\"value\\""`;
48 |
49 | exports[`With scss syntax converts array with nested arrays 1`] = `"$it: ((list, 1), (list, 2));"`;
50 |
51 | exports[`With scss syntax converts empty array 1`] = `"$it: ();"`;
52 |
53 | exports[`With scss syntax converts empty object 1`] = `""`;
54 |
55 | exports[`With scss syntax converts multi line string 1`] = `
56 | "$it: line1
57 | line2;"
58 | `;
59 |
60 | exports[`With scss syntax converts multi line string with double quotes 1`] = `
61 | "$it: \\"line1
62 | line2\\";"
63 | `;
64 |
65 | exports[`With scss syntax converts multi line string with single quotes 1`] = `
66 | "$it: 'line1
67 | line2';"
68 | `;
69 |
70 | exports[`With scss syntax converts multi value object 1`] = `"$it: (a: 1, b: 2);"`;
71 |
72 | exports[`With scss syntax converts multiple vars 1`] = `
73 | "$it: value;
74 | $also: this;"
75 | `;
76 |
77 | exports[`With scss syntax converts nested object with array 1`] = `"$it: (a: 1, b: 2, c: (d: (4, px, em)));"`;
78 |
79 | exports[`With scss syntax converts nested object: nested 1`] = `"$it: (a: 1, b: 2, c: (d: 4));"`;
80 |
81 | exports[`With scss syntax converts number 1`] = `"$it: 5;"`;
82 |
83 | exports[`With scss syntax converts object with nested array 1`] = `"$it: (15, px, (nested: (oh, no)));"`;
84 |
85 | exports[`With scss syntax converts simple object 1`] = `"$it: (a: 1);"`;
86 |
87 | exports[`With scss syntax converts simple simple 1`] = `"$it: (1, 2);"`;
88 |
89 | exports[`With scss syntax converts string 1`] = `"$it: value;"`;
90 |
91 | exports[`With scss syntax converts string with double quotes 1`] = `"$it: 'value';"`;
92 |
93 | exports[`With scss syntax converts string with single quotes 1`] = `"$it: \\"value\\";"`;
94 |
--------------------------------------------------------------------------------
/src/utils/convertJsToSass.js:
--------------------------------------------------------------------------------
1 | function convertJsToSass(obj, syntax) {
2 | const suffix = syntax === 'sass' ? '' : ';'
3 | const keys = Object.keys(obj)
4 | const lines = keys.map(key => `$${key}: ${formatValue(obj[key], syntax)}${suffix}`)
5 | return lines.join('\n')
6 | }
7 |
8 | function formatNestedObject(obj, syntax) {
9 | const keys = Object.keys(obj)
10 | return keys.map(key => `${key}: ${formatValue(obj[key], syntax)}`).join(', ')
11 | }
12 |
13 | function formatValue(value, syntax) {
14 | if (value instanceof Array) {
15 | return `(${value.map(formatValue).join(', ')})`
16 | }
17 |
18 | if (typeof value === 'object') {
19 | return `(${formatNestedObject(value, syntax)})`
20 | }
21 |
22 | if (typeof value === 'string') {
23 | return value
24 | }
25 |
26 | return JSON.stringify(value)
27 | }
28 |
29 | module.exports = convertJsToSass
30 |
--------------------------------------------------------------------------------
/src/utils/convertJsToSass.test.js:
--------------------------------------------------------------------------------
1 | const convertJsToSass = require('./convertJsToSass')
2 |
3 | const testCases = [
4 | { name: 'converts string', input: { it: 'value' } },
5 | { name: 'converts multi line string', input: { it: 'line1\nline2' } },
6 | { name: 'converts string with single quotes', input: { it: '"value"' } },
7 | { name: 'converts string with double quotes', input: { it: "'value'" } },
8 | { name: 'converts multi line string with double quotes', input: { it: '"line1\nline2"' } },
9 | { name: 'converts multi line string with single quotes', input: { it: "'line1\nline2'" } },
10 | { name: 'converts number', input: { it: 5 } },
11 | { name: 'converts empty array', input: { it: [] } },
12 | { name: 'converts simple simple', input: { it: [1, 2] } },
13 | { name: 'converts multiple vars', input: { it: 'value', also: 'this' } },
14 | {
15 | name: 'converts array with nested arrays',
16 | input: { it: [['list', 1], ['list', 2]] },
17 | },
18 | {
19 | name: 'converts object with nested array',
20 | input: { it: [15, 'px', { nested: ['oh', 'no'] }] },
21 | },
22 | { name: 'converts empty object', input: {} },
23 | { name: 'converts simple object', input: { it: { a: 1 } } },
24 | { name: 'converts multi value object', input: { it: { a: 1, b: 2 } } },
25 | {
26 | name: 'converts nested object: nested',
27 | input: { it: { a: 1, b: 2, c: { d: 4 } } },
28 | },
29 | {
30 | name: 'converts nested object with array',
31 | input: { it: { a: 1, b: 2, c: { d: [4, 'px', 'em'] } } },
32 | },
33 | ]
34 | ;['sass', 'scss'].forEach(syntax =>
35 | describe(`With ${syntax} syntax`, () =>
36 | testCases.forEach(testCase =>
37 | it(testCase.name, () => expect(convertJsToSass(testCase.input, syntax)).toMatchSnapshot())
38 | ))
39 | )
40 |
--------------------------------------------------------------------------------
/src/utils/isModule.js:
--------------------------------------------------------------------------------
1 | const isModule = path => {
2 | try {
3 | require.resolve(path)
4 | return true
5 | } catch (e) {
6 | return false
7 | }
8 | }
9 |
10 | module.exports = isModule
11 |
--------------------------------------------------------------------------------
/src/utils/isModule.test.js:
--------------------------------------------------------------------------------
1 | const isModule = require('./isModule')
2 |
3 | describe('isModule', () => {
4 | it('returns true if it is a module', () => {
5 | expect(isModule('fs')).toEqual(true)
6 | })
7 | it('returns false if it is not a module', () => {
8 | expect(isModule('->definitelyNotAModule')).toEqual(false)
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/utils/readSassFiles.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | module.exports = function(files) {
4 | return files.reduce((vars, filepath) => {
5 | if (filepath.match(/\.s[ac]ss/)) {
6 | return [vars, fs.readFileSync(filepath, 'utf8')].join('\n')
7 | }
8 | return vars
9 | }, '')
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/readVarsFromJSONFiles.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | module.exports = function(files) {
4 | return files.reduce(
5 | (vars, filepath) =>
6 | Object.assign(vars, filepath.endsWith('.json') && JSON.parse(fs.readFileSync(filepath, 'utf8'))),
7 | {}
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/utils/readVarsFromJSONFiles.test.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const readVarsFromJSONFiles = require('./readVarsFromJSONFiles')
3 |
4 | const files = [
5 | path.resolve(__dirname, '../__mocks__/jsonVars1.json'),
6 | path.resolve(__dirname, '../__mocks__/jsVars1.js'),
7 | path.resolve(__dirname, '../__mocks__/jsonVars2.json'),
8 | ]
9 |
10 | it('returns a vars object as expected', () => {
11 | expect(readVarsFromJSONFiles(files)).toEqual({
12 | value1FromJson: 'foo',
13 | value2FromJson: 'foo',
14 | loadingOrderTest1: 'fromJSON',
15 | loadingOrderTest2: 'fromJSON',
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/src/utils/readVarsFromJavascriptFiles.js:
--------------------------------------------------------------------------------
1 | module.exports = function(files) {
2 | return files.reduce((vars, filepath) => {
3 | if (!filepath.endsWith('.js')) {
4 | return vars
5 | }
6 | delete require.cache[filepath]
7 | return Object.assign(vars, require(filepath))
8 | }, {})
9 | }
10 |
--------------------------------------------------------------------------------
/src/utils/readVarsFromJavascriptFiles.test.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const readVarsFromJavascriptFiles = require('./readVarsFromJavascriptFiles')
3 |
4 | const files = [
5 | path.resolve(__dirname, '../__mocks__/jsVars1.js'),
6 | path.resolve(__dirname, '../__mocks__/jsonVars1.json'),
7 | path.resolve(__dirname, '../__mocks__/jsVars2.js'),
8 | ]
9 |
10 | it('returns a vars object as expected', () => {
11 | expect(readVarsFromJavascriptFiles(files)).toEqual({
12 | value1FromJs: 'foo',
13 | value2FromJs: 'foo',
14 | loadingOrderTest2: 'fromJS',
15 | loadingOrderTest3: 'fromJS',
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/src/utils/readVarsFromTypescriptFiles.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const ts = require('typescript')
3 | const requirefs = require('require-from-string')
4 |
5 | module.exports = function(files) {
6 | return files.reduce((vars, filepath) => {
7 | if (!filepath.endsWith('.ts')) {
8 | return vars
9 | }
10 | delete require.cache[filepath]
11 |
12 | const input = fs.readFileSync(filepath, 'utf8')
13 | const transpiledInput = ts.transpileModule(input, {
14 | compilerOptions: { module: ts.ModuleKind.CommonJS },
15 | })
16 | const result = requirefs(transpiledInput.outputText)
17 | return Object.assign(vars, result.default)
18 | }, {})
19 | }
20 |
--------------------------------------------------------------------------------
/src/utils/readVarsFromTypescriptFiles.test.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const readVarsFromTypescriptFiles = require('./readVarsFromTypescriptFiles')
3 |
4 | const files = [
5 | path.resolve(__dirname, '../__mocks__/jsVars1.js'),
6 | path.resolve(__dirname, '../__mocks__/jsonVars1.json'),
7 | path.resolve(__dirname, '../__mocks__/tsVars1.ts'),
8 | path.resolve(__dirname, '../__mocks__/jsVars2.js'),
9 | ]
10 |
11 | it('returns a vars object as expected', () => {
12 | expect(readVarsFromTypescriptFiles(files)).toEqual({
13 | value1FromTs: 'tsFoo',
14 | loadingOrderTest4: 'fromTS',
15 | loadingOrderTest5: 'fromTS',
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/src/utils/transformKeys.js:
--------------------------------------------------------------------------------
1 | function transformKeys(vars, callback) {
2 | return Object.keys(vars).reduce((result, key) => {
3 | const value = vars[key] instanceof Object ? transformKeys(vars[key], callback) : vars[key]
4 | return Object.assign(result, { [callback(key)]: value })
5 | }, {})
6 | }
7 |
8 | module.exports = transformKeys
9 |
--------------------------------------------------------------------------------
/src/utils/transformKeys.test.js:
--------------------------------------------------------------------------------
1 | const transformKeys = require('./transformKeys')
2 |
3 | const vars = {
4 | topPlainKey: 'hello',
5 | topNestedKey: {
6 | subNestedKey: {
7 | deepPlainKey: true,
8 | },
9 | },
10 | }
11 |
12 | describe('transformKeys', () => {
13 | it('Nested adding prefix', () => {
14 | expect(transformKeys(vars, key => `transformed-${key}`)).toStrictEqual({
15 | 'transformed-topPlainKey': 'hello',
16 | 'transformed-topNestedKey': {
17 | 'transformed-subNestedKey': {
18 | 'transformed-deepPlainKey': true,
19 | },
20 | },
21 | })
22 | })
23 |
24 | it('Nested camelCase to lower case', () => {
25 | expect(transformKeys(vars, key => key.toLowerCase())).toStrictEqual({
26 | topplainkey: 'hello',
27 | topnestedkey: {
28 | subnestedkey: {
29 | deepplainkey: true,
30 | },
31 | },
32 | })
33 | })
34 |
35 | it('Nested camelCase to UPPER CASE', () => {
36 | expect(transformKeys(vars, key => key.toUpperCase())).toStrictEqual({
37 | TOPPLAINKEY: 'hello',
38 | TOPNESTEDKEY: {
39 | SUBNESTEDKEY: {
40 | DEEPPLAINKEY: true,
41 | },
42 | },
43 | })
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/src/utils/transformObject.js:
--------------------------------------------------------------------------------
1 | function transformObject(varsObject, transformer) {
2 | if (transformer && {}.toString.call(transformer) === '[object Function]') return transformer(varsObject)
3 | return varsObject
4 | }
5 |
6 | module.exports = transformObject
7 |
--------------------------------------------------------------------------------
/src/utils/transformObject.test.js:
--------------------------------------------------------------------------------
1 | const transformObject = require('./transformObject')
2 |
3 | const vars = {
4 | topPlainKey: 'hello',
5 | topNestedKey: {
6 | subNestedKey: {
7 | deepPlainKey: true,
8 | },
9 | },
10 | }
11 |
12 | describe('transformObject', () => {
13 | it('Passing a transform function', () => {
14 | expect(transformObject(vars, obj => ({ newObj: true }))).toStrictEqual({ newObj: true })
15 | })
16 |
17 | it('Passing a non function value', () => {
18 | expect(transformObject(vars, 'non-function')).toStrictEqual(vars)
19 | })
20 |
21 | it('Not passing a transformer', () => {
22 | expect(transformObject(vars)).toStrictEqual(vars)
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/src/utils/watchFileForChanges.js:
--------------------------------------------------------------------------------
1 | /**
2 | * watchFileForChanges
3 | *
4 | * Adds a file as loader dependency which will make Webpack watch
5 | * the file in watch-mode and reloads if it changes.
6 | */
7 |
8 | module.exports = function(loader, file) {
9 | return loader.addDependency(file)
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/watchFileForChanges.test.js:
--------------------------------------------------------------------------------
1 | const watchFileForChanges = require('./watchFileForChanges')
2 |
3 | const mockLoader = {
4 | addDependency: jest.fn(),
5 | }
6 |
7 | describe('watchFileForChanges', () => {
8 | beforeAll(() => {
9 | watchFileForChanges(mockLoader, 'file1')
10 | })
11 | it('calls `loader.addDependency`', () => {
12 | expect(mockLoader.addDependency).toHaveBeenCalledTimes(1)
13 | expect(mockLoader.addDependency).toHaveBeenCalledWith('file1')
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/src/utils/watchFilesForChanges.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const watchFileForChanges = require('./watchFileForChanges')
3 | const watchModuleForChanges = require('./watchModuleForChanges')
4 | const isModule = require('./isModule')
5 |
6 | /**
7 | * watchFilesForChanges
8 | *
9 | * Adds files as loader dependency which will make Webpack watch
10 | * the files in watch-mode and reload if they change.
11 | */
12 |
13 | async function watchFilesForChanges(loader, files) {
14 | for (const file of files) {
15 | if (fs.existsSync(file)) {
16 | watchFileForChanges(loader, file)
17 | } else if (isModule(file)) {
18 | await watchModuleForChanges(loader, file)
19 | } else {
20 | throw new Error(`Invalid file: "${file}". Consider using "path.resolve" in your config.`)
21 | }
22 | }
23 | }
24 |
25 | module.exports = watchFilesForChanges
26 |
--------------------------------------------------------------------------------
/src/utils/watchFilesForChanges.test.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const watchFilesForChanges = require('./watchFilesForChanges')
3 | const watchFileForChanges = require('./watchFileForChanges')
4 | const watchModuleForChanges = require('./watchModuleForChanges')
5 |
6 | jest.mock('./watchFileForChanges')
7 | jest.mock('./watchModuleForChanges')
8 |
9 | describe('watchFilesForChanges', () => {
10 | it('watches modules', async () => {
11 | watchFileForChanges.mockClear()
12 | watchModuleForChanges.mockClear()
13 |
14 | let error
15 | try {
16 | await watchFilesForChanges({}, ['fs'])
17 | } catch (e) {
18 | error = e
19 | }
20 |
21 | expect(error).toEqual(undefined)
22 | expect(watchFileForChanges).toHaveBeenCalledTimes(0)
23 | expect(watchModuleForChanges).toHaveBeenCalledTimes(1)
24 | expect(watchModuleForChanges).toHaveBeenCalledWith(expect.anything(), 'fs')
25 | })
26 |
27 | it('watches files', async () => {
28 | watchFileForChanges.mockClear()
29 | watchModuleForChanges.mockClear()
30 |
31 | let error
32 | const file = path.resolve(__dirname, '../__mocks__/jsVars1.js')
33 | try {
34 | await watchFilesForChanges({}, [file])
35 | } catch (e) {
36 | error = e
37 | }
38 |
39 | expect(error).toEqual(undefined)
40 | expect(watchModuleForChanges).toHaveBeenCalledTimes(0)
41 | expect(watchFileForChanges).toHaveBeenCalledTimes(1)
42 | expect(watchFileForChanges).toHaveBeenCalledWith(expect.anything(), file)
43 | })
44 |
45 | it('throws error for invalid file', async () => {
46 | watchFileForChanges.mockClear()
47 | watchModuleForChanges.mockClear()
48 |
49 | let error
50 | const file = '~~invalid~~'
51 | try {
52 | await watchFilesForChanges({}, [file])
53 | } catch (e) {
54 | error = e
55 | }
56 |
57 | expect(error.message).toEqual(`Invalid file: "${file}". Consider using "path.resolve" in your config.`)
58 | expect(watchModuleForChanges).toHaveBeenCalledTimes(0)
59 | expect(watchFileForChanges).toHaveBeenCalledTimes(0)
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/src/utils/watchModuleForChanges.js:
--------------------------------------------------------------------------------
1 | /**
2 | * watchModuleForChanges
3 | *
4 | * Adds a file from a module as loader dependency which will make Webpack watch
5 | * the file in watch-mode and reload if it changes.
6 | */
7 |
8 | module.exports = async function(loader, file) {
9 | return new Promise((resolve, reject) => {
10 | delete require.cache[require.resolve(file)]
11 | loader.resolve(loader.rootContext, file, (err, resolvedFile) => {
12 | if (err) return reject(err)
13 | loader.addDependency(resolvedFile)
14 | resolve()
15 | })
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/src/utils/watchModuleForChanges.test.js:
--------------------------------------------------------------------------------
1 | const watchModuleForChanges = require('./watchModuleForChanges')
2 |
3 | let mockError
4 | const mockLoader = {
5 | addDependency: jest.fn(),
6 | resolve: jest.fn((context, file, callback) => {
7 | callback(mockError, file)
8 | }),
9 | }
10 |
11 | describe('watchModuleForChanges', () => {
12 | beforeEach(() => {
13 | mockLoader.addDependency.mockClear()
14 | })
15 | it('calls `loader.addDependency`', async () => {
16 | let error
17 | try {
18 | await watchModuleForChanges(mockLoader, 'fs')
19 | } catch (e) {
20 | error = e
21 | }
22 | expect(error).toEqual(undefined)
23 | expect(mockLoader.addDependency).toHaveBeenCalledTimes(1)
24 | expect(mockLoader.addDependency).toHaveBeenCalledWith('fs')
25 | })
26 |
27 | it('rejects if require cannot resolve the module', async () => {
28 | const moduleName = '->definitelyNotAModule'
29 | let error
30 | try {
31 | await watchModuleForChanges(mockLoader, moduleName)
32 | } catch (e) {
33 | error = e
34 | }
35 | expect(error.message).toEqual(`Cannot find module '${moduleName}' from 'watchModuleForChanges.js'`)
36 | expect(mockLoader.addDependency).toHaveBeenCalledTimes(0)
37 | })
38 | })
39 |
--------------------------------------------------------------------------------