Skip to content
This repository has been archived by the owner on Feb 18, 2024. It is now read-only.

Support Config.toString() with name hints #53

Merged
merged 6 commits into from
May 15, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"changelog": "changelog mozilla-neutrino/webpack-chain all --markdown > CHANGELOG.md"
},
"dependencies": {
"deepmerge": "^1.5.2"
"deepmerge": "^1.5.2",
"javascript-stringify": "^1.6.0"
},
"devDependencies": {
"ava": "^0.25.0",
Expand Down
51 changes: 50 additions & 1 deletion src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ module.exports = class extends ChainedMap {

plugin(name) {
if (!this.plugins.has(name)) {
this.plugins.set(name, new Plugin(this));
this.plugins.set(name, new Plugin(this, name));
}

return this.plugins.get(name);
Expand All @@ -78,6 +78,55 @@ module.exports = class extends ChainedMap {
}));
}

toString({
verbose = false,
configPrefix = 'config'
} = {}) {
const stringify = require('javascript-stringify');

const config = this.toConfig();

return stringify(config, (value, indent, stringify) => {
// improve plugin output
if (value && value.__pluginName) {
const prefix = `/* ${configPrefix}.plugin('${value.__pluginName}') */\n`;
const constructorName = value.__pluginConstructorName;

if (constructorName) {
// get correct indentation for args by stringifying the args array and
// discarding the square brackets.
const args = stringify(value.__pluginArgs).slice(1, -1);
return prefix + `new ${constructorName}(${args})`;
} else {
return prefix + stringify({ args: value.__pluginArgs || [] });
Copy link
Member

@edmorley edmorley May 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there known cases where this path is taken, or is it for defensive purposes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly defensive when I found out about the case of copy-webpack-plugin, but this is probably unnecessary as a plugin has to be new-able and thus must have a name.

}
}

// improve rule/use output
if (value && value.__ruleNames) {
const prefix = `/* ${configPrefix}.module.rule('${
value.__ruleNames[0]
}')${
value.__ruleNames.slice(1).map(r => `.oneOf('${r}')`).join('')
}${
value.__useName ? `.use('${value.__useName}')` : ``
} */\n`;
return prefix + stringify(value);
}

// shorten long functions
if (typeof value === 'function') {
if (value.__expression) {
return value.__expression;
} else if (!verbose && value.toString().length > 100) {
return `function () { /* omitted long function */ }`;
}
}

return stringify(value);
}, 2);
}

