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

Commit

Permalink
Lazily require() webpack plugins to improve performance (#1101)
Browse files Browse the repository at this point in the history
webpack-chain 4.11.0's `.plugin('abc').use(...)` now supports being
passed the path to a plugin, instead of the plugin itself. See:
neutrinojs/webpack-chain#102

This means we can avoid the expensive `require()` of plugins in cases
where the plugin isn't used - such as when:
* running ESLint's CLI with a Neutrino-generated `.eslintrc.js`
* running tests with Jest/Mocha (which don't perform a webpack build)
* the plugin isn't needed for that `NODE_ENV` (eg `clean-webpack-plugin`
  isn't used in development).

For example, this reduces `time node .eslintrc.js` for a React+AirBnb
project from 1700ms to 370ms - and for projects that use more of the
non-default core Neutrino presets, the improvement will be even more
noticeable.

As an added bonus, plugins specified by path also have their `require()`
statement generated automatically when using `toString()`, meaning
that the configuration output by `--inspect` no longer needs any
adjustments before it can be run.

ie this "just works" with the core presets:

```
$ echo "module.exports = $(yarn neutrino --inspect --mode production);" > exported-config.js
$ yarn webpack --config exported-config.js
```

The webpack-chain docs have also been synced with those from upstream.

Refs #239.
  • Loading branch information
edmorley committed Sep 14, 2018
1 parent f128f6f commit a0cb90a
Show file tree
Hide file tree
Showing 16 changed files with 138 additions and 110 deletions.
189 changes: 118 additions & 71 deletions docs/webpack-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ webpack configurations.

This documentation corresponds to v4 of webpack-chain, which Neutrino utilizes.

_Note: while webpack-chain is utilized extensively in Neutrino, the package is completely
standalone and can be used by any project. See the webpack-chain repo for standalone documentation._
_Note: while webpack-chain is utilized extensively in Neutrino, the package is
completely standalone and can be used by any project.
See the [webpack-chain](https://github.com/neutrinojs/webpack-chain) repository
for standalone documentation._

## Introduction

Expand Down Expand Up @@ -94,7 +96,7 @@ module.exports = (neutrino) => {
.loader('babel-loader')
.options({
presets: [
['babel-preset-es2015', { modules: false }]
['@babel/preset-env', { modules: false }]
]
});

Expand All @@ -107,11 +109,13 @@ module.exports = (neutrino) => {

## ChainedMap

One of the core API interfaces in Neutrino configuration is a `ChainedMap`. A `ChainedMap` operates
similar to a JavaScript Map, with some conveniences for chaining and generating configuration.
If a property is marked as being a `ChainedMap`, it will have an API and methods as described below:
One of the core API interfaces in Neutrino configuration is a `ChainedMap`. A
`ChainedMap` operates similar to a JavaScript Map, with some conveniences for
chaining and generating configuration. If a property is marked as being a
`ChainedMap`, it will have an API and methods as described below:

**Unless stated otherwise, these methods will return the `ChainedMap`, allowing you to chain these methods.**
**Unless stated otherwise, these methods will return the `ChainedMap`, allowing
you to chain these methods.**

```js
// Remove all entries from a Map.
Expand All @@ -131,6 +135,15 @@ delete(key)
get(key)
```

```js
// Fetch the value from a Map located at the corresponding key.
// If the key is missing, the key is set to the result of function fn.
// key: *
// fn: Function () -> value
// returns: value
getOrCompute(key, fn)
```

```js
// Set a value on the Map stored at the `key` location.
// key: *
Expand All @@ -139,7 +152,8 @@ set(key, value)
```

```js
// Returns `true` or `false` based on whether a Map as has a value set at a particular key.
// Returns `true` or `false` based on whether a Map as has a value set at a
// particular key.
// key: *
// returns: Boolean
has(key)
Expand Down Expand Up @@ -191,11 +205,13 @@ when(condition, whenTruthy, whenFalsy)

## ChainedSet

Another of the core API interfaces in Neutrino configuration is a `ChainedSet`. A `ChainedSet` operates
similar to a JavaScript Set, with some conveniences for chaining and generating configuration.
If a property is marked as being a `ChainedSet`, it will have an API and methods as described below:
Another of the core API interfaces in Neutrino configuration is a `ChainedSet`. A
`ChainedSet` operates similar to a JavaScript Set, with some conveniences for
chaining and generating configuration. If a property is marked as being a
`ChainedSet`, it will have an API and methods as described below:

**Unless stated otherwise, these methods will return the `ChainedSet`, allowing you to chain these methods.**
**Unless stated otherwise, these methods will return the `ChainedSet`, allowing
you to chain these methods.**

```js
// Add/append a value to the end of a Set.
Expand Down Expand Up @@ -271,8 +287,8 @@ neutrino.config.devServer.hot(true);
neutrino.config.devServer.set('hot', true);
```

A shorthand method is chainable, so calling it will return the original instance,
allowing you to continue to chain.
A shorthand method is chainable, so calling it will return the original
instance, allowing you to continue to chain.

### Config

Expand All @@ -283,8 +299,8 @@ If you are familiar with jQuery, `.end()` works similarly. All API calls
will return the API instance at the current context unless otherwise
specified. This is so you may chain API calls continuously if desired.

For details on the specific values that are valid for all shorthand and low-level methods,
please refer to their corresponding name in the
For details on the specific values that are valid for all shorthand and
low-level methods, please refer to their corresponding name in the
[webpack docs hierarchy](https://webpack.js.org/configuration/).

```js
Expand Down Expand Up @@ -469,31 +485,8 @@ neutrino.config.resolve.mainFiles

#### Config resolveLoader

```js
neutrino.config.resolveLoader : ChainedMap
```

#### Config resolveLoader extensions

```js
neutrino.config.resolveLoader.extensions : ChainedSet
neutrino.config.resolveLoader.extensions
.add(value)
.prepend(value)
.clear()
```

#### Config resolveLoader modules

```js
neutrino.config.resolveLoader.modules : ChainedSet
neutrino.config.resolveLoader.modules
.add(value)
.prepend(value)
.clear()
```
The API for `neutrino.config.resolveLoader` is identical to `neutrino.config.resolve` with
the following additions:

#### Config resolveLoader moduleExtensions

Expand Down Expand Up @@ -569,37 +562,40 @@ _NOTE: Do not use `new` to create the plugin, as this will be done for you._
```js
neutrino.config
.plugin(name)
.use(WebpackPlugin, args)
.use(WebpackPlugin, args)
// Examples
neutrino.config
.plugin('hot')
.use(webpack.HotModuleReplacementPlugin);
.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.
neutrino.config
.plugin('env')
.use(webpack.EnvironmentPlugin, ['NODE_ENV']);
.use(require.resolve('webpack/lib/EnvironmentPlugin'), [{ 'VAR': false }]);
```

#### Config plugins: modify arguments

```js
neutrino.config
.plugin(name)
.tap(args => newArgs)
.tap(args => newArgs)
// Example
neutrino.config
.plugin('env')
.tap(args => [...args, 'SECRET_KEY']);
.tap(args => [...args, 'SECRET_KEY']);
```

#### Config plugins: modify instantiation

```js
neutrino.config
.plugin(name)
.init((Plugin, args) => new Plugin(...args));
.init((Plugin, args) => new Plugin(...args));
```

#### Config plugins: removing
Expand All @@ -610,14 +606,14 @@ neutrino.config.plugins.delete(name)

#### Config plugins: ordering before

Specify that the current `plugin` context should operate before another named `plugin`.
You cannot use both `.before()` and `.after()` on the same plugin.
Specify that the current `plugin` context should operate before another named
`plugin`. You cannot use both `.before()` and `.after()` on the same plugin.

```js
neutrino.config
.plugin(name)
.before(otherName)
// Example
neutrino.config
Expand All @@ -631,8 +627,8 @@ neutrino.config

#### Config plugins: ordering after

Specify that the current `plugin` context should operate after another named `plugin`.
You cannot use both `.before()` and `.after()` on the same plugin.
Specify that the current `plugin` context should operate after another named
`plugin`. You cannot use both `.before()` and `.after()` on the same plugin.

```js
neutrino.config
Expand Down Expand Up @@ -664,7 +660,7 @@ _NOTE: Do not use `new` to create the plugin, as this will be done for you._
```js
neutrino.config.resolve
.plugin(name)
.use(WebpackPlugin, args)
.use(WebpackPlugin, args)
```

#### Config resolve plugins: modify arguments
Expand All @@ -680,7 +676,7 @@ neutrino.config.resolve
```js
neutrino.config.resolve
.plugin(name)
.init((Plugin, args) => new Plugin(...args))
.init((Plugin, args) => new Plugin(...args))
```

#### Config resolve plugins: removing
Expand All @@ -691,8 +687,9 @@ neutrino.config.resolve.plugins.delete(name)

#### Config resolve plugins: ordering before

Specify that the current `plugin` context should operate before another named `plugin`.
You cannot use both `.before()` and `.after()` on the same resolve plugin.
Specify that the current `plugin` context should operate before another named
`plugin`. You cannot use both `.before()` and `.after()` on the same resolve
plugin.

```js
neutrino.config.resolve
Expand All @@ -712,8 +709,9 @@ neutrino.config.resolve

#### Config resolve plugins: ordering after

Specify that the current `plugin` context should operate after another named `plugin`.
You cannot use both `.before()` and `.after()` on the same resolve plugin.
Specify that the current `plugin` context should operate after another named
`plugin`. You cannot use both `.before()` and `.after()` on the same resolve
plugin.

```js
neutrino.config.resolve
Expand Down Expand Up @@ -845,7 +843,7 @@ neutrino.config.module
.rule('compile')
.use('babel')
.loader('babel-loader')
.options({ presets: ['babel-preset-es2015'] });
.options({ presets: ['@babel/preset-env'] });
```

#### Config module rules uses (loaders): modifying options
Expand All @@ -861,7 +859,9 @@ neutrino.config.module
neutrino.config.module
.rule('compile')
.use('babel')
.tap(options => merge(options, { plugins: ['babel-plugin-syntax-object-rest-spread'] }));
.tap(options => merge(options, {
plugins: ['@babel/plugin-proposal-class-properties']
}));
```

#### Config module rules oneOfs (conditional rules):
Expand Down Expand Up @@ -893,10 +893,10 @@ neutrino.config.module

### Merging Config

Neutrino config supports merging in an object to the configuration instance which matches a layout
similar to how the configuration schema is laid out. Note that this is not a webpack configuration
object, but you may transform a webpack configuration object before providing it to Neutrino configuration
to match its layout.
Neutrino config supports merging in an object to the configuration instance which
matches a layout similar to how the configuration schema is laid out. Note that
this is not a webpack configuration object, but you may transform a webpack
configuration object before providing it to Neutrino configuration to match its layout.

```js
neutrino.config.merge({ devtool: 'source-map' });
Expand Down Expand Up @@ -1028,10 +1028,26 @@ neutrino.config.merge({
resolveLoader: {
[key]: value,
alias: {
[key]: value
},
aliasFields: [...values],
descriptionFields: [...values],
extensions: [...values],
mainFields: [...values],
mainFiles: [...values],
modules: [...values],
moduleExtensions: [...values],
packageMains: [...values]
packageMains: [...values],
plugin: {
[name]: {
plugin: WebpackPlugin,
args: [...args],
before,
after
}
}
},
module: {
Expand Down Expand Up @@ -1071,10 +1087,13 @@ neutrino.config.merge({

### Conditional configuration

When working with instances of `ChainedMap` and `ChainedSet`, you can perform conditional configuration using `when`.
You must specify an expression to `when()` which will be evaluated for truthiness or falsiness. If the expression is
truthy, the first function argument will be invoked with an instance of the current chained instance. You can optionally
provide a second function to be invoked when the condition is falsy, which is also given the current chained instance.
When working with instances of `ChainedMap` and `ChainedSet`, you can perform
conditional configuration using `when`. You must specify an expression to
`when()` which will be evaluated for truthiness or falsiness. If the expression
is truthy, the first function argument will be invoked with an instance of the
current chained instance. You can optionally provide a second function to be
invoked when the condition is falsy, which is also given the current chained
instance.

```js
// Example: Only add minify plugin during production
Expand All @@ -1098,7 +1117,9 @@ neutrino.config

### Inspecting generated configuration

You can inspect the generated webpack config using `neutrino.config.toString()`. This will generate a stringified version of the config with comment hints for named rules, uses and plugins:
You can inspect the generated webpack config using `neutrino.config.toString()`. This
will generate a stringified version of the config with comment hints for named
rules, uses and plugins:

``` js
neutrino.config
Expand Down Expand Up @@ -1130,7 +1151,10 @@ neutrino.config.toString({ configPrefix: 'neutrino.config' });
*/
```

By default the generated string cannot be used directly as real webpack config if it contains functions and plugins that need to be required. In order to generate usable config, you can customize how functions and plugins are stringified by setting a special `__expression` property on them:
By default the generated string cannot be used directly as real webpack config
if it contains functions and plugins that need to be required. In order to
generate usable config, you can customize how functions and plugins are
stringified by setting a special `__expression` property on them:

``` js
class MyPlugin {}
Expand All @@ -1156,6 +1180,29 @@ neutrino.config.toString({ configPrefix: 'neutrino.config' });
*/
```

Plugins specified via their path will have their `require()` statement generated
automatically:

``` js
neutrino.config
.plugin('env')
.use(require.resolve('webpack/lib/ProvidePlugin'), [{ jQuery: 'jquery' }])

neutrino.config.toString({ configPrefix: 'neutrino.config' });

/*
{
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

0 comments on commit a0cb90a

Please sign in to comment.