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

Commit

Permalink
Support specifying plugins by path (#102)
Browse files Browse the repository at this point in the history
This allows the expensive require()s to be skipped in cases where the
plugin or webpack configuration won't end up being used. For example,
using this feature in Neutrino reduces the overhead of dynamically
generating `.eslintrc.js` from 1800ms to 250ms.

As an added bonus, plugins specified by path will also have their
`require()` statement generated automatically when using `toString()`,
saving the need for callers to manually do so using `__expression`.

The plugin path is passed back to `toString()` separately, since it
needs stringifying to ensure special characters are escaped (such as
the backslashes in Windows-style paths) - and I'd prefer to avoid
importing `javascript-stringify` outside of `toString()`.
  • Loading branch information
edmorley committed Sep 12, 2018
1 parent c4d5e2a commit fc8ce23
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 8 deletions.
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -609,13 +609,16 @@ config
.use(WebpackPlugin, args)
// Examples
config
.plugin('hot')
.use(webpack.HotModuleReplacementPlugin);
// Plugins can also be specified by their path, allowing the expensive require()s to be
// skipped in cases where the plugin or webpack configuration won't end up being used.
config
.plugin('env')
.use(webpack.EnvironmentPlugin, ['NODE_ENV']);
.use(require.resolve('webpack/lib/EnvironmentPlugin'), [{ 'VAR': false }]);
```

#### Config plugins: modify arguments
Expand Down Expand Up @@ -1217,6 +1220,29 @@ config.toString();
*/
```
Plugins specified via their path will have their `require()` statement generated
automatically:
``` js
config
.plugin('env')
.use(require.resolve('webpack/lib/ProvidePlugin'), [{ jQuery: 'jquery' }])

config.toString();

/*
{
plugins: [
new (require('/foo/bar/src/node_modules/webpack/lib/EnvironmentPlugin.js'))(
{
jQuery: 'jquery'
}
)
]
}
*/
```
You can also call `toString` as a static method on `Config` in order to
modify the configuration object prior to stringifying.
Expand Down
10 changes: 7 additions & 3 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@ module.exports = class extends ChainedMap {
const prefix = `/* ${configPrefix}.plugin('${
value.__pluginName
}') */\n`;
const constructorName = value.__pluginConstructorName;
const constructorExpression = value.__pluginPath
? // The path is stringified to ensure special characters are escaped
// (such as the backslashes in Windows-style paths).
`(require(${stringify(value.__pluginPath)}))`
: value.__pluginConstructorName;

if (constructorName) {
if (constructorExpression) {
// 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})`;
return `${prefix}new ${constructorExpression}(${args})`;
}
return (
prefix +
Expand Down
19 changes: 16 additions & 3 deletions src/Plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,30 @@ module.exports = Orderable(

toConfig() {
const init = this.get('init');
const plugin = this.get('plugin');
let plugin = this.get('plugin');
const args = this.get('args');
let pluginPath = null;

// Support using the path to a plugin rather than the plugin itself,
// allowing expensive require()s to be skipped in cases where the plugin
// or webpack configuration won't end up being used.
if (typeof plugin === 'string') {
pluginPath = plugin;
// eslint-disable-next-line global-require, import/no-dynamic-require
plugin = require(pluginPath);
}

const constructorName = plugin.__expression
? `(${plugin.__expression})`
: plugin.name;

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

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

return config;
Expand Down
22 changes: 21 additions & 1 deletion test/Config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import test from 'ava';
import { validate } from 'webpack';
import EnvironmentPlugin from 'webpack/lib/EnvironmentPlugin';
import stringify from 'javascript-stringify';
import Config from '../src/Config';

class StringifyPlugin {
Expand Down Expand Up @@ -100,6 +102,9 @@ test('toConfig with values', t => {
.plugin('stringify')
.use(StringifyPlugin)
.end()
.plugin('env')
.use(require.resolve('webpack/lib/EnvironmentPlugin'))
.end()
.module.defaultRule('inline')
.use('banner')
.loader('banner-loader')
Expand Down Expand Up @@ -132,7 +137,7 @@ test('toConfig with values', t => {
path: 'build',
},
target: 'node',
plugins: [new StringifyPlugin()],
plugins: [new StringifyPlugin(), new EnvironmentPlugin()],
module: {
defaultRules: [
{
Expand Down Expand Up @@ -199,6 +204,9 @@ test('validate with values', t => {
.plugin('stringify')
.use(StringifyPlugin)
.end()
.plugin('env')
.use(require.resolve('webpack/lib/EnvironmentPlugin'), [{ VAR: false }])
.end()
.module.rule('compile')
.include.add('alpha')
.add('beta')
Expand Down Expand Up @@ -228,10 +236,16 @@ test('toString', t => {
.use('babel')
.loader('babel-loader');

const envPluginPath = require.resolve('webpack/lib/EnvironmentPlugin');
const stringifiedEnvPluginPath = stringify(envPluginPath);

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

config
.plugin('env')
.use(envPluginPath, [{ VAR: false }])
.end()
.plugin('gamma')
.use(FooPlugin)
.end()
Expand Down Expand Up @@ -264,6 +278,12 @@ test('toString', t => {
]
},
plugins: [
/* config.plugin('env') */
new (require(${stringifiedEnvPluginPath}))(
{
VAR: false
}
),
/* config.plugin('gamma') */
new (require('foo-plugin'))(),
/* config.plugin('delta') */
Expand Down
14 changes: 14 additions & 0 deletions test/Plugin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import test from 'ava';
import EnvironmentPlugin from 'webpack/lib/EnvironmentPlugin';
import Plugin from '../src/Plugin';

class StringifyPlugin {
Expand Down Expand Up @@ -97,3 +98,16 @@ test('toConfig with object literal plugin', t => {

t.is(initialized, TestPlugin);
});

test('toConfig with plugin as path', t => {
const plugin = new Plugin(null, 'gamma');
const envPluginPath = require.resolve('webpack/lib/EnvironmentPlugin');

plugin.use(envPluginPath);

const initialized = plugin.toConfig();

t.true(initialized instanceof EnvironmentPlugin);
t.is(initialized.__pluginConstructorName, 'EnvironmentPlugin');
t.is(initialized.__pluginPath, envPluginPath);
});

0 comments on commit fc8ce23

Please sign in to comment.