webpack-chain
Use a chaining API to generate and simplify the modification of
Webpack version 2-4 configurations.
This documentation corresponds to v4 of webpack-chain.
Note: while webpack-chain is utilized extensively in Neutrino, this package is completely
standalone and can be used by any project.
Introduction
Webpack's core configuration is based on creating and modifying a
potentially unwieldy JavaScript object. While this is OK for configurations
on individual projects, trying to share these objects across projects and
make subsequent modifications gets messy, as you need to have a deep
understanding of the underlying object structure to make those changes.
webpack-chain
attempts to improve this process by providing a chainable or
fluent API for creating and modifying webpack configurations. Key portions
of the API can be referenced by user-specified names, which helps to
standardize how to modify a configuration across projects.
This is easier explained through the examples following.
Installation
webpack-chain
requires Node.js v6.9 and higher. webpack-chain
also
only creates configuration objects designed for use in Webpack version 2, 3, and 4.
You may install this package using either Yarn or npm (choose one):
Yarn
yarn add --dev webpack-chain
npm
npm install --save-dev webpack-chain
Getting Started
Once you have webpack-chain
installed, you can start creating a
Webpack configuration. For this guide, our example base configuration will
be webpack.config.js
in the root of our project directory.
const Config = require('webpack-chain');
const config = new Config();
config
.entry('index')
.add('src/index.js')
.end()
.output
.path('dist')
.filename('[name].bundle.js');
config.module
.rule('lint')
.test(/\.js$/)
.pre()
.include
.add('src')
.end()
.use('eslint')
.loader('eslint-loader')
.options({
rules: {
semi: 'off'
}
});
config.module
.rule('compile')
.test(/\.js$/)
.include
.add('src')
.add('test')
.end()
.use('babel')
.loader('babel-loader')
.options({
presets: [
['babel-preset-es2015', { modules: false }]
]
});
config
.plugin('clean')
.use(CleanPlugin, [['dist'], { root: '/dir' }]);
module.exports = config.toConfig();
Having shared configurations is also simple. Just export the configuration
and call .toConfig()
prior to passing to Webpack.
const Config = require('webpack-chain');
const config = new Config();
module.exports = config;
const config = require('./webpack.core');
module.exports = config.toConfig();
const config = require('./webpack.core');
module.exports = config.toConfig();
ChainedMap
One of the core API interfaces in webpack-chain 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.
clear()
delete(key)
get(key)
set(key, value)
has(key)
values()
entries()
merge(obj, omit)
batch(handler)
when(condition, whenTruthy, whenFalsy)
ChainedSet
Another of the core API interfaces in webpack-chain 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.
add(value)
prepend(value)
clear()
delete(value)
has(value)
values()
merge(arr)
batch(handler)
when(condition, whenTruthy, whenFalsy)
Shorthand methods
A number of shorthand methods exist for setting a value on a ChainedMap
with the same key as the shorthand method name.
For example, devServer.hot
is a shorthand method, so it can be used as:
devServer.hot(true);
devServer.set('hot', true);
A shorthand method is chainable, so calling it will return the original instance,
allowing you to continue to chain.
Config
Create a new configuration object.
const Config = require('webpack-chain');
const config = new Config();
Moving to deeper points in the API will change the context of what you
are modifying. You can move back to the higher context by either referencing
the top-level config
again, or by calling .end()
to move up one level.
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
Webpack docs hierarchy.
Config : ChainedMap
Config shorthand methods
config
.amd(amd)
.bail(bail)
.cache(cache)
.devtool(devtool)
.context(context)
.externals(externals)
.loader(loader)
.mode(mode)
.parallelism(parallelism)
.profile(profile)
.recordsPath(recordsPath)
.recordsInputPath(recordsInputPath)
.recordsOutputPath(recordsOutputPath)
.stats(stats)
.target(target)
.watch(watch)
.watchOptions(watchOptions)
Config entryPoints
config.entry(name) : ChainedSet
config
.entry(name)
.add(value)
.add(value)
config
.entry(name)
.clear()
config.entryPoints
.get(name)
.add(value)
.add(value)
config.entryPoints
.get(name)
.clear()
Config output: shorthand methods
config.output : ChainedMap
config.output
.auxiliaryComment(auxiliaryComment)
.chunkFilename(chunkFilename)
.chunkLoadTimeout(chunkLoadTimeout)
.crossOriginLoading(crossOriginLoading)
.devtoolFallbackModuleFilenameTemplate(devtoolFallbackModuleFilenameTemplate)
.devtoolLineToLine(devtoolLineToLine)
.devtoolModuleFilenameTemplate(devtoolModuleFilenameTemplate)
.filename(filename)
.hashFunction(hashFunction)
.hashDigest(hashDigest)
.hashDigestLength(hashDigestLength)
.hashSalt(hashSalt)
.hotUpdateChunkFilename(hotUpdateChunkFilename)
.hotUpdateFunction(hotUpdateFunction)
.hotUpdateMainFilename(hotUpdateMainFilename)
.jsonpFunction(jsonpFunction)
.library(library)
.libraryExport(libraryExport)
.libraryTarget(libraryTarget)
.path(path)
.pathinfo(pathinfo)
.publicPath(publicPath)
.sourceMapFilename(sourceMapFilename)
.sourcePrefix(sourcePrefix)
.strictModuleExceptionHandling(strictModuleExceptionHandling)
.umdNamedDefine(umdNamedDefine)
Config resolve: shorthand methods
config.resolve : ChainedMap
config.resolve
.cachePredicate(cachePredicate)
.cacheWithContext(cacheWithContext)
.enforceExtension(enforceExtension)
.enforceModuleExtension(enforceModuleExtension)
.unsafeCache(unsafeCache)
.symlinks(symlinks)
Config resolve alias
config.resolve.alias : ChainedMap
config.resolve.alias
.set(key, value)
.set(key, value)
.delete(key)
.clear()
Config resolve modules
config.resolve.modules : ChainedSet
config.resolve.modules
.add(value)
.prepend(value)
.clear()
Config resolve aliasFields
config.resolve.aliasFields : ChainedSet
config.resolve.aliasFields
.add(value)
.prepend(value)
.clear()
Config resolve descriptionFields
config.resolve.descriptionFields : ChainedSet
config.resolve.descriptionFields
.add(value)
.prepend(value)
.clear()
Config resolve extensions
config.resolve.extensions : ChainedSet
config.resolve.extensions
.add(value)
.prepend(value)
.clear()
Config resolve mainFields
config.resolve.mainFields : ChainedSet
config.resolve.mainFields
.add(value)
.prepend(value)
.clear()
Config resolve mainFiles
config.resolve.mainFiles : ChainedSet
config.resolve.mainFiles
.add(value)
.prepend(value)
.clear()
Config resolveLoader
config.resolveLoader : ChainedMap
Config resolveLoader extensions
config.resolveLoader.extensions : ChainedSet
config.resolveLoader.extensions
.add(value)
.prepend(value)
.clear()
Config resolveLoader modules
config.resolveLoader.modules : ChainedSet
config.resolveLoader.modules
.add(value)
.prepend(value)
.clear()
Config resolveLoader moduleExtensions
config.resolveLoader.moduleExtensions : ChainedSet
config.resolveLoader.moduleExtensions
.add(value)
.prepend(value)
.clear()
Config resolveLoader packageMains
config.resolveLoader.packageMains : ChainedSet
config.resolveLoader.packageMains
.add(value)
.prepend(value)
.clear()
Config performance: shorthand methods
config.performance : ChainedMap
config.performance
.hints(hints)
.maxEntrypointSize(maxEntrypointSize)
.maxAssetSize(maxAssetSize)
.assetFilter(assetFilter)
Configuring optimizations: shorthand methods
config.optimization : ChainedMap
config.optimization
.concatenateModules(concatenateModules)
.flagIncludedChunks(flagIncludedChunks)
.mergeDuplicateChunks(mergeDuplicateChunks)
.minimize(minimize)
.minimizer(minimizer)
.namedChunks(namedChunks)
.namedModules(namedModules)
.nodeEnv(nodeEnv)
.noEmitOnErrors(noEmitOnErrors)
.occurrenceOrder(occurrenceOrder)
.portableRecords(portableRecords)
.providedExports(providedExports)
.removeAvailableModules(removeAvailableModules)
.removeEmptyChunks(removeEmptyChunks)
.runtimeChunk(runtimeChunk)
.sideEffects(sideEffects)
.splitChunks(splitChunks)
.usedExports(usedExports)
Config plugins
config.plugin(name) : ChainedMap
Config plugins: adding
NOTE: Do not use new
to create the plugin, as this will be done for you.
config
.plugin(name)
.use(WebpackPlugin, args)
config
.plugin('hot')
.use(webpack.HotModuleReplacementPlugin);
config
.plugin('env')
.use(webpack.EnvironmentPlugin, ['NODE_ENV']);
Config plugins: modify arguments
config
.plugin(name)
.tap(args => newArgs)
config
.plugin('env')
.tap(args => [...args, 'SECRET_KEY']);
Config plugins: modify instantiation
config
.plugin(name)
.init((Plugin, args) => new Plugin(...args));
Config plugins: removing
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.
config
.plugin(name)
.before(otherName)
config
.plugin('html-template')
.use(HtmlWebpackTemplate)
.end()
.plugin('script-ext')
.use(ScriptExtWebpackPlugin)
.before('html-template');
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.
config
.plugin(name)
.after(otherName)
config
.plugin('html-template')
.after('script-ext')
.use(HtmlWebpackTemplate)
.end()
.plugin('script-ext')
.use(ScriptExtWebpackPlugin);
Config resolve plugins
config.resolve.plugin(name) : ChainedMap
Config resolve plugins: adding
NOTE: Do not use new
to create the plugin, as this will be done for you.
config.resolve
.plugin(name)
.use(WebpackPlugin, args)
Config resolve plugins: modify arguments
config.resolve
.plugin(name)
.tap(args => newArgs)
Config resolve plugins: modify instantiation
config.resolve
.plugin(name)
.init((Plugin, args) => new Plugin(...args))
Config resolve plugins: removing
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.
config.resolve
.plugin(name)
.before(otherName)
config.resolve
.plugin('beta')
.use(BetaWebpackPlugin)
.end()
.plugin('alpha')
.use(AlphaWebpackPlugin)
.before('beta');
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.
config.resolve
.plugin(name)
.after(otherName)
config.resolve
.plugin('beta')
.after('alpha')
.use(BetaWebpackTemplate)
.end()
.plugin('alpha')
.use(AlphaWebpackPlugin);
Config node
config.node : ChainedMap
config.node
.set('__dirname', 'mock')
.set('__filename', 'mock');
Config devServer
config.devServer : ChainedMap
Config devServer allowedHosts
config.devServer.allowedHosts : ChainedSet
config.devServer.allowedHosts
.add(value)
.prepend(value)
.clear()
Config devServer: shorthand methods
config.devServer
.bonjour(bonjour)
.clientLogLevel(clientLogLevel)
.color(color)
.compress(compress)
.contentBase(contentBase)
.disableHostCheck(disableHostCheck)
.filename(filename)
.headers(headers)
.historyApiFallback(historyApiFallback)
.host(host)
.hot(hot)
.hotOnly(hotOnly)
.https(https)
.inline(inline)
.info(info)
.lazy(lazy)
.noInfo(noInfo)
.open(open)
.openPage(openPage)
.overlay(overlay)
.pfx(pfx)
.pfxPassphrase(pfsPassphrase)
.port(port)
.progress(progress)
.proxy(proxy)
.public(public)
.publicPath(publicPath)
.quiet(quiet)
.setup(setup)
.socket(socket)
.staticOptions(staticOptions)
.stats(stats)
.stdin(stdin)
.useLocalIp(useLocalIp)
.watchContentBase(watchContentBase)
.watchOptions(watchOptions)
Config module
config.module : ChainedMap
Config module: shorthand methods
config.module : ChainedMap
config.module
.noParse(noParse)
Config module rules: shorthand methods
config.module.rules : ChainedMap
config.module
.rule(name)
.test(test)
.pre()
.post()
.enforce(preOrPost)
Config module rules uses (loaders): creating
config.module.rules{}.uses : ChainedMap
config.module
.rule(name)
.use(name)
.loader(loader)
.options(options)
config.module
.rule('compile')
.use('babel')
.loader('babel-loader')
.options({ presets: ['babel-preset-es2015'] });
Config module rules uses (loaders): modifying options
config.module
.rule(name)
.use(name)
.tap(options => newOptions)
config.module
.rule('compile')
.use('babel')
.tap(options => merge(options, { plugins: ['babel-plugin-syntax-object-rest-spread'] }));
Config module rules oneOfs (conditional rules):
config.module.rules{}.oneOfs : ChainedMap<Rule>
config.module
.rule(name)
.oneOf(name)
config.module
.rule('css')
.oneOf('inline')
.resourceQuery(/inline/)
.use('url')
.loader('url-loader')
.end()
.end()
.oneOf('external')
.resourceQuery(/external/)
.use('file')
.loader('file-loader')
Merging Config
webpack-chain supports merging in an object to the configuration instance which matches a layout
similar to how the webpack-chain 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 webpack-chain
to match its layout.
config.merge({ devtool: 'source-map' });
config.get('devtool')
config.merge({
[key]: value,
amd,
bail,
cache,
context,
devtool,
externals,
loader,
mode,
parallelism,
profile,
recordsPath,
recordsInputPath,
recordsOutputPath,
stats,
target,
watch,
watchOptions,
entry: {
[name]: [...values]
},
plugin: {
[name]: {
plugin: WebpackPlugin,
args: [...args],
before,
after
}
},
devServer: {
[key]: value,
clientLogLevel,
compress,
contentBase,
filename,
headers,
historyApiFallback,
host,
hot,
hotOnly,
https,
inline,
lazy,
noInfo,
overlay,
port,
proxy,
quiet,
setup,
stats,
watchContentBase
},
node: {
[key]: value
},
optimizations: {
concatenateModules,
flagIncludedChunks,
mergeDuplicateChunks,
minimize,
minimizer,
namedChunks,
namedModules,
nodeEnv,
noEmitOnErrors,
occurrenceOrder,
portableRecords,
providedExports,
removeAvailableModules,
removeEmptyChunks,
runtimeChunk,
sideEffects,
splitChunks,
usedExports,
},
performance: {
[key]: value,
hints,
maxEntrypointSize,
maxAssetSize,
assetFilter
},
resolve: {
[key]: value,
alias: {
[key]: value
},
aliasFields: [...values],
descriptionFields: [...values],
extensions: [...values],
mainFields: [...values],
mainFiles: [...values],
modules: [...values],
plugin: {
[name]: {
plugin: WebpackPlugin,
args: [...args],
before,
after
}
}
},
resolveLoader: {
[key]: value,
extensions: [...values],
modules: [...values],
moduleExtensions: [...values],
packageMains: [...values]
},
module: {
[key]: value,
rule: {
[name]: {
[key]: value,
enforce,
issuer,
parser,
resource,
resourceQuery,
test,
include: [...paths],
exclude: [...paths],
oneOf: {
[name]: Rule
},
use: {
[name]: {
loader: LoaderString,
options: LoaderOptions,
before,
after
}
}
}
}
}
})
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.
config
.when(process.env.NODE_ENV === 'production', config => {
config
.plugin('minify')
.use(BabiliWebpackPlugin);
});
config
.when(process.env.NODE_ENV === 'production',
config => config.plugin('minify').use(BabiliWebpackPlugin),
config => config.devtool('source-map')
);
Inspecting generated configuration
You can inspect the generated webpack config using config.toString()
. This will generate a stringified version of the config with comment hints for named rules, uses and plugins:
config
.module
.rule('compile')
.test(/\.js$/)
.use('babel')
.loader('babel-loader');
config.toString();
{
test: /\.js$/,
use: [
{
loader: 'babel-loader'
}
]
}
]
}
}
*/
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:
class MyPlugin {}
MyPlugin.__expression = `require('my-plugin')`;
function myFunction () {}
myFunction.__expression = `require('my-function')`;
config
.plugin('example')
.use(MyPlugin, [{ fn: myFunction }]);
config.toString();
You can also call toString
as a static method on Config
in order to
modify the configuration object prior to stringifying.
Config.toString({
...config.toConfig(),
module: {
defaultRules: [
{
use: [
{
loader: 'banner-loader',
options: { prefix: 'banner-prefix.txt' },
},
],
},
],
},
})
new TestPlugin()
],
module: {
defaultRules: [
{
use: [
{
loader: 'banner-loader',
options: {
prefix: 'banner-prefix.txt'
}
}
]
}
]
}
}
*/