merge(obj = {}, omit = []) {
const omissions = [
'node',
Expand Down
4 changes: 2 additions & 2 deletions src/Module.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ module.exports = class extends ChainedMap {

defaultRule(name) {
if (!this.defaultRules.has(name)) {
this.defaultRules.set(name, new Rule(this));
this.defaultRules.set(name, new Rule(this, name));
}

return this.defaultRules.get(name);
}

rule(name) {
if (!this.rules.has(name)) {
this.rules.set(name, new Rule(this));
this.rules.set(name, new Rule(this, name));
}

return this.rules.get(name);
Expand Down
17 changes: 15 additions & 2 deletions src/Plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ const ChainedMap = require('./ChainedMap');
const Orderable = require('./Orderable');

module.exports = Orderable(class extends ChainedMap {
constructor(parent) {
constructor(parent, name) {
super(parent);
this.name = name;
this.extend(['init']);

this.init((Plugin, args = []) => new Plugin(...args));
Expand Down Expand Up @@ -34,7 +35,19 @@ module.exports = Orderable(class extends ChainedMap {

toConfig() {
const init = this.get('init');
const plugin = this.get('plugin');
const constructorName = plugin.__expression
? `(${plugin.__expression})`
: plugin.name;

return init(this.get('plugin'), this.get('args'));
const config = init(this.get('plugin'), this.get('args'));

Object.defineProperties(config, {
__pluginName: { value: this.name },
__pluginArgs: { value: this.get('args') },
__pluginConstructorName: { value: constructorName }
});

return config;
}
});
2 changes: 1 addition & 1 deletion src/Resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module.exports = class extends ChainedMap {

plugin(name) {
if (!this.plugins.has(name)) {
this.plugins.set(name, new Plugin(this));
this.plugins.set(name, new Plugin(this, name));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test for this one to test/Resolve.js? :-)

}

return this.plugins.get(name);
Expand Down
23 changes: 19 additions & 4 deletions src/Rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@ const ChainedSet = require('./ChainedSet');
const Use = require('./Use');

module.exports = class Rule extends ChainedMap {
constructor(parent) {
constructor(parent, name) {
super(parent);
this.name = name;
this.names = [];

let rule = this;
while (rule instanceof Rule) {
this.names.unshift(rule.name);
rule = rule.parent;
}

this.uses = new ChainedMap(this);
this.include = new ChainedSet(this);
this.exclude = new ChainedSet(this);
Expand All @@ -23,15 +32,15 @@ module.exports = class Rule extends ChainedMap {

use(name) {
if (!this.uses.has(name)) {
this.uses.set(name, new Use(this));
this.uses.set(name, new Use(this, name));
}

return this.uses.get(name);
}

oneOf(name) {
if (!this.oneOfs.has(name)) {
this.oneOfs.set(name, new Rule(this));
this.oneOfs.set(name, new Rule(this, name));
}

return this.oneOfs.get(name);
Expand All @@ -46,12 +55,18 @@ module.exports = class Rule extends ChainedMap {
}

toConfig() {
return this.clean(Object.assign(this.entries() || {}, {
const config = this.clean(Object.assign(this.entries() || {}, {
include: this.include.values(),
exclude: this.exclude.values(),
oneOf: this.oneOfs.values().map(oneOf => oneOf.toConfig()),
use: this.uses.values().map(use => use.toConfig())
}));

Object.defineProperties(config, {
__ruleNames: { value: this.names }
});

return config;
}

merge(obj, omit = []) {
Expand Down
12 changes: 10 additions & 2 deletions src/Use.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ const Orderable = require('./Orderable');
const merge = require('deepmerge');

module.exports = Orderable(class extends ChainedMap {
constructor(parent) {
constructor(parent, name) {
super(parent);
this.name = name;
this.extend(['loader', 'options']);
}

Expand All @@ -26,6 +27,13 @@ module.exports = Orderable(class extends ChainedMap {
}

toConfig() {
return this.clean(this.entries() || {});
const config = this.clean(this.entries() || {});

Object.defineProperties(config, {
__useName: { value: this.name },
__ruleNames: { value: this.parent && this.parent.names }
});

return config;
}
});
112 changes: 112 additions & 0 deletions test/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,115 @@ test('validate with values', t => {

t.is(errors.length, 0);
});

test('toString', t => {
const config = new Config();

config
.module
.rule('alpha')
.oneOf('beta')
.use('babel')
.loader('babel-loader');

class FooPlugin {}
FooPlugin.__expression = `require('foo-plugin')`;

config
.plugin('gamma')
.use(FooPlugin)
.end()
.plugin('delta')
.use(class BarPlugin {}, ['bar'])
.end()
.plugin('epsilon')
.use(class BazPlugin {}, [{ n: 1 }, [2, 3]])

const string = config.toString();

t.is(config.toString().trim(), `
{
module: {
rules: [
/* config.module.rule('alpha') */
{
oneOf: [
/* config.module.rule('alpha').oneOf('beta') */
{
use: [
/* config.module.rule('alpha').oneOf('beta').use('babel') */
{
loader: 'babel-loader'
}
]
}
]
}
]
},
plugins: [
/* config.plugin('gamma') */
new (require('foo-plugin'))(),
/* config.plugin('delta') */
new BarPlugin(
'bar'
),
/* config.plugin('epsilon') */
new BazPlugin(
{
n: 1
},
[
2,
3
]
)
]
}
`.trim())
});

test('toString for functions with custom expression', t => {
const fn = function foo () {};
fn.__expression = `require('foo')`;

const config = new Config();

config
.module
.rule('alpha')
.include
.add(fn);

t.is(config.toString().trim(), `
{
module: {
rules: [
/* config.module.rule('alpha') */
{
include: [
require('foo')
]
}
]
}
}
`.trim());
});

test('toString with custom prefix', t => {
const config = new Config();

config
.plugin('foo')
.use(class TestPlugin {});

t.is(config.toString({ configPrefix: 'neutrino.config' }).trim(), `
{
plugins: [
/* neutrino.config.plugin('foo') */
new TestPlugin()
]
}
`.trim());
});
22 changes: 19 additions & 3 deletions test/Plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,28 @@ test('init', t => {
});

test('toConfig', t => {
const plugin = new Plugin();
const plugin = new Plugin(null, 'gamma');

plugin.use(StringifyPlugin);
plugin.use(StringifyPlugin, ['delta']);

const initialized = plugin.toConfig();

t.true(initialized instanceof StringifyPlugin);
t.deepEqual(initialized.values, []);
t.deepEqual(initialized.values, ['delta']);
t.is(initialized.__pluginName, 'gamma');
t.deepEqual(initialized.__pluginArgs, ['delta']);
t.is(initialized.__pluginConstructorName, 'StringifyPlugin');
});

test('toConfig with custom expression', t => {
const plugin = new Plugin(null, 'gamma');

class TestPlugin {}
TestPlugin.__expression = `require('my-plugin')`;

plugin.use(TestPlugin);

const initialized = plugin.toConfig();

t.is(initialized.__pluginConstructorName, `(require('my-plugin'))`);
});
9 changes: 9 additions & 0 deletions test/Resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,12 @@ test('merge with omit', t => {
alias: { React: 'src/react' }
});
});

test('plugin with name', t => {
const resolve = new Resolve();

resolve
.plugin('alpha');

t.is(resolve.plugins.get('alpha').name, 'alpha');
});
10 changes: 10 additions & 0 deletions test/Rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ test('toConfig empty', t => {
t.deepEqual(rule.toConfig(), {});
});

test('toConfig with name', t => {
const parent = new Rule(null, 'alpha');
const child = parent.oneOf('beta');
const grandChild = child.oneOf('gamma')

t.deepEqual(parent.toConfig().__ruleNames, ['alpha']);
t.deepEqual(child.toConfig().__ruleNames, ['alpha', 'beta']);
t.deepEqual(grandChild.toConfig().__ruleNames, ['alpha', 'beta', 'gamma']);
});

test('toConfig with values', t => {
const rule = new Rule();

Expand Down
Loading