diff --git a/README.md b/README.md index c8052d8..a5d0778 100644 --- a/README.md +++ b/README.md @@ -201,8 +201,11 @@ entries() ```js // Provide an object which maps its properties and values // into the backing Map as keys and values. +// You can also provide an array to the second argument +// for property names to omit from being merged. // obj: Object -merge(obj) +// omit: Optional Array +merge(obj, omit) ``` ```js diff --git a/src/ChainedMap.js b/src/ChainedMap.js index 72acb9c..da4a9ca 100644 --- a/src/ChainedMap.js +++ b/src/ChainedMap.js @@ -1,3 +1,4 @@ +const merge = require('deepmerge'); const Chainable = require('./Chainable'); module.exports = class extends Chainable { @@ -53,8 +54,23 @@ module.exports = class extends Chainable { return this; } - merge(obj) { - Object.keys(obj).forEach(key => this.set(key, obj[key])); + merge(obj, omit = []) { + Object + .keys(obj) + .forEach(key => { + if (omit.includes(key)) { + return; + } + + const value = obj[key]; + + if ((!Array.isArray(value) && typeof value !== 'object') || value === null || !this.has(key)) { + this.set(key, value); + } else { + this.set(key, merge(this.get(key), value)); + } + }); + return this; } diff --git a/src/Config.js b/src/Config.js index ee2c779..8089dea 100644 --- a/src/Config.js +++ b/src/Config.js @@ -74,41 +74,35 @@ module.exports = class extends ChainedMap { })); } - merge(obj = {}) { - Object - .keys(obj) - .forEach(key => { - const value = obj[key]; + merge(obj = {}, omit = []) { + const omissions = [ + 'node', + 'output', + 'resolve', + 'resolveLoader', + 'devServer', + 'performance', + 'module' + ]; - switch (key) { - case 'node': - case 'output': - case 'resolve': - case 'resolveLoader': - case 'devServer': - case 'performance': - case 'module': { - return this[key].merge(value); - } - - case 'entry': { - return Object - .keys(value) - .forEach(name => this.entry(name).merge(value[name])); - } + if (!omit.includes('entry') && 'entry' in obj) { + Object + .keys(obj.entry) + .forEach(name => this.entry(name).merge(obj.entry[name])); + } - case 'plugin': { - return Object - .keys(value) - .forEach(name => this.plugin(name).merge(value[name])); - } + if (!omit.includes('plugin') && 'plugin' in obj) { + Object + .keys(obj.plugin) + .forEach(name => this.plugin(name).merge(obj.plugin[name])); + } - default: { - this.set(key, value); - } - } - }); + omissions.forEach(key => { + if (!omit.includes(key) && key in obj) { + this[key].merge(obj[key]); + } + }); - return this; + return super.merge(obj, [...omit, ...omissions, 'entry', 'plugin']); } }; diff --git a/src/DevServer.js b/src/DevServer.js index 4a84842..a960a9a 100644 --- a/src/DevServer.js +++ b/src/DevServer.js @@ -54,27 +54,11 @@ module.exports = class extends ChainedMap { }, this.entries() || {})); } - merge(obj) { - Object - .keys(obj) - .forEach(key => { - const value = obj[key]; + merge(obj, omit = []) { + if (!omit.includes('allowedHosts') && 'allowedHosts' in obj) { + this.allowedHosts.merge(obj.allowedHosts); + } - switch (key) { - case 'allowedHosts': { - return this[key].merge(value); - } - - default: { - if (this.has(key)) { - this.set(key, merge(this.get(key), value)); - } else { - this.set(key, value); - } - } - } - }); - - return this; + return super.merge(obj, ['allowedHosts']); } }; diff --git a/src/Module.js b/src/Module.js index d31b273..3d2af64 100644 --- a/src/Module.js +++ b/src/Module.js @@ -22,25 +22,13 @@ module.exports = class extends ChainedMap { })); } - merge(obj) { - Object - .keys(obj) - .forEach(key => { - const value = obj[key]; - - switch (key) { - case 'rule': { - return Object - .keys(value) - .forEach(name => this.rule(name).merge(value[name])); - } - - default: { - this.set(key, value); - } - } - }); + merge(obj, omit = []) { + if (!omit.includes('rule') && 'rule' in obj) { + Object + .keys(obj.rule) + .forEach(name => this.rule(name).merge(obj.rule[name])); + } - return this; + return super.merge(obj, ['rule']); } }; diff --git a/src/Plugin.js b/src/Plugin.js index e9fb212..301e171 100644 --- a/src/Plugin.js +++ b/src/Plugin.js @@ -19,16 +19,16 @@ module.exports = class extends ChainedMap { return this; } - merge(obj) { - if (obj.plugin) { + merge(obj, omit = []) { + if ('plugin' in obj) { this.set('plugin', obj.plugin); } - if (obj.args) { + if ('args' in obj) { this.set('args', obj.args); } - return this; + return super.merge(obj, [...omit, 'args', 'plugin']) } toConfig() { diff --git a/src/Resolve.js b/src/Resolve.js index acc56da..85ae880 100644 --- a/src/Resolve.js +++ b/src/Resolve.js @@ -45,34 +45,24 @@ module.exports = class extends ChainedMap { })); } - merge(obj) { - Object - .keys(obj) - .forEach(key => { - const value = obj[key]; + merge(obj, omit = []) { + const omissions = [ + 'alias', + 'aliasFields', + 'descriptionFiles', + 'extensions', + 'mainFields', + 'mainFiles', + 'modules', + 'plugins' + ]; - switch (key) { - case 'alias': - case 'aliasFields': - case 'descriptionFiles': - case 'extensions': - case 'mainFields': - case 'mainFiles': - case 'modules': - case 'plugins': { - return this[key].merge(value); - } + omissions.forEach(key => { + if (!omit.includes(key) && key in obj) { + this[key].merge(obj[key]); + } + }); - default: { - if (this.has(key)) { - this.set(key, merge(this.get(key), value)); - } else { - this.set(key, value); - } - } - } - }); - - return this; + return super.merge(obj, [...omit, ...omissions]); } }; diff --git a/src/ResolveLoader.js b/src/ResolveLoader.js index 6f1ce92..ffe230a 100644 --- a/src/ResolveLoader.js +++ b/src/ResolveLoader.js @@ -20,30 +20,20 @@ module.exports = class extends ChainedMap { }, this.entries() || {})); } - merge(obj) { - Object - .keys(obj) - .forEach(key => { - const value = obj[key]; + merge(obj, omit = []) { + const omissions = [ + 'extensions', + 'modules', + 'moduleExtensions', + 'packageMains' + ]; - switch (key) { - case 'extensions': - case 'modules': - case 'moduleExtensions': - case 'packageMains': { - return this[key].merge(value); - } + omissions.forEach(key => { + if (!omit.includes(key) && key in obj) { + this[key].merge(obj[key]); + } + }); - default: { - if (this.has(key)) { - this.set(key, merge(this.get(key), value)); - } else { - this.set(key, value); - } - } - } - }); - - return this; + return super.merge(obj, [...omit, ...omissions]); } }; diff --git a/src/Rule.js b/src/Rule.js index edbfe95..09700c9 100644 --- a/src/Rule.js +++ b/src/Rule.js @@ -40,45 +40,36 @@ module.exports = class Rule extends ChainedMap { return this.clean(Object.assign(this.entries() || {}, { include: this.include.values(), exclude: this.exclude.values(), - oneOf: this.oneOfs.values().map(r => r.toConfig()), + oneOf: this.oneOfs.values().map(oneOf => oneOf.toConfig()), use: this.uses.values().map(use => use.toConfig()) })); } - merge(obj) { - Object - .keys(obj) - .forEach(key => { - const value = obj[key]; - - switch (key) { - case 'include': - case 'exclude': { - return this[key].merge(value); - } + merge(obj, omit = []) { + if (!omit.includes('include') && 'include' in obj) { + this.include.merge(obj.include); + } - case 'use': { - return Object - .keys(value) - .forEach(name => this.use(name).merge(value[name])); - } + if (!omit.includes('exclude') && 'exclude' in obj) { + this.exclude.merge(obj.exclude); + } - case 'oneOf': { - return Object - .keys(value) - .forEach(name => this.oneOf(name).merge(value[name])) - } + if (!omit.includes('use') && 'use' in obj) { + Object + .keys(obj.use) + .forEach(name => this.use(name).merge(obj.use[name])); + } - case 'test': { - return this.test(value instanceof RegExp ? value : new RegExp(value)); - } + if (!omit.includes('oneOf') && 'oneOf' in obj) { + Object + .keys(obj.oneOf) + .forEach(name => this.oneOf(name).merge(obj.oneOf[name])) + } - default: { - this.set(key, value); - } - } - }); + if (!omit.includes('test') && 'test' in obj) { + this.test(obj.test instanceof RegExp ? obj.test : new RegExp(obj.test)); + } - return this; + return super.merge(obj, [...omit, 'include', 'exclude', 'use', 'oneOf', 'test']); } }; diff --git a/src/Use.js b/src/Use.js index dd131a1..e9218cf 100644 --- a/src/Use.js +++ b/src/Use.js @@ -12,16 +12,16 @@ module.exports = class extends ChainedMap { return this; } - merge(obj) { - if (obj.loader) { + merge(obj, omit = []) { + if (!omit.includes('loader') && 'loader' in obj) { this.loader(obj.loader); } - if (obj.options) { + if (!omit.includes('options') && 'options' in obj) { this.options(merge(this.store.get('options') || {}, obj.options)); } - return this; + return super.merge(obj, [...omit, 'loader', 'options']); } toConfig() { diff --git a/test/ChainedMap.js b/test/ChainedMap.js index d8e99cb..214f18d 100644 --- a/test/ChainedMap.js +++ b/test/ChainedMap.js @@ -118,6 +118,15 @@ test('merge with overriding values', t => { t.deepEqual(map.entries(), { a: 'alpha', b: 'beta', c: 'gamma' }); }); +test('merge with omitting keys', t => { + const map = new ChainedMap(); + const obj = { a: 'alpha', b: 'beta', c: 'gamma'}; + + map.merge(obj, ['b']); + + t.deepEqual(map.entries(), { a: 'alpha', c: 'gamma' }); +}); + test('when true', t => { const map = new ChainedMap(); const right = instance => { diff --git a/test/Resolve.js b/test/Resolve.js index 0f28bac..24f6069 100644 --- a/test/Resolve.js +++ b/test/Resolve.js @@ -83,3 +83,24 @@ test('merge with values', t => { alias: { React: 'src/react', ReactDOM: 'src/react-dom' } }); }); + +test('merge with omit', t => { + const resolve = new Resolve(); + + resolve + .modules.add('src').end() + .extensions.add('.js').end() + .alias.set('React', 'src/react'); + + resolve.merge({ + modules: ['dist'], + extensions: ['.jsx'], + alias: { ReactDOM: 'src/react-dom' } + }, ['alias']); + + t.deepEqual(resolve.toConfig(), { + modules: ['src', 'dist'], + extensions: ['.js', '.jsx'], + alias: { React: 'src/react' } + }); +}); diff --git a/test/ResolveLoader.js b/test/ResolveLoader.js index ef55838..c75f134 100644 --- a/test/ResolveLoader.js +++ b/test/ResolveLoader.js @@ -64,3 +64,21 @@ test('merge with values', t => { moduleExtensions: ['-loader', '-fake'] }); }); + +test('merge with omit', t => { + const resolveLoader = new ResolveLoader(); + + resolveLoader + .modules.add('src').end() + .moduleExtensions.add('-loader'); + + resolveLoader.merge({ + modules: ['dist'], + moduleExtensions: ['-fake'] + }, ['moduleExtensions']); + + t.deepEqual(resolveLoader.toConfig(), { + modules: ['src', 'dist'], + moduleExtensions: ['-loader'] + }); +}); diff --git a/test/Rule.js b/test/Rule.js index 026ec83..22d010c 100644 --- a/test/Rule.js +++ b/test/Rule.js @@ -216,3 +216,52 @@ test('merge with values', t => { }] }); }); + +test('merge with omit', t => { + const rule = new Rule(); + + rule + .test(/\.js$/) + .post() + .include.add('gamma').add('delta').end() + .use('babel') + .loader('babel-loader') + .options({ presets: ['alpha'] }); + + rule.merge({ + test: /\.jsx$/, + enforce: 'pre', + include: ['alpha', 'beta'], + exclude: ['alpha', 'beta'], + oneOf: { + inline: { + resourceQuery: /inline/, + use: { + url: { + loader: 'url-loader' + } + } + } + }, + use: { + babel: { + options: { + presets: ['beta'] + } + } + } + }, ['use', 'oneOf']); + + t.deepEqual(rule.toConfig(), { + test: /\.jsx$/, + enforce: 'pre', + include: ['gamma', 'delta', 'alpha', 'beta'], + exclude: ['alpha', 'beta'], + use: [{ + loader: 'babel-loader', + options: { + presets: ['alpha'] + } + }] + }); +});