├── test ├── fixtures │ ├── failures │ │ ├── not-found │ │ │ └── .gitkeep │ │ └── bad-file │ │ │ └── webpack.config.js │ ├── formats │ │ ├── yml │ │ │ └── .webpackrc │ │ ├── rc │ │ │ └── .webpackrc │ │ ├── common-js │ │ │ └── webpack.config.js │ │ ├── es6 │ │ │ └── webpack.config.es6 │ │ ├── flow │ │ │ └── webpack.config.flow │ │ └── typescript │ │ │ └── webpack.config.ts │ ├── types │ │ ├── invalid-type │ │ │ └── webpack.config.js │ │ ├── object │ │ │ └── webpack.config.js │ │ ├── schema │ │ │ └── webpack.config.js │ │ ├── array │ │ │ └── webpack.config.js │ │ ├── object-es6 │ │ │ └── webpack.config.js │ │ ├── function-argv │ │ │ └── webpack.config.js │ │ ├── function │ │ │ └── webpack.config.js │ │ ├── promise │ │ │ └── webpack.config.js │ │ ├── function-promise │ │ │ └── webpack.config.js │ │ └── invalid-array │ │ │ └── webpack.config.js │ └── extends │ │ ├── config-b.js │ │ ├── config-c.js │ │ ├── config-d.js │ │ ├── config-multi.js │ │ ├── config-a.js │ │ ├── config-base.js │ │ └── config-filters.js ├── test.js ├── tests │ ├── errors.js │ ├── full.js │ ├── extends.js │ ├── resolve.js │ └── load.js ├── snapshot.js └── __snapshots__ │ └── test.js.snap ├── .eslintignore ├── .gitattributes ├── .prettierrc ├── codecov.yml ├── CHANGELOG.md ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── SUPPORT.md │ ├── FEATURE.md │ ├── DOCS.md │ ├── MODIFICATION.md │ └── BUG.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── CONTRIBUTING.md ├── .gitignore ├── .editorconfig ├── .eslintrc.js ├── lib ├── LoadConfigError.js ├── RequireModuleError.js ├── filters │ ├── plugins.js │ └── rules.js ├── resolve.js ├── index.js ├── extend.js └── load.js ├── .babelrc ├── commitlint.config.js ├── LICENSE ├── package.json ├── docs └── EXTENDS.md ├── .circleci └── config.yml └── README.md /test/fixtures/failures/not-found/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | __snapshots__ 3 | -------------------------------------------------------------------------------- /test/fixtures/formats/yml/.webpackrc: -------------------------------------------------------------------------------- 1 | mode: "yml" 2 | -------------------------------------------------------------------------------- /test/fixtures/formats/rc/.webpackrc: -------------------------------------------------------------------------------- 1 | { 2 | "mode": "rc" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | package-lock.json -diff 2 | * text=auto 3 | bin/* eol=lf -------------------------------------------------------------------------------- /test/fixtures/types/invalid-type/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = 'invalid'; 2 | -------------------------------------------------------------------------------- /test/fixtures/formats/common-js/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'common-js', 3 | }; 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "arrowParens": "always" 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/formats/es6/webpack.config.es6: -------------------------------------------------------------------------------- 1 | const config = { 2 | mode: 'es6', 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /test/fixtures/types/object/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: 'object', 3 | mode: 'development', 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/types/schema/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: 'object', 3 | mode: 'development', 4 | serve: {}, 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/failures/bad-file/webpack.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | bad(); 3 | 4 | module.exports = { 5 | mode: 'common-js', 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixtures/types/array/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | entry: 'array', 4 | }, 5 | { 6 | entry: 'array-b', 7 | }, 8 | ]; 9 | -------------------------------------------------------------------------------- /test/fixtures/types/object-es6/webpack.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | entry: 'object-es6', 3 | mode: 'development', 4 | }; 5 | 6 | export default config; 7 | -------------------------------------------------------------------------------- /test/fixtures/types/function-argv/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (argv) => { 2 | return { 3 | entry: 'function', 4 | mode: argv.mode, 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixtures/types/function/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (/* argv */) => { 2 | return { 3 | entry: 'function', 4 | mode: 'development', 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /test/fixtures/extends/config-b.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | module.exports = { 4 | extends: [join(__dirname, 'config-c.js'), join(__dirname, 'config-d.js')], 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/types/promise/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = new Promise((resolve /* reject */) => { 2 | resolve({ 3 | entry: 'promise', 4 | mode: 'development', 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | branch: master 3 | coverage: 4 | precision: 2 5 | round: down 6 | range: 70...100 7 | status: 8 | project: 'no' 9 | patch: 'yes' 10 | comment: 'off' 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | -------------------------------------------------------------------------------- /test/fixtures/formats/flow/webpack.config.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | type Config = {| 4 | mode: string, 5 | |}; 6 | 7 | const config: Config = { 8 | mode: 'flow', 9 | }; 10 | 11 | module.exports = config; 12 | -------------------------------------------------------------------------------- /test/fixtures/formats/typescript/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { Configuration } from 'webpack'; 3 | 4 | const config: Configuration = { 5 | mode: 'typescript' 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These are the default owners for everything in 2 | # webpack-contrib 3 | @webpack-contrib/org-maintainers 4 | 5 | # Add repository specific users / groups 6 | # below here for libs that are not maintained by the org. 7 | -------------------------------------------------------------------------------- /test/fixtures/types/function-promise/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (/* argv */) => 2 | new Promise((resolve /* reject */) => { 3 | resolve({ 4 | entry: 'function-promise', 5 | mode: 'development', 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | .eslintcache 5 | /coverage 6 | /dist 7 | /local 8 | /reports 9 | /node_modules 10 | .DS_Store 11 | Thumbs.db 12 | .idea 13 | .vscode 14 | *.sublime-project 15 | *.sublime-workspace 16 | .nyc_output 17 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | require('loud-rejection/register'); 2 | 3 | global.expect = require('expect'); 4 | 5 | require('./snapshot'); 6 | 7 | require('./tests/load'); 8 | require('./tests/resolve'); 9 | require('./tests/full'); 10 | require('./tests/errors'); 11 | require('./tests/extends'); 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | insert_final_newline = true 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /test/fixtures/extends/config-c.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'batman', 3 | module: { 4 | rules: [ 5 | { 6 | test: /\*.json$/, 7 | use: [ 8 | { 9 | loader: `json-loader`, 10 | }, 11 | ], 12 | }, 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | plugins: ['prettier'], 4 | extends: ['@webpack-contrib/eslint-config-webpack'], 5 | rules: { 6 | 'prettier/prettier': [ 7 | 'error', 8 | { singleQuote: true, trailingComma: 'es5', arrowParens: 'always' }, 9 | ], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/types/invalid-array/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | entry: './entry-a', 4 | output: { 5 | filename: 'entry-a.bundle.js', 6 | }, 7 | }, 8 | 'this string makes this array of configurations invalid.', 9 | { 10 | entry: './entry-b', 11 | output: { 12 | filename: 'entry-b.bundle.js', 13 | }, 14 | }, 15 | ]; 16 | -------------------------------------------------------------------------------- /lib/LoadConfigError.js: -------------------------------------------------------------------------------- 1 | module.exports = class LoadConfigError extends Error { 2 | constructor(error, configPath) { 3 | super(error.message); 4 | 5 | this.name = 'LoadConfigError'; 6 | 7 | const stack = error.stack.split('\n').slice(1); 8 | stack.unshift(this.toString()); 9 | 10 | this.stack = stack.join('\n'); 11 | this.meta = { configPath }; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /lib/RequireModuleError.js: -------------------------------------------------------------------------------- 1 | module.exports = class RequireModuleError extends Error { 2 | constructor(error, moduleName) { 3 | super(error.message); 4 | 5 | this.name = 'RequireModuleError'; 6 | 7 | const stack = error.stack.split('\n').slice(1); 8 | stack.unshift(this.toString()); 9 | 10 | this.stack = stack.join('\n'); 11 | this.meta = { moduleName }; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /test/fixtures/extends/config-d.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | module.exports = { 4 | name: 'batman', 5 | extends: join(__dirname, 'config-base.js'), 6 | module: { 7 | rules: [ 8 | { 9 | test: /\*.json$/, 10 | use: [ 11 | { 12 | loader: `val-loader`, 13 | }, 14 | ], 15 | }, 16 | ], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/SUPPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🆘 Support, Help, and Advice 3 | about: 👉🏽 Need support, help, or advice? Don't open an issue! Head to StackOverflow or https://gitter.im/webpack/webpack. 4 | 5 | --- 6 | 7 | Hey there! If you need support, help, or advice then this is not the place to ask. 8 | Please visit [StackOverflow](https://stackoverflow.com/questions/tagged/webpack) 9 | or [the Webpack Gitter](https://gitter.im/webpack/webpack) instead. -------------------------------------------------------------------------------- /test/tests/errors.js: -------------------------------------------------------------------------------- 1 | const RequireModuleError = require('../../lib/RequireModuleError'); 2 | 3 | describe('RequireModuleError', () => { 4 | it(`should construct an error`, () => { 5 | const base = new Error('message'); 6 | const error = new RequireModuleError(base, 'test'); 7 | 8 | expect(error).toBeInstanceOf(Error); 9 | expect(error.name).toBe('RequireModuleError'); 10 | expect(error.stack).toContain('errors.js'); 11 | expect(error.meta.moduleName).toBe('test'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /lib/filters/plugins.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | default: (config) => config.plugins, 3 | 4 | constructor: (config) => { 5 | const { plugins } = config; 6 | /* istanbul ignore if */ 7 | if (!plugins || !plugins.length) { 8 | return plugins; 9 | } 10 | 11 | return plugins.reduceRight( 12 | (array, other) => 13 | array.findIndex( 14 | (plugin) => plugin.constructor.name === other.constructor.name 15 | ) < 0 16 | ? [...array, other] 17 | : array, 18 | [] 19 | ); 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "useBuiltIns": true, 7 | "targets": { 8 | "node": "6.9.0" 9 | }, 10 | "exclude": [ 11 | "transform-async-to-generator", 12 | "transform-regenerator" 13 | ] 14 | } 15 | ] 16 | ], 17 | "plugins": [ 18 | [ 19 | "transform-object-rest-spread", 20 | { 21 | "useBuiltIns": true 22 | } 23 | ] 24 | ], 25 | "env": { 26 | "test": { 27 | "presets": [ 28 | "env" 29 | ], 30 | "plugins": [ 31 | "transform-object-rest-spread" 32 | ] 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /test/fixtures/extends/config-multi.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | const { DefinePlugin } = require('webpack'); 4 | 5 | module.exports = [ 6 | { 7 | extends: { 8 | configs: [join(__dirname, 'config-b.js')], 9 | filters: { plugins: 'constructor' }, 10 | }, 11 | name: 'client', 12 | plugins: [ 13 | new DefinePlugin({ 14 | 'process.env.NODE_ENV': '"production"', 15 | }), 16 | ], 17 | }, 18 | { 19 | extends: join(__dirname, 'config-b.js'), 20 | name: 'server', 21 | plugins: [ 22 | new DefinePlugin({ 23 | 'process.env.NODE_ENV': '"production"', 24 | }), 25 | ], 26 | }, 27 | ]; 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature Request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | 17 | 18 | * Operating System: 19 | * Node Version: 20 | * NPM Version: 21 | * webpack Version: 22 | * config-loader Version: 23 | 24 | ### Feature Proposal 25 | 26 | 27 | 28 | ### Feature Use Case -------------------------------------------------------------------------------- /test/fixtures/extends/config-a.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | const { DefinePlugin } = require('webpack'); 4 | 5 | module.exports = { 6 | extends: join(__dirname, 'config-b.js'), 7 | devtool: 'cheap-eval-source-map', 8 | entry: { 9 | client: ['./app-entry.js'], 10 | }, 11 | mode: 'production', 12 | module: { 13 | noParse: /batman/, 14 | rules: [ 15 | { 16 | test: /target-file.js$/, 17 | use: [ 18 | { 19 | loader: `val-loader`, 20 | }, 21 | ], 22 | }, 23 | ], 24 | }, 25 | output: { 26 | filename: '[name].js', 27 | }, 28 | plugins: [ 29 | new DefinePlugin({ 30 | 'process.env.NODE_ENV': '"production"', 31 | }), 32 | ], 33 | }; 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/DOCS.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 📚 Documentation 3 | about: Are the docs lacking or missing something? Do they need some new 🔥 hotness? Tell us here. 4 | 5 | --- 6 | 7 | 17 | 18 | Documentation Is: 19 | 20 | 21 | 22 | - [ ] Missing 23 | - [ ] Needed 24 | - [ ] Confusing 25 | - [ ] Not Sure? 26 | 27 | ### Please Explain in Detail... 28 | 29 | 30 | ### Your Proposal for Changes -------------------------------------------------------------------------------- /lib/resolve.js: -------------------------------------------------------------------------------- 1 | const minimist = require('minimist'); 2 | 3 | const fromFunction = (config, argv) => { 4 | const result = config(argv); 5 | 6 | return Promise.resolve(result); 7 | }; 8 | 9 | const fromObject = (config) => Promise.resolve(config); 10 | 11 | const handlers = { 12 | function: fromFunction, 13 | object: fromObject, 14 | }; 15 | 16 | module.exports = (configSet, argv) => { 17 | const { config, configPath } = configSet; 18 | const type = typeof (config.default || config); 19 | const handler = handlers[type]; 20 | 21 | // eslint-disable-next-line no-param-reassign 22 | argv = argv || minimist(process.argv.slice(2)); 23 | 24 | return handler(config.default || config, argv).then((configResult) => { 25 | return { 26 | config: configResult, 27 | configPath, 28 | }; 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /test/fixtures/extends/config-base.js: -------------------------------------------------------------------------------- 1 | const { DefinePlugin, NamedModulesPlugin } = require('webpack'); 2 | 3 | module.exports = { 4 | devtool: 'source-map', 5 | entry: { 6 | client: ['./base/shared.js'], 7 | server: './app-entry.js', 8 | }, 9 | mode: 'development', 10 | module: { 11 | noParse: /jquery|lodash/, 12 | rules: [ 13 | { 14 | test: /.css$/, 15 | resourceQuery: /inline/, 16 | use: 'url-loader', 17 | }, 18 | { 19 | test: /\.txt$/, 20 | use: 'raw-loader', 21 | }, 22 | ], 23 | }, 24 | output: { 25 | filename: 'bundle.js', 26 | path: `${__dirname}/dist`, 27 | }, 28 | plugins: [ 29 | new DefinePlugin({ 30 | 'process.env.NODE_ENV': '"development"', 31 | }), 32 | new NamedModulesPlugin(), 33 | ], 34 | }; 35 | -------------------------------------------------------------------------------- /lib/filters/rules.js: -------------------------------------------------------------------------------- 1 | const regexEqual = (reg, exp) => 2 | reg instanceof RegExp && 3 | exp instanceof RegExp && 4 | reg.source === exp.source && 5 | reg.global === exp.global && 6 | reg.ignoreCase === exp.ignoreCase && 7 | reg.multiline === exp.multiline; 8 | 9 | module.exports = { 10 | default: (config) => { 11 | const { rules } = config.module; 12 | /* istanbul ignore if */ 13 | if (!rules || !rules.length) { 14 | return rules; 15 | } 16 | 17 | return rules.reduce( 18 | (array, other) => 19 | array.findIndex( 20 | (rule) => 21 | rule.test === other.test || regexEqual(rule.test, other.test) 22 | ) < 0 23 | ? [...array, other] 24 | : array, 25 | [] 26 | ); 27 | }, 28 | 29 | none: (config) => config.module.rules, 30 | }; 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/MODIFICATION.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🔧 Modification Request 3 | about: Would you like something work differently? Have an alternative approach? This is the template for you. 4 | 5 | --- 6 | 7 | 17 | 18 | * Operating System: 19 | * Node Version: 20 | * NPM Version: 21 | * webpack Version: 22 | * config-loader Version: 23 | 24 | 25 | ### Expected Behavior / Situation 26 | 27 | 28 | 29 | ### Actual Behavior / Situation 30 | 31 | 32 | 33 | ### Modification Proposal -------------------------------------------------------------------------------- /test/fixtures/extends/config-filters.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | const { DefinePlugin } = require('webpack'); 4 | 5 | module.exports = { 6 | extends: { 7 | configs: [join(__dirname, 'config-b.js')], 8 | filters: { plugins: 'constructor', rules: 'none' }, 9 | }, 10 | devtool: 'cheap-eval-source-map', 11 | entry: { 12 | client: ['./app-entry.js'], 13 | }, 14 | mode: 'production', 15 | module: { 16 | noParse: /aquaman/, 17 | rules: [ 18 | { 19 | test: /target-file.js$/, 20 | use: [ 21 | { 22 | loader: `val-loader`, 23 | }, 24 | ], 25 | }, 26 | ], 27 | }, 28 | output: { 29 | filename: '[name].js', 30 | }, 31 | plugins: [ 32 | new DefinePlugin({ 33 | 'process.env.NODE_ENV': '"production"', 34 | }), 35 | ], 36 | }; 37 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const Configuration = { 3 | extends: ['@commitlint/config-conventional'], 4 | 5 | rules: { 6 | 'body-leading-blank': [1, 'always'], 7 | 'footer-leading-blank': [1, 'always'], 8 | 'header-max-length': [2, 'always', 72], 9 | 'scope-case': [2, 'always', 'lower-case'], 10 | 'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']], 11 | 'subject-empty': [2, 'never'], 12 | 'subject-full-stop': [2, 'never', '.'], 13 | 'type-case': [2, 'always', 'lower-case'], 14 | 'type-empty': [2, 'never'], 15 | 'type-enum': [2, 'always', [ 16 | 'build', 17 | 'chore', 18 | 'ci', 19 | 'docs', 20 | 'feat', 21 | 'fix', 22 | 'perf', 23 | 'refactor', 24 | 'revert', 25 | 'style', 26 | 'test', 27 | ], 28 | ], 29 | }, 30 | }; 31 | 32 | module.exports = Configuration; 33 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | This PR contains a: 12 | 13 | - [ ] **bugfix** 14 | - [ ] new **feature** 15 | - [ ] **code refactor** 16 | - [ ] **test update** 17 | - [ ] **typo fix** 18 | - [ ] **metadata update** 19 | 20 | ### Motivation / Use-Case 21 | 22 | 27 | 28 | ### Breaking Changes 29 | 30 | 34 | 35 | ### Additional Info 36 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const merge = require('merge-options'); 2 | const webpackLog = require('webpack-log'); 3 | const validate = require('@webpack-contrib/schema-utils'); 4 | const webpackSchema = require('webpack/schemas/WebpackOptions.json'); 5 | 6 | const { extend } = require('./extend'); 7 | const load = require('./load'); 8 | const resolve = require('./resolve'); 9 | 10 | module.exports = (options = {}) => { 11 | webpackLog({ name: 'config', id: 'webpack-config-loader' }); 12 | 13 | const name = 'config-loader'; 14 | const raw = load(options); 15 | const schema = merge({}, options.schema, webpackSchema); 16 | 17 | return resolve(raw).then((result) => { 18 | const { config, configPath } = result; 19 | 20 | return extend(config).then((finalConfig) => { 21 | // finalConfig may be a single Object or it may be an Array[Object] 22 | // for simplicity, concat into an array and treat both types the same. 23 | for (const target of [].concat(finalConfig)) { 24 | validate({ name, schema, target }); 25 | } 26 | return { config: finalConfig, configPath }; 27 | }); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: Something went awry and you'd like to tell us about it. 4 | 5 | --- 6 | 7 | 17 | 18 | * Operating System: 19 | * Node Version: 20 | * NPM Version: 21 | * webpack Version: 22 | * config-loader Version: 23 | 24 | ### Expected Behavior 25 | 26 | 27 | 28 | ### Actual Behavior 29 | 30 | 31 | 32 | ### Code 33 | 34 | ```js 35 | // webpack.config.js 36 | // If your bitchin' code blocks are over 20 lines, please paste a link to a gist 37 | // (https://gist.github.com). 38 | ``` 39 | 40 | ```js 41 | // additional code, HEY YO remove this block if you don't need it 42 | ``` 43 | 44 | ### How Do We Reproduce? 45 | 46 | -------------------------------------------------------------------------------- /test/tests/full.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const loader = require('../../lib/index'); 4 | 5 | const types = ['array', 'function', 'function-promise', 'promise']; 6 | 7 | describe('Full Process', () => { 8 | for (const type of types) { 9 | it(`should fully load config type: ${type}`, (done) => { 10 | const cwd = path.join(__dirname, `../fixtures/types/${type}`); 11 | 12 | const options = { cwd }; 13 | 14 | loader(options).then((result) => { 15 | expect(result.config).toMatchSnapshot(); 16 | done(); 17 | }); 18 | }); 19 | } 20 | 21 | it(`should fully load a config from abs path`, (done) => { 22 | const configPath = path.join( 23 | __dirname, 24 | `../fixtures/types/function-promise/webpack.config.js` 25 | ); 26 | 27 | const options = { configPath }; 28 | 29 | loader(options).then((result) => { 30 | expect(result.config).toMatchSnapshot(); 31 | done(); 32 | }); 33 | }); 34 | 35 | it(`should load with schema option`, (done) => { 36 | const configPath = path.join( 37 | __dirname, 38 | `../fixtures/types/schema/webpack.config.js` 39 | ); 40 | 41 | const options = { 42 | configPath, 43 | schema: { 44 | properties: { 45 | serve: { 46 | additionalProperties: true, 47 | type: 'object', 48 | }, 49 | }, 50 | }, 51 | }; 52 | 53 | loader(options).then((result) => { 54 | expect(result.config).toMatchSnapshot(); 55 | done(); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/tests/extends.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { extend } = require('../../lib/extend'); 4 | const loader = require('../../lib/index'); 5 | 6 | const config = require('../fixtures/extends/config-a'); 7 | const configFilters = require('../fixtures/extends/config-filters'); 8 | const configMulti = require('../fixtures/extends/config-multi'); 9 | 10 | const configPath = path.join(__dirname, '../fixtures/extends/config-a.js'); 11 | const configMultiPath = path.join( 12 | __dirname, 13 | '../fixtures/extends/config-multi.js' 14 | ); 15 | 16 | describe('extends', () => { 17 | it(`should extend`, () => 18 | extend(config).then((result) => { 19 | expect(result).toMatchSnapshot(); 20 | })); 21 | 22 | it(`should extend, use filters`, () => 23 | extend(configFilters).then((result) => { 24 | expect(result).toMatchSnapshot(); 25 | })); 26 | 27 | it(`should extend, use default filters`, () => { 28 | configFilters.extends.filters.plugins = 'unknown'; 29 | configFilters.extends.filters.rules = 'unknown'; 30 | return extend(configFilters).then((result) => { 31 | expect(result).toMatchSnapshot(); 32 | }); 33 | }); 34 | 35 | it(`should extend MultiCompilers`, () => 36 | extend(configMulti).then((result) => { 37 | expect(result).toMatchSnapshot(); 38 | })); 39 | 40 | it(`should load full`, () => 41 | loader({ configPath }).then((result) => { 42 | expect(result.config).toMatchSnapshot(); 43 | })); 44 | 45 | it(`should load and extend MultiCompilers`, () => 46 | loader({ configPath: configMultiPath }).then((result) => { 47 | expect(result.config).toMatchSnapshot(); 48 | })); 49 | }); 50 | -------------------------------------------------------------------------------- /test/tests/resolve.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const resolve = require('../../lib/resolve'); 4 | 5 | const types = ['array', 'function', 'function-promise', 'promise']; 6 | const argv = {}; 7 | 8 | describe('Resolve', () => { 9 | for (const type of types) { 10 | it(`should resolve type: ${type}`, (done) => { 11 | const configPath = path.join( 12 | __dirname, 13 | `../fixtures/types/${type}/webpack.config.js` 14 | ); 15 | // eslint-disable-next-line import/no-dynamic-require, global-require 16 | const config = require(configPath); 17 | resolve({ config, configPath }, argv).then((result) => { 18 | expect(result.config).toMatchSnapshot(); 19 | done(); 20 | }); 21 | }); 22 | } 23 | 24 | it(`should resolve a function config and pass argv`, (done) => { 25 | const configPath = path.join( 26 | __dirname, 27 | `../fixtures/types/function-argv/webpack.config.js` 28 | ); 29 | // eslint-disable-next-line import/no-dynamic-require, global-require 30 | const config = require(configPath); 31 | resolve({ config, configPath }, { mode: 'batman' }).then((result) => { 32 | expect(result.config).toMatchSnapshot(); 33 | done(); 34 | }); 35 | }); 36 | 37 | /* NOTE: This test relies upon --mode=superman being part of the NPM script 38 | running the tests, as it examines the actual process.argv, since argv is 39 | not manually passed to resolve. 40 | */ 41 | it(`should resolve a function config and parse argv`, (done) => { 42 | const configPath = path.join( 43 | __dirname, 44 | `../fixtures/types/function-argv/webpack.config.js` 45 | ); 46 | // eslint-disable-next-line import/no-dynamic-require, global-require 47 | const config = require(configPath); 48 | resolve({ config, configPath }).then((result) => { 49 | expect(result.config).toMatchSnapshot(); 50 | done(); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/snapshot.js: -------------------------------------------------------------------------------- 1 | const { 2 | addSerializer, 3 | SnapshotState, 4 | toMatchSnapshot, 5 | toThrowErrorMatchingSnapshot, 6 | } = require('jest-snapshot'); 7 | const serializer = require('jest-serializer-path'); 8 | const minimist = require('minimist'); 9 | 10 | const argv = minimist(process.argv.slice(2)); 11 | 12 | let current = null; 13 | const cache = {}; 14 | 15 | global.beforeEach(function beforeEach() { 16 | current = this.currentTest; 17 | }); 18 | 19 | function nameTest(test) { 20 | let next = test; 21 | const title = []; 22 | 23 | for (;;) { 24 | if (!next.parent) { 25 | break; 26 | } 27 | 28 | title.push(next.title); 29 | next = next.parent; 30 | } 31 | 32 | let result = title.reverse().join(' > '); 33 | let index = 0; 34 | 35 | // can't yet figure out how jest-snapshot figures out how many snapshots are 36 | // in a single test/it() group, so we'll mimic it for the time being. 37 | if (typeof cache[result] !== 'undefined') { 38 | cache[result] += 1; 39 | index = cache[result]; 40 | } else { 41 | cache[result] = 0; 42 | } 43 | 44 | result = `${result} #${index}`; 45 | 46 | return result; 47 | } 48 | 49 | function match(received) { 50 | const { file } = current; 51 | const snapshotState = new SnapshotState(file, { 52 | updateSnapshot: argv.update || argv.u ? 'all' : 'new', 53 | }); 54 | const matcher = toMatchSnapshot.bind({ 55 | snapshotState, 56 | currentTestName: nameTest(current), 57 | }); 58 | 59 | const result = matcher(received); 60 | 61 | snapshotState.save(); 62 | 63 | return result; 64 | } 65 | 66 | function matchError(received) { 67 | const { file } = current; 68 | const snapshotState = new SnapshotState(file, { 69 | updateSnapshot: argv.update ? 'all' : 'new', 70 | }); 71 | const matcher = toThrowErrorMatchingSnapshot.bind({ 72 | snapshotState, 73 | currentTestName: nameTest(current), 74 | }); 75 | 76 | const result = matcher(received); 77 | 78 | snapshotState.save(); 79 | 80 | return result; 81 | } 82 | 83 | addSerializer(serializer); 84 | 85 | expect.extend({ 86 | toMatchSnapshot: match, 87 | toThrowErrorMatchingSnapshot: matchError, 88 | }); 89 | -------------------------------------------------------------------------------- /test/tests/load.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const webpackLog = require('webpack-log'); 4 | 5 | const LoadConfigError = require('../../lib/LoadConfigError'); 6 | const RequireModuleError = require('../../lib/RequireModuleError'); 7 | 8 | const formats = { 9 | 'common-js': null, 10 | es6: 'babel-register', 11 | flow: 'flow-remove-types/register', 12 | rc: null, 13 | typescript: 'ts-node/register', 14 | yml: null, 15 | }; 16 | 17 | webpackLog({ 18 | name: 'config', 19 | id: 'webpack-config-loader', 20 | level: 'silent', 21 | }); 22 | 23 | const load = require('../../lib/load'); 24 | 25 | describe('Load', () => { 26 | for (const format of Object.keys(formats)) { 27 | it(`should config of type ${format}`, () => { 28 | const req = formats[format]; 29 | const options = { cwd: `./test/fixtures/formats/${format}` }; 30 | 31 | if (req) { 32 | options.require = req; 33 | 34 | if (format === 'es6') { 35 | options.requireOptions = { 36 | extensions: ['.es6'], 37 | }; 38 | } 39 | } 40 | 41 | const result = load(options); 42 | 43 | expect(result.config).toMatchSnapshot(); 44 | }); 45 | } 46 | 47 | it('should throw error for config not found', () => { 48 | const failure = () => { 49 | load({ cwd: `./test/fixtures/formats/not-found` }); 50 | }; 51 | 52 | expect(failure).toThrowError(LoadConfigError); 53 | expect(failure).toThrowErrorMatchingSnapshot(); 54 | }); 55 | 56 | it('should not throw for config not found, but allowed', () => { 57 | const result = load({ 58 | allowMissing: true, 59 | cwd: `./test/fixtures/formats/not-found`, 60 | }); 61 | 62 | expect(result).toMatchSnapshot(); 63 | }); 64 | 65 | it('should throw error for a bad config', () => { 66 | const failure = () => { 67 | load({ cwd: `./test/fixtures/failures/bad-config` }); 68 | }; 69 | 70 | expect(failure).toThrow(); 71 | }); 72 | 73 | it('should throw error for a bad exact config', () => { 74 | const failure = () => { 75 | load({ 76 | configPath: resolve( 77 | `./test/fixtures/failures/bad-config/webpack.config.js` 78 | ), 79 | }); 80 | }; 81 | 82 | expect(failure).toThrowError(LoadConfigError); 83 | expect(failure).toThrowErrorMatchingSnapshot(); 84 | }); 85 | 86 | it('should throw error for a bad require', () => { 87 | const failure = () => { 88 | load({ require: 'batman', cwd: `./test/fixtures/types/object` }); 89 | }; 90 | 91 | expect(failure).toThrowError(RequireModuleError); 92 | expect(failure).toThrowErrorMatchingSnapshot(); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /lib/extend.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const isPlainObj = require('is-plain-obj'); 4 | const merge = require('merge-options').bind({ concatArrays: true }); 5 | 6 | const load = require('./load'); 7 | const resolve = require('./resolve'); 8 | const pluginFilters = require('./filters/plugins'); 9 | const ruleFilters = require('./filters/rules'); 10 | 11 | const loadExtends = (extent, options) => { 12 | let result; 13 | /* istanbul ignore else */ 14 | if (path.isAbsolute(extent)) { 15 | result = load({ allowMissing: true, configPath: extent }); 16 | } else { 17 | // eslint-disable-next-line import/no-dynamic-require, global-require 18 | result = { config: require(extent), configPath: extent }; 19 | } 20 | 21 | // eslint-disable-next-line no-use-before-define 22 | return resolve(result).then(({ config }) => mergeExtend(config, options)); 23 | }; 24 | 25 | const filterPlugins = (config, options = {}) => { 26 | const filterName = options.plugins || 'default'; 27 | const filter = pluginFilters[filterName] || pluginFilters.default; 28 | 29 | return filter(config); 30 | }; 31 | 32 | const filterRules = (config, options = {}) => { 33 | const filterName = options.rules || 'default'; 34 | const filter = ruleFilters[filterName] || ruleFilters.default; 35 | 36 | return filter(config); 37 | }; 38 | 39 | const filterModule = (config, options = {}) => { 40 | const { module } = config; 41 | /* istanbul ignore else */ 42 | if (module) { 43 | module.rules = filterRules(config, options); 44 | } 45 | 46 | return module; 47 | }; 48 | 49 | const filter = (config, options = {}) => { 50 | /* eslint-disable no-param-reassign */ 51 | config.module = filterModule(config, options); 52 | config.plugins = filterPlugins(config, options); 53 | 54 | return config; 55 | }; 56 | 57 | const mergeExtend = (config, options = {}) => { 58 | let { extends: configExtends } = config; 59 | 60 | if (configExtends) { 61 | if (isPlainObj(configExtends)) { 62 | // eslint-disable-next-line no-param-reassign 63 | options = configExtends.filters; 64 | configExtends = configExtends.configs; 65 | } 66 | 67 | configExtends = [].concat(configExtends); 68 | 69 | const result = configExtends.reduceRight( 70 | (prev, conf) => 71 | loadExtends(conf, options).then((extent) => 72 | prev.then((prevConfig) => filter(merge(extent, prevConfig), options)) 73 | ), 74 | Promise.resolve(config) 75 | ); 76 | 77 | return result; 78 | } 79 | 80 | return Promise.resolve(config); 81 | }; 82 | 83 | module.exports = { 84 | extend(config, options) { 85 | const promises = [].concat(config).map((conf) => 86 | mergeExtend(conf, options).then((result) => { 87 | delete result.extends; 88 | return result; 89 | }) 90 | ); 91 | 92 | return Promise.all(promises).then( 93 | (results) => (results.length > 1 ? results : results[0]) 94 | ); 95 | }, 96 | }; 97 | -------------------------------------------------------------------------------- /lib/load.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const cosmiconfig = require('cosmiconfig'); 3 | const resolvePath = require('resolve').sync; 4 | const webpackLog = require('webpack-log'); 5 | 6 | const LoadConfigError = require('./LoadConfigError'); 7 | const RequireModuleError = require('./RequireModuleError'); 8 | 9 | const cwd = process.cwd(); 10 | const { loadJs } = cosmiconfig; 11 | const prefix = 'webpack'; 12 | const cosmicOptions = { 13 | loaders: { 14 | '.es6': loadJs, 15 | '.flow': loadJs, 16 | '.mjs': loadJs, 17 | '.ts': loadJs, 18 | }, 19 | searchPlaces: [ 20 | `${prefix}.config.js`, 21 | `${prefix}.config.es6`, 22 | `${prefix}.config.flow`, 23 | `${prefix}.config.mjs`, 24 | `${prefix}.config.ts`, 25 | `.${prefix}rc`, 26 | 'package.json', 27 | `.${prefix}rc.json`, 28 | `.${prefix}rc.yaml`, 29 | `.${prefix}rc.yml`, 30 | ], 31 | }; 32 | 33 | module.exports = (options = {}) => { 34 | const log = webpackLog({ name: 'config', id: 'webpack-config-loader' }); 35 | const requires = [].concat(options.require).filter((r) => !!r); 36 | 37 | // eslint-disable-next-line no-param-reassign 38 | options = Object.assign({ cwd: process.cwd() }, options); 39 | 40 | for (const module of requires) { 41 | try { 42 | const modulePath = resolvePath(module, { basedir: cwd }); 43 | 44 | log.info(chalk`Requiring the {cyan ${module}} module`); 45 | 46 | if (options.requireOptions) { 47 | const { requireOptions } = options; 48 | // eslint-disable-next-line import/no-dynamic-require, global-require 49 | require(modulePath)(requireOptions); 50 | } else { 51 | // eslint-disable-next-line import/no-dynamic-require, global-require 52 | require(modulePath); 53 | } 54 | } catch (e) { 55 | log.error(chalk`An error occurred while requiring: {grey ${module}}`); 56 | throw new RequireModuleError(e, module); 57 | } 58 | } 59 | 60 | let config = {}; 61 | let { configPath } = options; 62 | 63 | const explorer = cosmiconfig(prefix, cosmicOptions); 64 | 65 | try { 66 | let result; 67 | if (configPath) { 68 | result = explorer.loadSync(configPath) || {}; 69 | } else { 70 | result = explorer.searchSync(options.cwd) || {}; 71 | } 72 | 73 | ({ config, filepath: configPath } = result); 74 | 75 | log.debug(chalk`Found config at {grey ${configPath}}`); 76 | } catch (e) { 77 | /* istanbul ignore else */ 78 | if (configPath) { 79 | log.error(chalk`An error occurred while trying to load {grey ${configPath}} 80 | Did you forget to specify a --require?`); 81 | } else { 82 | log.error(chalk`An error occurred while trying to find a config file 83 | Did you forget to specify a --require?`); 84 | } 85 | throw new LoadConfigError(e, configPath); 86 | } 87 | 88 | if (!configPath && !options.allowMissing) { 89 | // prettier-ignore 90 | log.error(chalk`Unable to load a config from: {grey ${options.cwd}}`); 91 | const e = new Error(`Unable to load a config from: ${options.cwd}`); 92 | throw new LoadConfigError(e, configPath); 93 | } else { 94 | config = config || {}; 95 | configPath = configPath || ''; 96 | } 97 | 98 | return { config, configPath }; 99 | }; 100 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webpack-contrib/config-loader", 3 | "version": "1.2.1", 4 | "description": "A webpack configuration loader", 5 | "license": "MIT", 6 | "repository": "webpack-contrib/config-loader", 7 | "author": "Andrew Powell ", 8 | "homepage": "https://github.com/webpack-contrib/config-loader", 9 | "bugs": "https://github.com/webpack-contrib/config-loader/issues", 10 | "main": "lib/index.js", 11 | "engines": { 12 | "node": ">= 6.9.0 <7.0.0 || >= 8.9.0" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "scripts": { 18 | "commitlint": "commitlint", 19 | "commitmsg": "commitlint -e $GIT_PARAMS", 20 | "lint": "eslint --cache lib test", 21 | "ci:lint:commits": "commitlint --from=${CIRCLE_BRANCH} --to=${CIRCLE_SHA1}", 22 | "lint-staged": "lint-staged", 23 | "release": "standard-version", 24 | "release:ci": "conventional-github-releaser -p angular", 25 | "release:validate": "commitlint --from=$(git describe --tags --abbrev=0) --to=$(git rev-parse HEAD)", 26 | "security": "nsp check", 27 | "test": "mocha test/test.js --exit --mode=superman", 28 | "test:coverage": "mkdir -p coverage && nyc --silent npm test && npm run test:coverage:report", 29 | "test:coverage:report": "nyc report --reporter=lcov --reporter=text-lcov --reporter=json --reporter=clover > coverage/lcov.info", 30 | "ci:lint": "npm run lint && npm run security", 31 | "ci:test": "npm run test -- --runInBand", 32 | "ci:coverage": "npm run test:coverage -- --runInBand", 33 | "defaults": "webpack-defaults" 34 | }, 35 | "files": [ 36 | "lib/", 37 | "index.js" 38 | ], 39 | "peerDependencies": { 40 | "webpack": "^4.3.0" 41 | }, 42 | "dependencies": { 43 | "@webpack-contrib/schema-utils": "^1.0.0-beta.0", 44 | "chalk": "^2.1.0", 45 | "cosmiconfig": "^5.0.2", 46 | "is-plain-obj": "^1.1.0", 47 | "loud-rejection": "^1.6.0", 48 | "merge-options": "^1.0.1", 49 | "minimist": "^1.2.0", 50 | "resolve": "^1.6.0", 51 | "webpack-log": "^1.1.2" 52 | }, 53 | "devDependencies": { 54 | "@commitlint/cli": "^7.0.0", 55 | "@commitlint/config-conventional": "^7.0.1", 56 | "@webpack-contrib/eslint-config-webpack": "^2.0.4", 57 | "babel-cli": "^6.26.0", 58 | "babel-core": "^6.26.0", 59 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 60 | "babel-polyfill": "^6.26.0", 61 | "babel-preset-env": "^1.6.1", 62 | "babel-register": "^6.26.0", 63 | "conventional-github-releaser": "^3.1.2", 64 | "cross-env": "^5.1.4", 65 | "del": "^3.0.0", 66 | "del-cli": "^1.1.0", 67 | "eslint": "^4.19.1", 68 | "eslint-plugin-import": "^2.10.0", 69 | "eslint-plugin-prettier": "^2.6.0", 70 | "esm": "^3.0.15", 71 | "execa": "^0.10.0", 72 | "expect": "^23.2.0", 73 | "flow-remove-types": "^1.2.3", 74 | "husky": "^0.14.3", 75 | "jest-serializer-path": "^0.1.15", 76 | "jest-snapshot": "^23.2.0", 77 | "lint-staged": "^7.2.0", 78 | "memory-fs": "^0.4.1", 79 | "mocha": "^5.0.5", 80 | "nsp": "^3.2.1", 81 | "nyc": "^12.0.2", 82 | "pre-commit": "^1.2.2", 83 | "prettier": "^1.11.1", 84 | "standard-version": "^4.3.0", 85 | "ts-node": "^5.0.1", 86 | "typescript": "^2.8.1", 87 | "webpack": "^4.4.1", 88 | "webpack-defaults": "^2.3.0" 89 | }, 90 | "keywords": [ 91 | "webpack" 92 | ], 93 | "pre-commit": "lint-staged", 94 | "lint-staged": { 95 | "*.js": [ 96 | "eslint --fix", 97 | "git add" 98 | ] 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /docs/EXTENDS.md: -------------------------------------------------------------------------------- 1 | # Extending Configuration Files 2 | 3 | This module supports extending webpack configuration files with 4 | [ESLint-style](https://eslint.org/docs/user-guide/configuring#extending-configuration-files) 5 | `extends` functionality. A configuration file can extend the properties and 6 | collections from base configurations, and are filtered intelligently. 7 | 8 | The extends property value is either: 9 | 10 | - A `String` that specifies; a fully-qualified configuration path, or an NPM 11 | module name, or `require` path. 12 | - An `Array` of `String`. Each additional configuration extends the preceding 13 | configurations in the array. Configurations are extended recursively so a base 14 | configuration can also have an extends property. 15 | - An `Object` containing a `configs` property, which matches the two previously 16 | types, and a `filters` property, which contains an `Object` specifying filter 17 | rules. 18 | 19 | To demonstrate the concept, suppose we had the following bare-bones setup: 20 | 21 | ```js 22 | // base.config.js 23 | module.exports = { 24 | name: 'base', 25 | mode: 'development', 26 | plugins: [...] 27 | } 28 | ``` 29 | 30 | ```js 31 | // webpack.config.js 32 | module.exports = { 33 | extends: path.join(..., 'base-config.js'), 34 | name: 'dev' 35 | } 36 | ``` 37 | 38 | The resulting configuration object will look like: 39 | 40 | ```js 41 | // result 42 | { 43 | name: 'dev', 44 | mode: 'development', 45 | plugins: [...] 46 | } 47 | ``` 48 | 49 | Where `plugins` contains the plugins as defined in the `base.config.js` file. 50 | 51 | ## Multiple Extended Configurations 52 | 53 | There might be situations where we just want to get crazy and extend multiple 54 | base or nested configurations. In that situation, users need only specify them 55 | in the order they should be applied. For example: 56 | 57 | ```js 58 | // webpack.config.js 59 | module.exports = { 60 | extends: [ 61 | path.join(..., 'batcave.config.js'), 62 | path.join(..., 'wayne-tower.config.js'), 63 | ... 64 | ] 65 | }; 66 | ``` 67 | 68 | ## Using a Shareable Configuration Package 69 | 70 | A sharable configuration is an NPM package that exports a configuration object. 71 | Make sure the package has been installed to a directory where `config-loader` 72 | can `require` it. 73 | 74 | To use a sharable configuration, simply set the `extends` property equal to the 75 | name of the package. For example, suppose we have a sharable configuration named 76 | `webpack-config-batcave`. Use of that configuration would look like: 77 | 78 | ```js 79 | // webpack.config.js 80 | module.exports = { 81 | extends: 'webpack-config-batman', 82 | ... 83 | }; 84 | ``` 85 | 86 | ## Filtering Properties and Collections 87 | 88 | Filters are applied to the end-result of an extends-tree, and are applied using 89 | a defined set of rules. If you don't see a filtering rule you need, please 90 | consider [contributing one](../.github/CONTRIBUTING.md). By default those rules are: 91 | 92 | - `rules` → applies to `module.rules`, filtered by the `test` property, 93 | duplicates removed. 94 | - `plugins` → no filtering. All plugins from every configuration are 95 | concatenated. 96 | 97 | Along with the default filters, users may choose to apply alternative filters. 98 | Filters are applied by setting the `extends` property to an `Object` like so: 99 | 100 | ```js 101 | // webpack.config.js 102 | module.exports = { 103 | extends: { 104 | configs: path.join(..., 'base-config.js'), 105 | filters: { plugins: 'constructor' }, 106 | }, 107 | name: 'dev', 108 | } 109 | ``` 110 | 111 | Available filters include: 112 | 113 | ### `plugins` 114 | 115 | Applied to `plugins`. 116 | 117 | Rules: 118 | - `'default'` → no filtering. All plugins from every configuration are 119 | concatenated. 120 | - `'constructor'` → filtered buy the plugin `constructor.name` property, duplicates 121 | removed. 122 | 123 | ### `rules` 124 | 125 | Applied to `module.rules`. 126 | 127 | Rules: 128 | - `'default'` → filtered by the `test` property, duplicates removed. 129 | - `'none'` → no filtering, allows all aggregated rules from all extended 130 | configurations. 131 | 132 | _Note: [Contributions](../.github/CONTRIBUTING.md) of, and suggestions for new 133 | filters are welcome!_ 134 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | unit_tests: &unit_tests 2 | steps: 3 | - checkout 4 | - restore_cache: 5 | key: dependency-cache-{{ checksum "package-lock.json" }} 6 | - run: 7 | name: NPM Rebuild 8 | command: npm install 9 | - run: 10 | name: Run unit tests. 11 | command: npm run ci:test 12 | canary_tests: &canary_tests 13 | steps: 14 | - checkout 15 | - restore_cache: 16 | key: dependency-cache-{{ checksum "package-lock.json" }} 17 | - run: 18 | name: NPM Rebuild 19 | command: npm install 20 | - run: 21 | name: Install Webpack Canary 22 | command: npm i --no-save webpack@next 23 | - run: 24 | name: Run unit tests. 25 | command: if [[ $(compver --name webpack --gte next --lt latest) < 1 ]] ; then printf "Next is older than Latest - Skipping Canary Suite"; else npm run ci:test ; fi 26 | 27 | version: 2 28 | jobs: 29 | dependency_cache: 30 | docker: 31 | - image: webpackcontrib/circleci-node-base:latest 32 | steps: 33 | - checkout 34 | - restore_cache: 35 | key: dependency-cache-{{ checksum "package-lock.json" }} 36 | - run: 37 | name: Install Dependencies 38 | command: npm install 39 | - save_cache: 40 | key: dependency-cache-{{ checksum "package-lock.json" }} 41 | paths: 42 | - ./node_modules 43 | 44 | node8-latest: 45 | docker: 46 | - image: webpackcontrib/circleci-node8:latest 47 | steps: 48 | - checkout 49 | - restore_cache: 50 | key: dependency-cache-{{ checksum "package-lock.json" }} 51 | - run: 52 | name: NPM Rebuild 53 | command: npm install 54 | - run: 55 | name: Run unit tests. 56 | command: npm run ci:coverage 57 | - run: 58 | name: Submit coverage data to codecov. 59 | command: bash <(curl -s https://codecov.io/bash) 60 | when: on_success 61 | node6-latest: 62 | docker: 63 | - image: webpackcontrib/circleci-node6:latest 64 | <<: *unit_tests 65 | node9-latest: 66 | docker: 67 | - image: webpackcontrib/circleci-node9:latest 68 | <<: *unit_tests 69 | node8-canary: 70 | docker: 71 | - image: webpackcontrib/circleci-node8:latest 72 | <<: *canary_tests 73 | analysis: 74 | docker: 75 | - image: webpackcontrib/circleci-node-base:latest 76 | steps: 77 | - checkout 78 | - restore_cache: 79 | key: dependency-cache-{{ checksum "package-lock.json" }} 80 | - run: 81 | name: NPM Rebuild 82 | command: npm install 83 | - run: 84 | name: Run linting. 85 | command: npm run lint 86 | - run: 87 | name: Run NSP Security Check. 88 | command: npm run security 89 | - run: 90 | name: Validate Commit Messages 91 | command: npm run ci:lint:commits 92 | publish: 93 | docker: 94 | - image: webpackcontrib/circleci-node-base:latest 95 | steps: 96 | - checkout 97 | - restore_cache: 98 | key: dependency-cache-{{ checksum "package-lock.json" }} 99 | - run: 100 | name: NPM Rebuild 101 | command: npm install 102 | # - run: 103 | # name: Validate Commit Messages 104 | # command: npm run release:validate 105 | - run: 106 | name: Publish to NPM 107 | command: printf "noop running conventional-github-releaser" 108 | 109 | version: 2.0 110 | workflows: 111 | version: 2 112 | validate-publish: 113 | jobs: 114 | - dependency_cache 115 | - node6-latest: 116 | requires: 117 | - dependency_cache 118 | filters: 119 | tags: 120 | only: /.*/ 121 | - analysis: 122 | requires: 123 | - dependency_cache 124 | filters: 125 | tags: 126 | only: /.*/ 127 | - node8-latest: 128 | requires: 129 | - analysis 130 | - node6-latest 131 | filters: 132 | tags: 133 | only: /.*/ 134 | - node9-latest: 135 | requires: 136 | - analysis 137 | - node6-latest 138 | filters: 139 | tags: 140 | only: /.*/ 141 | - node8-canary: 142 | requires: 143 | - analysis 144 | - node6-latest 145 | filters: 146 | tags: 147 | only: /.*/ 148 | - publish: 149 | requires: 150 | - node8-latest 151 | - node8-canary 152 | - node9-latest 153 | filters: 154 | branches: 155 | only: 156 | - master 157 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing in @webpack-contrib 2 | 3 | We'd always love contributions to further improve the webpack / webpack-contrib ecosystem! 4 | Here are the guidelines we'd like you to follow: 5 | 6 | * [Questions and Problems](#question) 7 | * [Issues and Bugs](#issue) 8 | * [Feature Requests](#feature) 9 | * [Pull Request Submission Guidelines](#submit-pr) 10 | * [Commit Message Conventions](#commit) 11 | 12 | ### Got a Question or Problem? 13 | 14 | Please submit support requests and questions to StackOverflow using the tag [[webpack]](http://stackoverflow.com/tags/webpack). 15 | StackOverflow is better suited for this kind of support though you may also inquire in [Webpack Gitter](https://gitter.im/webpack/webpack). 16 | The issue tracker is for bug reports and feature discussions. 17 | 18 | ### Found an Issue or Bug? 19 | 20 | Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available. 21 | 22 | We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we ask that you to provide a minimal reproduction scenario (github repo or failing test case). Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions like: 23 | 24 | - version of Webpack used 25 | - version of the loader / plugin you are creating a bug report for 26 | - the use-case that fails 27 | 28 | A minimal reproduce scenario allows us to quickly confirm a bug (or point out config problems) as well as confirm that we are fixing the right problem. 29 | 30 | We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it. 31 | 32 | Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced. 33 | 34 | ### Feature Requests? 35 | 36 | You can *request* a new feature by creating an issue on Github. 37 | 38 | If you would like to *implement* a new feature, please submit an issue with a proposal for your work `first`, to be sure that particular makes sense for the project. 39 | 40 | ### Pull Request Submission Guidelines 41 | 42 | Before you submit your Pull Request (PR) consider the following guidelines: 43 | 44 | - Search Github for an open or closed PR that relates to your submission. You don't want to duplicate effort. 45 | - Commit your changes using a descriptive commit message that follows our [commit message conventions](#commit). Adherence to these conventions is necessary because release notes are automatically generated from these messages. 46 | - Fill out our `Pull Request Template`. Your pull request will not be considered if it is ignored. 47 | - Please sign the `Contributor License Agreement (CLA)` when a pull request is opened. We cannot accept your pull request without this. Make sure you sign with the primary email address associated with your local / github account. 48 | 49 | ### Webpack Contrib Commit Conventions 50 | 51 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special 52 | format that includes a **type**, a **scope** and a **subject**: 53 | 54 | ``` 55 | (): 56 | 57 | 58 | 59 |