├── .npmignore ├── .gitignore ├── .babelrc ├── .eslintignore ├── test ├── fixture │ ├── config2.js │ ├── config1.js │ ├── merged-config.js │ └── module.js ├── suite.js ├── helper │ ├── config-loader-runner.js │ └── webpack-runner.js ├── config-loader.test.js └── config-webpack-plugin.test.js ├── img ├── how-it-works.png ├── how-it-works.sketch ├── env-vars-replacement.png └── merging-config-files.png ├── .travis.yml ├── lib ├── config-loader.min.js ├── config-loader.js ├── config-webpack-plugin.min.js └── config-webpack-plugin.js ├── .editorconfig ├── src ├── config-loader.js └── config-webpack-plugin.js ├── .eslintrc.json ├── LICENSE ├── package.json ├── gulpfile.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | /img 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore library files. 2 | /lib 3 | -------------------------------------------------------------------------------- /test/fixture/config2.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'config2', 3 | number: 222 4 | }; 5 | -------------------------------------------------------------------------------- /test/suite.js: -------------------------------------------------------------------------------- 1 | require('./config-loader.test.js'); 2 | require('./config-webpack-plugin.test.js'); 3 | -------------------------------------------------------------------------------- /img/how-it-works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmariuzzo/config-webpack-plugin/HEAD/img/how-it-works.png -------------------------------------------------------------------------------- /img/how-it-works.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmariuzzo/config-webpack-plugin/HEAD/img/how-it-works.sketch -------------------------------------------------------------------------------- /img/env-vars-replacement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmariuzzo/config-webpack-plugin/HEAD/img/env-vars-replacement.png -------------------------------------------------------------------------------- /img/merging-config-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmariuzzo/config-webpack-plugin/HEAD/img/merging-config-files.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | install: 5 | - npm install 6 | before_script: 7 | - npm install -g grunt-cli 8 | -------------------------------------------------------------------------------- /test/fixture/config1.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'config1', 3 | number: 111, 4 | boolean: true, 5 | string: 'abc' 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixture/merged-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'config2', 3 | number: 222, 4 | boolean: true, 5 | string: 'abc' 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixture/module.js: -------------------------------------------------------------------------------- 1 | const config = require('./config1'); 2 | 3 | module.exports = { 4 | name: config.name, 5 | number: config.number, 6 | boolean: config.boolean, 7 | string: config.string 8 | }; 9 | -------------------------------------------------------------------------------- /lib/config-loader.min.js: -------------------------------------------------------------------------------- 1 | "use strict";/**! 2 | * config-webpack-plugin 3 | * 4 | * @author Rubens Mariuzzo 5 | * @license MIT https://opensource.org/licenses/MIT 6 | */ 7 | module.exports=function(){var r="{}";"string"==typeof this.query&&"?"===this.query.charAt(0)&&(r=this.query.slice(1));var t=JSON.parse(r);return"module.exports = "+JSON.stringify(t)}; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Tab indentation. 12 | [*.js] 13 | indent_style = space 14 | indent_size = 4 15 | 16 | # Because NPM doesn't respect formatting and I don't want to fight it. 17 | [{package.json}] 18 | indent_style = space 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /src/config-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The ConfigLoader function. 3 | * 4 | * @author Rubens Mariuzzo 5 | * @license MIT https://opensource.org/licenses/MIT 6 | */ 7 | module.exports = function () { 8 | // Get JSON string from query string. 9 | let query = '{}'; 10 | if (typeof this.query === 'string' && this.query.charAt(0) === '?') { 11 | query = this.query.slice(1); 12 | } 13 | 14 | // Parse the JSON string and... stringify it back. 15 | let config = JSON.parse(query); 16 | return `module.exports = ${JSON.stringify(config)}`; 17 | }; 18 | -------------------------------------------------------------------------------- /test/helper/config-loader-runner.js: -------------------------------------------------------------------------------- 1 | let ConfigLoader = require('../../lib/config-loader'); 2 | 3 | module.exports = { 4 | 5 | /** 6 | * Run the config loader. 7 | * 8 | * @param {Object} json The JSON object to pass to the config loader. 9 | * @return {Object} The result of the config loader. 10 | */ 11 | run: (json) => { 12 | let query = ''; 13 | if (json) { 14 | query = '?' + JSON.stringify(json); 15 | } 16 | let fn = ConfigLoader.bind({ 17 | query 18 | }); 19 | return eval(fn()); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "indent": [ 12 | "error", 13 | 4 14 | ], 15 | "linebreak-style": [ 16 | "error", 17 | "unix" 18 | ], 19 | "quotes": [ 20 | "error", 21 | "single" 22 | ], 23 | "semi": [ 24 | "error", 25 | "always" 26 | ], 27 | "no-console": "off" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/config-loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /**! 4 | * config-webpack-plugin 5 | * 6 | * @author Rubens Mariuzzo 7 | * @license MIT https://opensource.org/licenses/MIT 8 | */ 9 | 10 | /** 11 | * The ConfigLoader function. 12 | */ 13 | module.exports = function () { 14 | // Get JSON string from query string. 15 | var query = '{}'; 16 | if (typeof this.query === 'string' && this.query.charAt(0) === '?') { 17 | query = this.query.slice(1); 18 | } 19 | 20 | // Parse the JSON string and... stringify it back. 21 | var config = JSON.parse(query); 22 | return 'module.exports = ' + JSON.stringify(config); 23 | }; -------------------------------------------------------------------------------- /test/config-loader.test.js: -------------------------------------------------------------------------------- 1 | /*global describe,it,expect*/ 2 | 3 | const ConfigLoader = require('../lib/config-loader'); 4 | const loader = require('./helper/config-loader-runner'); 5 | 6 | describe('config-loader', () => { 7 | 8 | it('should be a function', () => { 9 | expect(typeof ConfigLoader).toBe('function'); 10 | }); 11 | 12 | it('should return an empty object', () => { 13 | expect(loader.run()).toEqual({}); 14 | }); 15 | 16 | it('should return an object with same property', () => { 17 | let obj = { 18 | a: 123 19 | }; 20 | expect(loader.run(obj)).toEqual(obj); 21 | }); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Rubens Mariuzzo 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "config-webpack-plugin", 3 | "version": "1.1.0", 4 | "description": "Merge configuration files with environment variables.", 5 | "main": "lib/config-webpack-plugin.js", 6 | "scripts": { 7 | "test": "gulp test", 8 | "test:watch": "gulp test:watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/rmariuzzo/config-webpack-plugin.git" 13 | }, 14 | "keywords": [ 15 | "webpack", 16 | "config", 17 | "configuration", 18 | "plugin", 19 | "env", 20 | "environment" 21 | ], 22 | "author": "Rubens Mariuzzo (http://github.com/rmariuzzo)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/rmariuzzo/config-webpack-plugin/issues" 26 | }, 27 | "homepage": "https://github.com/rmariuzzo/config-webpack-plugin#readme", 28 | "devDependencies": { 29 | "babel-preset-es2015": "^6.13.2", 30 | "eslint": "^3.2.2", 31 | "gulp": "^3.9.1", 32 | "gulp-babel": "^6.1.2", 33 | "gulp-eslint": "^3.0.1", 34 | "gulp-jasmine": "^2.4.0", 35 | "gulp-rename": "^1.2.2", 36 | "gulp-rollup": "^2.3.0", 37 | "gulp-uglify": "^2.0.0", 38 | "tmp": "0.0.28", 39 | "webpack": "^1.13.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/helper/webpack-runner.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const tmp = require('tmp'); 3 | 4 | let webpackConfig = { 5 | entry: './test/fixture/module.js', 6 | output: { 7 | path: tmp.dirSync().name 8 | }, 9 | }; 10 | 11 | /*globals fail*/ 12 | 13 | module.exports = { 14 | 15 | /** 16 | * Run webpack. 17 | * 18 | * @param {Object} plugin The plugin to install. 19 | * @param {Function} fn The callback function. 20 | */ 21 | run(plugin, fn) { 22 | // Generate a random filename for the output file. 23 | let filename = Math.random().toString(36).slice(2) + '.js'; 24 | webpackConfig.output.filename = filename; 25 | 26 | // Add given plugin, if any. 27 | let plugins = []; 28 | if (plugin) { 29 | plugins.push(plugin); 30 | } 31 | 32 | // Prepare the webpack compiler instance. 33 | let compiler = webpack(Object.assign({}, webpackConfig, { 34 | plugins 35 | })); 36 | 37 | // Run webpack's compiler. 38 | compiler.run((err, stats) => { 39 | if (err) { 40 | fail(err); 41 | } 42 | 43 | // Eval emitted output and return it –if requested-. 44 | let output; 45 | try { 46 | output = eval(stats.compilation.assets[filename].source()); 47 | } catch (err) { 48 | fail(err); 49 | } 50 | 51 | if (fn) { 52 | fn(output); 53 | } 54 | }); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /lib/config-webpack-plugin.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function _toConsumableArray(e){if(Array.isArray(e)){for(var r=0,t=Array(e.length);r { 22 | /** @desc compile the source files into the final library. */ 23 | 24 | return gulp 25 | .src([asset.src]) 26 | .pipe(rollup({ 27 | entry: [asset.plugin, asset.loader] 28 | })) 29 | .pipe(babel()) 30 | .pipe(gulp.dest('lib/')) 31 | .pipe(rename({ 32 | suffix: '.min' 33 | })) 34 | .pipe(uglify({ 35 | preserveComments: 'license' 36 | })) 37 | .pipe(gulp.dest('lib/')); 38 | 39 | }); 40 | 41 | gulp.task('test', ['lint:test', 'build'], () => { 42 | /** @desc Run all the tests. */ 43 | 44 | return gulp 45 | .src([asset.testSuite]) 46 | .pipe(jasmine()); 47 | }); 48 | 49 | gulp.task('watch', () => { 50 | /** @desc Watch source and test files then run all tests on changes. */ 51 | 52 | return gulp 53 | .watch([asset.src, asset.test], ['test']); 54 | }); 55 | 56 | gulp.task('lint:src', () => { 57 | /** @desc Lint all source files. */ 58 | 59 | return gulp 60 | .src([asset.src]) 61 | .pipe(eslint()); 62 | }); 63 | 64 | gulp.task('lint:test', () => { 65 | /** @desc Lint all test files. */ 66 | 67 | return gulp 68 | .src([asset.test]) 69 | .pipe(eslint()); 70 | }); 71 | -------------------------------------------------------------------------------- /test/config-webpack-plugin.test.js: -------------------------------------------------------------------------------- 1 | /*global describe,it,expect,spyOn*/ 2 | 3 | const ConfigPlugin = require('../lib/config-webpack-plugin'); 4 | const config1 = require('./fixture/config1'); 5 | const mergedConfig = require('./fixture/merged-config'); 6 | const webpackRunner = require('./helper/webpack-runner'); 7 | 8 | describe('config-webpack-plugin', () => { 9 | 10 | it('should be a function', () => { 11 | expect(typeof ConfigPlugin).toBe('function'); 12 | }); 13 | 14 | it('should complain when no configuration file is specified', () => { 15 | expect(() => new ConfigPlugin()).toThrow(); 16 | }); 17 | 18 | it('should invoke `apply` when webpack is resolving modules', (done) => { 19 | let plugin = new ConfigPlugin('./test/fixture/config1.js'); 20 | spyOn(plugin, 'apply').and.callThrough(); 21 | 22 | webpackRunner.run(plugin, () => { 23 | expect(plugin.apply).toHaveBeenCalled(); 24 | done(); 25 | }); 26 | }); 27 | 28 | it('should invoke `intercept` when webpack try to resolve the specified configuration file', (done) => { 29 | let plugin = new ConfigPlugin('./test/fixture/config1.js'); 30 | spyOn(plugin, 'intercept').and.callThrough(); 31 | 32 | webpackRunner.run(plugin, () => { 33 | expect(plugin.intercept).toHaveBeenCalled(); 34 | done(); 35 | }); 36 | }); 37 | 38 | it('should not do anything with the specified configuration file when plugin is not installed', (done) => { 39 | webpackRunner.run(null, (output) => { 40 | expect(output).toEqual(config1); 41 | done(); 42 | }); 43 | }); 44 | 45 | it('should not do anything with the specified configuration file when there is no matching environment variables', (done) => { 46 | let plugin = new ConfigPlugin('./test/fixture/config1.js'); 47 | webpackRunner.run(plugin, (output) => { 48 | expect(output).toEqual(config1); 49 | done(); 50 | }); 51 | }); 52 | 53 | it('should modify the source of the specified configuration file when there is a matching environment variable', (done) => { 54 | try { 55 | process.env.name = 'intercepted'; 56 | let plugin = new ConfigPlugin('./test/fixture/config1.js'); 57 | webpackRunner.run(plugin, (output) => { 58 | expect(output.name).toBe('intercepted'); 59 | done(); 60 | }); 61 | } finally { 62 | delete process.env.name; 63 | } 64 | }); 65 | 66 | it('should merge multiple configuration files', (done) => { 67 | let plugins = new ConfigPlugin([ 68 | './test/fixture/config1.js', 69 | './test/fixture/config2.js' 70 | ]); 71 | webpackRunner.run(plugins, (output) => { 72 | expect(output).toEqual(mergedConfig); 73 | done(); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # config-webpack-plugin 2 | 3 | > 💫 Merge one or more configuration files together with environment variables too. 4 | 5 | ## Installation 6 | 7 | ```sh 8 | npm install config-webpack-plugin --save-dev 9 | ``` 10 | 11 | ### How it works? 12 | 13 | Let's say you have a base configuration file (`./config.js`) shared across your team. Now, you want for every developer to be able to override any configuration value without modifying the base configuration file. Therefore, every developer can create a _local_ configuration file (`./config.local.js`). And… `config-webpack-plugin` will do the rest for you. 14 | 15 | **`webpack.config.js`:** 16 | ```js 17 | const ConfigPlugin = require('config-webpack-plugin') 18 | 19 | module.exports = { 20 | plugins: [ 21 | new ConfigPlugin([ 22 | './config.js', 23 | './config.local.js' 24 | ]) 25 | ] 26 | } 27 | ``` 28 | 29 | 1. The `config-webpack-plugin` will merge all specified configuration file contents from _right to left_, thus creating a _‘merged configuration’_. 30 | ![Merging configuration files](https://github.com/rmariuzzo/config-webpack-plugin/raw/master/img/merging-config-files.png) 31 | 32 | 2. If the _‘merged configuration’_ contains a `key` matching a current environment variable then the related `value` will be replaced by the environment variable's value. 33 | ![Environment variables replacements](https://github.com/rmariuzzo/config-webpack-plugin/raw/master/img/env-vars-replacement.png) 34 | 35 | 3. Finally, the `config-webpack-plugin` will intercept the _‘main configuration’_ file (the first specified) during webpack's module resolution and will replace its source with the _‘merged configuration’_. 36 | 37 | ## Usage 38 | 39 | ### Single configuration 40 | 41 | ```js 42 | const ConfigPlugin = require('config-webpack-plugin'); 43 | 44 | module.exports = { 45 | plugins: [ 46 | new ConfigPlugin('./config.js') 47 | ] 48 | } 49 | ``` 50 | 51 | ### Multiple configuration 52 | 53 | ```js 54 | const ConfigPlugin = require('config-webpack-plugin'); 55 | 56 | module.exports = { 57 | plugins: [ 58 | new ConfigPlugin([ 59 | './config.js', 60 | './config.local.js' 61 | ]) 62 | ] 63 | } 64 | ``` 65 | 66 | ## FAQ 67 | 68 | ### What is a configuration file? 69 | 70 | A module that export an object with key/value pairs that looks like: 71 | 72 | ```js 73 | module.exports = { 74 | API_BASE_URL: 'http://localhost/', 75 | NODE_ENV: 'development', 76 | AUTH_SPOOFED: true, 77 | } 78 | ``` 79 | 80 | ### Does my configuration file gets modified? 81 | 82 | No. 83 | 84 | ## Development 85 | 86 | If you want to collaborate with the development of `config-webpack-plugin` you need to have installed NodeJS 6 and Gulp. Then follow these instructions to make my life easier: 87 | 88 | 1. Fork this repo. 89 | 2. Clone your forked repo. 90 | 3. Create a _feature branch_ with: `git checkout develop; git checkout -b feature/`. 91 | 4. Create a pull request **to be merged into `develop` branch**. 92 | 93 | > 💁 Please, do not submit PR to be merged into `master` branch. 94 | 95 | ### Roadmap 96 | 97 | - [x] [Add test. Please!](https://github.com/rmariuzzo/config-webpack-plugin/issues/4) 98 | - [x] [Add CI. Please!](https://github.com/rmariuzzo/config-webpack-plugin/issues/5) 99 | - [x] [Support multiple file](https://github.com/rmariuzzo/config-webpack-plugin/issues/2) (`new ConfigPlugin(['./config.default.js', '.config.local.js'])`). 100 | - [ ] Support JSON file too ♥️, because they are prettiest for configuration. 101 | - [ ] [Add a static website using GitHub Pages](https://github.com/rmariuzzo/config-webpack-plugin/issues/3) (why not?). 102 | 103 | > 💁 Do you want to suggest a feature? [Add a suggestion](https://github.com/rmariuzzo/config-webpack-plugin/issues/new). 104 | -------------------------------------------------------------------------------- /src/config-webpack-plugin.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | /** 4 | * The ConfigPlugin class. 5 | * 6 | * @author Rubens Mariuzzo 7 | * @license MIT https://opensource.org/licenses/MIT 8 | */ 9 | class ConfigPlugin { 10 | 11 | /** 12 | * Construct a new ConfigPlugin instance. 13 | * 14 | * @param {string|Array} files The path of one or more configuration files to merge. 15 | */ 16 | constructor(files) { 17 | 18 | // If one configuration files was provided let's wrap into an array. 19 | if (typeof files === 'string') { 20 | files = [files]; 21 | } 22 | 23 | // If the given argument wasn't a string neither an array 24 | // then we don't know what we are dealing with. 25 | if (!Array.isArray(files)) { 26 | throw new Error('The configuration file should be a string or an array.'); 27 | } 28 | 29 | let cwd = process.cwd(); 30 | 31 | // Map each configuration file with their absolute file path. 32 | this.files = files.map((file) => { 33 | return { 34 | request: file, 35 | path: path.join(cwd, file) 36 | }; 37 | }); 38 | } 39 | 40 | /** 41 | * Apply this plugin in the compiler build process. 42 | * 43 | * @param {Object} compiler The webpack compiler. 44 | */ 45 | apply(compiler) { 46 | 47 | // Merge configuration files with environment variables. 48 | let files = this.load(this.files); 49 | let merged = this.merge(files.map(file => file.source)); 50 | 51 | // Get the main configuration file. As we merge from right to left then our 52 | // main configuration file is the last in the array. 53 | let mainConfig = this.files[0]; 54 | 55 | // Intercept all resolved modules and look for the specified configuration file. 56 | compiler.plugin('normal-module-factory', (nmf) => { 57 | nmf.plugin('after-resolve', (data, next) => { 58 | let interceptedPath = data.resource; 59 | 60 | // Is this one of our configuration files? 61 | if (interceptedPath === mainConfig.path) { 62 | this.intercept(data, merged); 63 | } 64 | 65 | // Continue the normal resolution process. 66 | return next(null, data); 67 | }); 68 | }); 69 | } 70 | 71 | /** 72 | * Load one or more configuration files. 73 | * 74 | * @param {Array} files One or more configuration files. 75 | * 76 | * @return {Array} List of configuration file contents. 77 | */ 78 | load(files) { 79 | // Get the path of this directory relative to the CWD. 80 | let cwd = path.relative(__dirname, process.cwd()); 81 | 82 | // Map files to their contents and return it. 83 | return files.map((file) => { 84 | try { 85 | // Get the path of the configuration file relative to this file. 86 | let request = path.join(cwd, file.request); 87 | 88 | // Try to require the configuration file. 89 | file.source = require(request); 90 | return file; 91 | } catch (e) { 92 | throw new Error(`Cannot load: ${path}. ${e.message}`); 93 | } 94 | }); 95 | } 96 | 97 | /** 98 | * Merge one more configuration objects then merge with any matching 99 | * environment variable. 100 | * 101 | * @param {Array} configs One or more configuration files to merge. 102 | * 103 | * @return {object} The merged configuration. 104 | */ 105 | merge(configs) { 106 | // Merge configurations together from right to left. 107 | let merged = Object.assign({}, ...configs); 108 | 109 | // Merge any matching environment variable. 110 | Object.keys(process.env).forEach((key) => { 111 | if (merged.hasOwnProperty(key)) { 112 | merged[key] = process.env[key]; 113 | } 114 | }); 115 | 116 | return merged; 117 | } 118 | 119 | /** 120 | * Intercept a data chunk replacing all loaders by ours. 121 | * 122 | * @param {Object} data The data chunk. 123 | */ 124 | intercept(data, replacement) { 125 | let loader = path.join(__dirname, 'config-loader.js'); 126 | let json = JSON.stringify(replacement); 127 | data.loaders = [`${loader}?${json}`]; 128 | } 129 | } 130 | 131 | module.exports = ConfigPlugin; 132 | -------------------------------------------------------------------------------- /lib/config-webpack-plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 4 | 5 | var _path = require('path'); 6 | 7 | var _path2 = _interopRequireDefault(_path); 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 10 | 11 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 12 | 13 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 14 | 15 | /** 16 | * The ConfigPlugin class. 17 | */ 18 | var ConfigPlugin = function () { 19 | 20 | /** 21 | * Construct a new ConfigPlugin instance. 22 | * 23 | * @param {string|Array} files The path of one or more configuration files to merge. 24 | */ 25 | function ConfigPlugin(files) { 26 | _classCallCheck(this, ConfigPlugin); 27 | 28 | // If one configuration files was provided let's wrap into an array. 29 | if (typeof files === 'string') { 30 | files = [files]; 31 | } 32 | 33 | // If the given argument wasn't a string neither an array 34 | // then we don't know what we are dealing with. 35 | if (!Array.isArray(files)) { 36 | throw new Error('The configuration file should be a string or an array.'); 37 | } 38 | 39 | var cwd = process.cwd(); 40 | 41 | // Map each configuration file with their absolute file path. 42 | this.files = files.map(function (file) { 43 | return { 44 | request: file, 45 | path: _path2.default.join(cwd, file) 46 | }; 47 | }); 48 | } 49 | 50 | /** 51 | * Apply this plugin in the compiler build process. 52 | * 53 | * @param {Object} compiler The webpack compiler. 54 | */ 55 | 56 | 57 | _createClass(ConfigPlugin, [{ 58 | key: 'apply', 59 | value: function apply(compiler) { 60 | var _this = this; 61 | 62 | // Merge configuration files with environment variables. 63 | var files = this.load(this.files); 64 | var merged = this.merge(files.map(function (file) { 65 | return file.source; 66 | })); 67 | 68 | // Get the main configuration file. As we merge from right to left then our 69 | // main configuration file is the last in the array. 70 | var mainConfig = this.files[0]; 71 | 72 | // Intercept all resolved modules and look for the specified configuration file. 73 | compiler.plugin('normal-module-factory', function (nmf) { 74 | nmf.plugin('after-resolve', function (data, next) { 75 | var interceptedPath = data.resource; 76 | 77 | // Is this one of our configuration files? 78 | if (interceptedPath === mainConfig.path) { 79 | _this.intercept(data, merged); 80 | } 81 | 82 | // Continue the normal resolution process. 83 | return next(null, data); 84 | }); 85 | }); 86 | } 87 | 88 | /** 89 | * Load one or more configuration files. 90 | * 91 | * @param {Array} files One or more configuration files. 92 | * 93 | * @return {Array} List of configuration file contents. 94 | */ 95 | 96 | }, { 97 | key: 'load', 98 | value: function load(files) { 99 | // Get the path of this directory relative to the CWD. 100 | var cwd = _path2.default.relative(__dirname, process.cwd()); 101 | 102 | // Map files to their contents and return it. 103 | return files.map(function (file) { 104 | try { 105 | // Get the path of the configuration file relative to this file. 106 | var request = _path2.default.join(cwd, file.request); 107 | 108 | // Try to require the configuration file. 109 | file.source = require(request); 110 | return file; 111 | } catch (e) { 112 | throw new Error('Cannot load: ' + _path2.default + '. ' + e.message); 113 | } 114 | }); 115 | } 116 | 117 | /** 118 | * Merge one more configuration objects then merge with any matching 119 | * environment variable. 120 | * 121 | * @param {Array} configs One or more configuration files to merge. 122 | * 123 | * @return {object} The merged configuration. 124 | */ 125 | 126 | }, { 127 | key: 'merge', 128 | value: function merge(configs) { 129 | // Merge configurations together from right to left. 130 | var merged = Object.assign.apply(Object, [{}].concat(_toConsumableArray(configs))); 131 | 132 | // Merge any matching environment variable. 133 | Object.keys(process.env).forEach(function (key) { 134 | if (merged.hasOwnProperty(key)) { 135 | merged[key] = process.env[key]; 136 | } 137 | }); 138 | 139 | return merged; 140 | } 141 | 142 | /** 143 | * Intercept a data chunk replacing all loaders by ours. 144 | * 145 | * @param {Object} data The data chunk. 146 | */ 147 | 148 | }, { 149 | key: 'intercept', 150 | value: function intercept(data, replacement) { 151 | var loader = _path2.default.join(__dirname, 'config-loader.js'); 152 | var json = JSON.stringify(replacement); 153 | data.loaders = [loader + '?' + json]; 154 | } 155 | }]); 156 | 157 | return ConfigPlugin; 158 | }(); 159 | 160 | module.exports = ConfigPlugin; --------------------------------------------------------------------------------