![Oracle Drags Its Feet in the JavaScript Trademark Dispute](https://cdn.sanity.io/images/cgdhsj6q/production/919c3b22c24f93884c548d60cbb338e819ff2435-1024x1024.webp?w=400&fit=max&auto=format)
Security News
Oracle Drags Its Feet in the JavaScript Trademark Dispute
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
@amory/chain
Advanced tools
Use a chaining API to generate and simplify the modification of Webpack version 2-4 configurations.
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.
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.
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
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.
// Require the webpack-chain module. This module exports a single
// constructor function for creating a configuration API.
const Config = require('webpack-chain');
// Instantiate the configuration with a new API
const config = new Config();
// Make configuration changes using the chain API.
// Every API call tracks a change to the stored configuration.
config
// Interact with entry points
.entry('index')
.add('src/index.js')
.end()
// Modify output settings
.output
.path('dist')
.filename('[name].bundle.js');
// Create named rules which can be modified later
config.module
.rule('lint')
.test(/\.js$/)
.pre()
.include
.add('src')
.end()
// Even create named uses (loaders)
.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 }]
]
});
// Create named plugins too!
config
.plugin('clean')
.use(CleanPlugin, [['dist'], { root: '/dir' }]);
// Export the completed configuration object to be consumed by webpack
module.exports = config.toConfig();
Having shared configurations is also simple. Just export the configuration
and call .toConfig()
prior to passing to Webpack.
// webpack.core.js
const Config = require('webpack-chain');
const config = new Config();
// Make configuration shared across targets
// ...
module.exports = config;
// webpack.dev.js
const config = require('./webpack.core');
// Dev-specific configuration
// ...
module.exports = config.toConfig();
// webpack.prod.js
const config = require('./webpack.core');
// Production-specific configuration
// ...
module.exports = config.toConfig();
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.
// Remove all entries from a Map.
clear()
// Remove a single entry from a Map given its key.
// key: *
delete(key)
// Fetch the value from a Map located at the corresponding key.
// key: *
// returns: value
get(key)
// Set a value on the Map stored at the `key` location.
// key: *
// value: *
set(key, value)
// Returns `true` or `false` based on whether a Map as has a value set at a particular key.
// key: *
// returns: Boolean
has(key)
// Returns an array of all the values stored in the Map.
// returns: Array
values()
// Returns an object of all the entries in the backing Map
// where the key is the object property, and the value
// corresponding to the key. Will return `undefined` if the backing
// Map is empty.
// This will order properties by their name if the value is
// a ChainedMap that used .before() or .after().
// returns: Object, undefined if empty
entries()
// Provide an object which maps its properties and values
// into the backing Map as keys and values.
// You can also provide an array as the second argument
// for property names to omit from being merged.
// obj: Object
// omit: Optional Array
merge(obj, omit)
// Execute a function against the current configuration context
// handler: Function -> ChainedMap
// A function which is given a single argument of the ChainedMap instance
batch(handler)
// Conditionally execute a function to continue configuration
// condition: Boolean
// whenTruthy: Function -> ChainedMap
// invoked when condition is truthy, given a single argument of the ChainedMap instance
// whenFalsy: Optional Function -> ChainedMap
// invoked when condition is falsy, given a single argument of the ChainedMap instance
when(condition, whenTruthy, whenFalsy)
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/append a value to the end of a Set.
// value: *
add(value)
// Add a value to the beginning of a Set.
// value: *
prepend(value)
// Remove all values from a Set.
clear()
// Remove a specific value from a Set.
// value: *
delete(value)
// Returns `true` or `false` based on whether or not the
// backing Set contains the specified value.
// value: *
// returns: Boolean
has(value)
// Returns an array of values contained in the backing Set.
// returns: Array
values()
// Concatenates the given array to the end of the backing Set.
// arr: Array
merge(arr)
// Execute a function against the current configuration context
// handler: Function -> ChainedSet
// A function which is given a single argument of the ChainedSet instance
batch(handler)
// Conditionally execute a function to continue configuration
// condition: Boolean
// whenTruthy: Function -> ChainedSet
// invoked when condition is truthy, given a single argument of the ChainedSet instance
// whenFalsy: Optional Function -> ChainedSet
// invoked when condition is falsy, given a single argument of the ChainedSet instance
when(condition, whenTruthy, whenFalsy)
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:
// A shorthand method for setting a value on a ChainedMap
devServer.hot(true);
// This would be equivalent to:
devServer.set('hot', true);
A shorthand method is chainable, so calling it will return the original instance, allowing you to continue to chain.
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
.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)
// Backed at config.entryPoints : ChainedMap
config.entry(name) : ChainedSet
config
.entry(name)
.add(value)
.add(value)
config
.entry(name)
.clear()
// Using low-level config.entryPoints:
config.entryPoints
.get(name)
.add(value)
.add(value)
config.entryPoints
.get(name)
.clear()
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 : ChainedMap
config.resolve
.cachePredicate(cachePredicate)
.cacheWithContext(cacheWithContext)
.enforceExtension(enforceExtension)
.enforceModuleExtension(enforceModuleExtension)
.unsafeCache(unsafeCache)
.symlinks(symlinks)
config.resolve.alias : ChainedMap
config.resolve.alias
.set(key, value)
.set(key, value)
.delete(key)
.clear()
config.resolve.modules : ChainedSet
config.resolve.modules
.add(value)
.prepend(value)
.clear()
config.resolve.aliasFields : ChainedSet
config.resolve.aliasFields
.add(value)
.prepend(value)
.clear()
config.resolve.descriptionFields : ChainedSet
config.resolve.descriptionFields
.add(value)
.prepend(value)
.clear()
config.resolve.extensions : ChainedSet
config.resolve.extensions
.add(value)
.prepend(value)
.clear()
config.resolve.mainFields : ChainedSet
config.resolve.mainFields
.add(value)
.prepend(value)
.clear()
config.resolve.mainFiles : ChainedSet
config.resolve.mainFiles
.add(value)
.prepend(value)
.clear()
config.resolveLoader : ChainedMap
config.resolveLoader.extensions : ChainedSet
config.resolveLoader.extensions
.add(value)
.prepend(value)
.clear()
config.resolveLoader.modules : ChainedSet
config.resolveLoader.modules
.add(value)
.prepend(value)
.clear()
config.resolveLoader.moduleExtensions : ChainedSet
config.resolveLoader.moduleExtensions
.add(value)
.prepend(value)
.clear()
config.resolveLoader.packageMains : ChainedSet
config.resolveLoader.packageMains
.add(value)
.prepend(value)
.clear()
config.performance : ChainedMap
config.performance
.hints(hints)
.maxEntrypointSize(maxEntrypointSize)
.maxAssetSize(maxAssetSize)
.assetFilter(assetFilter)
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)
// Backed at config.plugins
config.plugin(name) : ChainedMap
NOTE: Do not use new
to create the plugin, as this will be done for you.
config
.plugin(name)
.use(WebpackPlugin, args)
// Examples
config
.plugin('hot')
.use(webpack.HotModuleReplacementPlugin);
config
.plugin('env')
.use(webpack.EnvironmentPlugin, ['NODE_ENV']);
config
.plugin(name)
.tap(args => newArgs)
// Example
config
.plugin('env')
.tap(args => [...args, 'SECRET_KEY']);
config
.plugin(name)
.init((Plugin, args) => new Plugin(...args));
config.plugins.delete(name)
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)
// Example
config
.plugin('html-template')
.use(HtmlWebpackTemplate)
.end()
.plugin('script-ext')
.use(ScriptExtWebpackPlugin)
.before('html-template');
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)
// Example
config
.plugin('html-template')
.after('script-ext')
.use(HtmlWebpackTemplate)
.end()
.plugin('script-ext')
.use(ScriptExtWebpackPlugin);
// Backed at config.resolve.plugins
config.resolve.plugin(name) : ChainedMap
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
.plugin(name)
.tap(args => newArgs)
config.resolve
.plugin(name)
.init((Plugin, args) => new Plugin(...args))
config.resolve.plugins.delete(name)
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)
// Example
config.resolve
.plugin('beta')
.use(BetaWebpackPlugin)
.end()
.plugin('alpha')
.use(AlphaWebpackPlugin)
.before('beta');
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)
// Example
config.resolve
.plugin('beta')
.after('alpha')
.use(BetaWebpackTemplate)
.end()
.plugin('alpha')
.use(AlphaWebpackPlugin);
config.node : ChainedMap
config.node
.set('__dirname', 'mock')
.set('__filename', 'mock');
config.devServer : ChainedMap
config.devServer.allowedHosts : ChainedSet
config.devServer.allowedHosts
.add(value)
.prepend(value)
.clear()
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 : ChainedMap
config.module : ChainedMap
config.module
.noParse(noParse)
config.module.rules : ChainedMap
config.module
.rule(name)
.test(test)
.pre()
.post()
.enforce(preOrPost)
config.module.rules{}.uses : ChainedMap
config.module
.rule(name)
.use(name)
.loader(loader)
.options(options)
// Example
config.module
.rule('compile')
.use('babel')
.loader('babel-loader')
.options({ presets: ['babel-preset-es2015'] });
config.module
.rule(name)
.use(name)
.tap(options => newOptions)
// Example
config.module
.rule('compile')
.use('babel')
.tap(options => merge(options, { plugins: ['babel-plugin-syntax-object-rest-spread'] }));
config.module.rules{}.oneOfs : ChainedMap<Rule>
config.module
.rule(name)
.oneOf(name)
// Example
config.module
.rule('css')
.oneOf('inline')
.resourceQuery(/inline/)
.use('url')
.loader('url-loader')
.end()
.end()
.oneOf('external')
.resourceQuery(/external/)
.use('file')
.loader('file-loader')
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') // "source-map"
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
}
}
}
}
}
})
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.
// Example: Only add minify plugin during production
config
.when(process.env.NODE_ENV === 'production', config => {
config
.plugin('minify')
.use(BabiliWebpackPlugin);
});
// Example: Only add minify plugin during production,
// otherwise set devtool to source-map
config
.when(process.env.NODE_ENV === 'production',
config => config.plugin('minify').use(BabiliWebpackPlugin),
config => config.devtool('source-map')
);
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();
/*
{
module: {
rules: [
/* config.module.rule('compile') */
{
test: /\.js$/,
use: [
/* config.module.rule('compile').use('babel') */
{
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();
/*
{
plugins: [
new (require('my-plugin'))({
fn: require('my-function')
})
]
}
*/
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' },
},
],
},
],
},
})
/*
{
plugins: [
/* config.plugin('foo') */
new TestPlugin()
],
module: {
defaultRules: [
{
use: [
{
loader: 'banner-loader',
options: {
prefix: 'banner-prefix.txt'
}
}
]
}
]
}
}
*/
FAQs
Use a chaining API to generate and simplify the modification of Webpack version 2-4 configurations.
The npm package @amory/chain receives a total of 3 weekly downloads. As such, @amory/chain popularity was classified as not popular.
We found that @amory/chain demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.