# rspack-chain

<p>
  <a href="https://npmjs.com/package/rspack-chain?activeTab=readme">
   <img src="https://img.shields.io/npm/v/rspack-chain?style=flat-square&colorA=564341&colorB=EDED91" alt="npm version" />
  </a>
  <a href="https://npmcharts.com/compare/rspack-chain?minimal=true"><img src="https://img.shields.io/npm/dm/rspack-chain.svg?style=flat-square&colorA=564341&colorB=EDED91" alt="downloads" /></a>
  <a href="https://github.com/rstackjs/rspack-chain/blob/main/LICENSE">
    <img src="https://img.shields.io/npm/l/rspack-chain?style=flat-square&colorA=564341&colorB=EDED91" alt="license" />
  </a>
</p>

Use a chaining API to generate and simplify the modification of Rspack configurations.

> Note: rspack-chain is a fork of [neutrinojs/webpack-chain](https://github.com/neutrinojs/webpack-chain), thanks for their excellent work ❤️

## Introduction

Rspack'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.

`rspack-chain` attempts to improve this process by providing a chainable or
fluent API for creating and modifying Rspack 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

For Rspack v2, install `rspack-chain` v2 beta:

```bash
npm add rspack-chain@beta -D
```

For Rspack v1, install the `rspack-chain` v1:

```bash
npm add rspack-chain@^1.0.0 -D
```

If you are using Rspack v1, refer to the
[`v1.x` branch README](https://github.com/rstackjs/rspack-chain/blob/v1.x/README.md)
for the matching documentation.

## Getting Started

Once you have `rspack-chain` installed, you can start creating a
Rspack configuration. For this guide, our example base configuration will
be `rspack.config.js` in the root of our project directory.

```js
// Import the rspack-chain module. This module exports a single
// named constructor for creating a configuration API.
import { RspackChain } from 'rspack-chain';

// Instantiate the configuration with a new API
const config = new RspackChain();

// 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-env', { modules: false }]],
  });

// Create named plugins too!
config.plugin('clean').use(CleanPlugin, [['dist'], { root: '/dir' }]);

// Export the completed configuration object to be consumed by rspack
export default config.toConfig();
```

Having shared configurations is also simple. Just export the configuration
and call `.toConfig()` prior to passing to rspack.

```js
// rspack.core.js
import { RspackChain } from 'rspack-chain';

const config = new RspackChain();

// Make configuration shared across targets
// ...

export default config;

// rspack.dev.js
import config from './rspack.core.js';

// Dev-specific configuration
// ...
export default config.toConfig();

// rspack.prod.js
import config from './rspack.core.js';

// Production-specific configuration
// ...
export default config.toConfig();
```

## ChainedMap

One of the core API interfaces in rspack-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.**

```js
// Remove all entries from a Map.
clear();
```

```js
// Remove a single entry from a Map given its key.
// key: *
delete key;
```

```js
// Fetch the value from a Map located at the corresponding key.
// key: *
// returns: value
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: *
// value: *
set(key, value);
```

```js
// Returns `true` or `false` based on whether a Map as has a value set at a
// particular key.
// key: *
// returns: Boolean
has(key);
```

```js
// Returns an array of all the values stored in the Map.
// returns: Array
values();
```

```js
// 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();
```

```js
// 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);
```

```js
// 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);
```

```js
// 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);
```

## ChainedValueMap

`ChainedValueMap` inherited `ChainedMap` but callable. call it by value means to
set it to it and clean all data in map. set any key/value in map will clear value
setted by call it.

It is chainable,so calling it will return the original instance, allowing you to continue to chain.
For example, `config.node` is a `ChainedValueMap` instance, so it can be used as:

```js
// call as function will setting a value on a ChainedMap
config.node(false);

// use as `ChainedMap`
config.node.set('amd', 'true');
```

## ChainedSet

Another of the core API interfaces in rspack-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.**

```js
// Add/append a value to the end of a Set.
// value: *
add(value);
```

```js
// Add a value to the beginning of a Set.
// value: *
prepend(value);
```

```js
// Remove all values from a Set.
clear();
```

```js
// Remove a specific value from a Set.
// value: *
delete value;
```

```js
// Returns `true` or `false` based on whether or not the
// backing Set contains the specified value.
// value: *
// returns: Boolean
has(value);
```

```js
// Returns an array of values contained in the backing Set.
// returns: Array
values();
```

```js
// Concatenates the given array to the end of the backing Set.
// arr: Array
merge(arr);
```

```js
// 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);
```

```js
// 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);
```

## 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:

```js
// 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.

### RspackChain

Create a new configuration object.

```js
import { RspackChain } from 'rspack-chain';

const config = new RspackChain();
```

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
[Rspack docs hierarchy](https://rspack.rs/config/).

```js
RspackChain: ChainedMap;
```

#### shorthand methods

```js
config
  .context(context)
  .mode(mode)
  .devtool(devtool)
  .target(target)
  .watch(watch)
  .watchOptions(watchOptions)
  .externals(externals)
  .externalsType(externalsType)
  .externalsPresets(externalsPresets)
  .stats(stats)
  .experiments(experiments)
  .lazyCompilation(lazyCompilation)
  .amd(amd)
  .bail(bail)
  .cache(cache)
  .dependencies(dependencies)
  .ignoreWarnings(ignoreWarnings)
  .loader(loader)
  .name(name)
  .infrastructureLogging(infrastructureLogging)
  .snapshot(snapshot);
```

#### entryPoints

```js
// 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()
```

#### output: shorthand methods

```js
config.output : ChainedMap

config.output
  .chunkFilename(chunkFilename)
  .chunkLoadTimeout(chunkLoadTimeout)
  .chunkLoadingGlobal(chunkLoadingGlobal)
  .chunkLoading(chunkLoading)
  .chunkFormat(chunkFormat)
  .enabledChunkLoadingTypes(enabledChunkLoadingTypes)
  .crossOriginLoading(crossOriginLoading)
  .devtoolFallbackModuleFilenameTemplate(devtoolFallbackModuleFilenameTemplate)
  .devtoolModuleFilenameTemplate(devtoolModuleFilenameTemplate)
  .devtoolNamespace(devtoolNamespace)
  .filename(filename)
  .assetModuleFilename(assetModuleFilename)
  .globalObject(globalObject)
  .uniqueName(uniqueName)
  .hashDigest(hashDigest)
  .hashDigestLength(hashDigestLength)
  .hashFunction(hashFunction)
  .hashSalt(hashSalt)
  .hotUpdateChunkFilename(hotUpdateChunkFilename)
  .hotUpdateFunction(hotUpdateFunction)
  .hotUpdateMainFilename(hotUpdateMainFilename)
  .library(library)
  .importFunctionName(importFunctionName)
  .path(path)
  .pathinfo(pathinfo)
  .publicPath(publicPath)
  .scriptType(scriptType)
  .sourceMapFilename(sourceMapFilename)
  .strictModuleErrorHandling(strictModuleErrorHandling)
  .strictModuleExceptionHandling(strictModuleExceptionHandling)
  .workerChunkLoading(workerChunkLoading)
  .enabledLibraryTypes(enabledLibraryTypes)
  .environment(environment)
  .compareBeforeEmit(compareBeforeEmit)
  .wasmLoading(wasmLoading)
  .enabledWasmLoadingTypes(enabledWasmLoadingTypes)
  .iife(iife)
  .module(module)
  .clean(clean)
```

#### resolve: shorthand methods

```js
config.resolve : ChainedMap

config.resolve
  .enforceExtension(enforceExtension)
  .symlinks(symlinks)
  .preferRelative(preferRelative)
  .preferAbsolute(preferAbsolute)
  .tsConfig({
    configFile: './tsconfig.json',
    references: 'auto',
  });
```

#### resolve alias

```js
config.resolve.alias : ChainedMap

config.resolve.alias
  .set(key, value)
  .set(key, value)
  .delete(key)
  .clear()
```

#### resolve aliasFields

```js
config.resolve.aliasFields : ChainedSet

config.resolve.aliasFields
  .add(value)
  .prepend(value)
  .clear()
```

#### resolve conditionNames

```js
config.resolve.conditionNames : ChainedSet

config.resolve.conditionNames
  .add(value)
  .prepend(value)
  .clear()
```

#### resolve descriptionFields

```js
config.resolve.descriptionFields : ChainedSet

config.resolve.descriptionFields
  .add(value)
  .prepend(value)
  .clear()
```

#### resolve extensions

```js
config.resolve.extensions : ChainedSet

config.resolve.extensions
  .add(value)
  .prepend(value)
  .clear()
```

#### resolve extensionAlias

```js
config.resolve.extensionAlias : ChainedMap

config.resolve.extensionAlias
  .set(key, value)
  .clear()
```

#### resolve mainFields

```js
config.resolve.mainFields : ChainedSet

config.resolve.mainFields
  .add(value)
  .prepend(value)
  .clear()
```

#### resolve mainFiles

```js
config.resolve.mainFiles : ChainedSet

config.resolve.mainFiles
  .add(value)
  .prepend(value)
  .clear()
```

#### resolve exportsFields

```js
config.resolve.exportsFields : ChainedSet

config.resolve.exportsFields
  .add(value)
  .prepend(value)
  .clear()
```

#### resolve importsFields

```js
config.resolve.importsFields : ChainedSet

config.resolve.importsFields
  .add(value)
  .prepend(value)
  .clear()
```

#### resolve restrictions

```js
config.resolve.restrictions : ChainedSet

config.resolve.restrictions
  .add(value)
  .prepend(value)
  .clear()
```

#### resolve roots

```js
config.resolve.roots : ChainedSet

config.resolve.roots
  .add(value)
  .prepend(value)
  .clear()
```

#### resolve modules

```js
config.resolve.modules : ChainedSet

config.resolve.modules
  .add(value)
  .prepend(value)
  .clear()
```

#### resolve fallback

```js
config.resolve.fallback : ChainedMap

config.resolve.fallback
  .set(key, value)
  .set(key, value)
  .delete(key)
  .clear()
```

#### resolve byDependency

```js
config.resolve.byDependency : ChainedMap

config.resolve.byDependency
  .set(key, value)
  .set(key, value)
  .delete(key)
  .clear()
```

#### resolveLoader

The API for `config.resolveLoader` is identical to `config.resolve` with
the following additions:

#### resolveLoader modules

```js
config.resolveLoader.modules : ChainedSet

config.resolveLoader.modules
  .add(value)
  .prepend(value)
  .clear()
```

#### resolveLoader moduleExtensions

```js
config.resolveLoader.moduleExtensions : ChainedSet

config.resolveLoader.moduleExtensions
  .add(value)
  .prepend(value)
  .clear()
```

#### resolveLoader packageMains

```js
config.resolveLoader.packageMains : ChainedSet

config.resolveLoader.packageMains
  .add(value)
  .prepend(value)
  .clear()
```

#### performance: shorthand methods

```js
config.performance : ChainedValueMap

config.performance(false)
  .performance
  .assetFilter(assetFilter)
  .hints(hints)
  .maxEntrypointSize(maxEntrypointSize)
  .maxAssetSize(maxAssetSize)
```

#### Configuring optimizations: shorthand methods

```js
config.optimization : ChainedMap

config.optimization
  .minimize(minimize)
  .runtimeChunk(runtimeChunk)
  .emitOnErrors(emitOnErrors)
  .moduleIds(moduleIds)
  .chunkIds(chunkIds)
  .nodeEnv(nodeEnv)
  .removeEmptyChunks(removeEmptyChunks)
  .mergeDuplicateChunks(mergeDuplicateChunks)
  .providedExports(providedExports)
  .usedExports(usedExports)
  .concatenateModules(concatenateModules)
  .sideEffects(sideEffects)
  .mangleExports(mangleExports)
  .innerGraph(innerGraph)
  .realContentHash(realContentHash)
```

#### optimization minimizers

```js
// Backed at config.optimization.minimizers
config.optimization
  .minimizer(name) : ChainedMap
```

#### optimization minimizers: adding

_NOTE: Do not use `new` to create the minimizer plugin, as this will be done for you._

```js
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);

config.optimization.minimizer(name).use(RspackPlugin, args);

// Examples

config.optimization
  .minimizer('css')
  .use(OptimizeCSSAssetsPlugin, [{ cssProcessorOptions: { safe: true } }]);

// Minimizer plugins can also be specified by their path, allowing the expensive module loads to be
// skipped in cases where the plugin or Rspack configuration won't end up being used.
config.optimization
  .minimizer('css')
  .use(require.resolve('optimize-css-assets-webpack-plugin'), [
    { cssProcessorOptions: { safe: true } },
  ]);
```

#### optimization minimizers: modify arguments

```js
config.optimization.minimizer(name).tap((args) => newArgs);

// Example
config.optimization
  .minimizer('css')
  .tap((args) => [...args, { cssProcessorOptions: { safe: false } }]);
```

#### optimization minimizers: modify instantiation

```js
config.optimization.minimizer(name).init((Plugin, args) => new Plugin(...args));
```

#### optimization minimizers: removing

```js
config.optimization.minimizers.delete(name);
```

#### optimization splitChunks

```js
config.optimization.splitChunks : ChainedValueMap

config.optimization
  .splitChunks({
    chunks: all
  }
  .set(key, value)
  .set(key, value)
  .delete(key)
  .clear()
```

#### plugins

```js
// Backed at config.plugins
config.plugin(name) : ChainedMap
```

#### plugins: adding

_NOTE: Do not use `new` to create the plugin, as this will be done for you._

```js
import { createRequire } from 'node:module';
import { HotModuleReplacementPlugin, EnvironmentPlugin } from '@rspack/core';

const require = createRequire(import.meta.url);

config.plugin(name).use(RspackPlugin, args);

// Examples

config.plugin('hot').use(HotModuleReplacementPlugin);

// Plugins can also be specified by their path, allowing the expensive module loads to be
// skipped in cases where the plugin or Rspack configuration won't end up being used.
config.plugin('env').use(EnvironmentPlugin, [{ VAR: false }]);

config.plugin('custom').use(require.resolve('my-plugin'), [{ answer: 42 }]);
```

#### plugins: modify arguments

```js
config.plugin(name).tap((args) => newArgs);

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

#### plugins: modify instantiation

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

#### plugins: removing

```js
config.plugins.delete(name);
```

#### 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.

```js
config.plugin(name).before(otherName);

// Example

config
  .plugin('html-template')
  .use(HtmlRspackTemplate)
  .end()
  .plugin('script-ext')
  .use(ScriptExtRspackPlugin)
  .before('html-template');
```

#### 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.

```js
config.plugin(name).after(otherName);

// Example

config
  .plugin('html-template')
  .after('script-ext')
  .use(HtmlRspackTemplate)
  .end()
  .plugin('script-ext')
  .use(ScriptExtRspackPlugin);
```

#### node

```js
config.node : ChainedValueMap

config.node(false)
  .node
  .set('__dirname', 'mock')
  .set('__filename', 'mock');
```

#### devServer

```js
config.devServer : ChainedMap
```

#### devServer: shorthand methods

```js
config.devServer
  .allowedHosts(allowedHosts)
  .app(app)
  .client(client)
  .compress(compress)
  .devMiddleware(devMiddleware)
  .headers(headers)
  .historyApiFallback(historyApiFallback)
  .host(host)
  .hot(hot)
  .liveReload(liveReload)
  .ipc(ipc)
  .onListening(onListening)
  .open(open)
  .port(port)
  .proxy(proxy)
  .server(server)
  .setupExitSignals(setupExitSignals)
  .setupMiddlewares(setupMiddlewares)
  .static(static)
  .watchFiles(watchFiles)
  .webSocketServer(webSocketServer);
```

#### module

```js
config.module : ChainedMap
```

#### module: shorthand methods

```js
config.module : ChainedMap

config.module.noParse(noParse)
```

#### module rules: shorthand methods

```js
config.module.rules : ChainedMap

config.module
  .rule(name)
    .test(test)
    .pre()
    .post()
    .enforce(preOrPost)
```

#### module rules uses (loaders): creating

```js
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-env'] });
```

#### module rules uses (loaders): modifying options

```js
config.module
  .rule(name)
  .use(name)
  .tap((options) => newOptions);

// Example

config.module
  .rule('compile')
  .use('babel')
  .tap((options) =>
    merge(options, {
      plugins: ['@babel/plugin-proposal-class-properties'],
    }),
  );
```

#### module rules nested rules

```js
config.module.rules{}.rules : ChainedMap<Rule>

config.module
  .rule(name)
    .rule(name)

// Example

config.module
  .rule('css')
    .test(/\.css$/)
    .use('style')
      .loader('style-loader')
      .end()
    .rule('postcss')
      .resourceQuery(/postcss/)
      .use('postcss')
        .loader('postcss-loader')
```

#### module rules nested rules: ordering before

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

```js
config.module.rules{}.rules : ChainedMap<Rule>

config.module
  .rule(name)
    .rule(name)
      .before(otherName)

// Example

config.module
  .rule('css')
    .use('style')
      .loader('style-loader')
      .end()
    .rule('postcss')
      .resourceQuery(/postcss/)
      .use('postcss')
        .loader('postcss-loader')
        .end()
      .end()
    .rule('css-loader')
      .resourceQuery(/css-loader/)
      .before('postcss')
      .use('css-loader')
        .loader('css-loader')
```

#### module rules nested rules: ordering after

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

```js
config.module.rules{}.rules : ChainedMap<Rule>

config.module
  .rule(name)
    .rule(name)
      .after(otherName)

// Example

config.module
  .rule('css')
    .use('style')
      .loader('style-loader')
      .end()
    .rule('postcss')
      .resourceQuery(/postcss/)
      .after('css-loader')
      .use('postcss')
        .loader('postcss-loader')
        .end()
      .end()
    .rule('css-loader')
      .resourceQuery(/css-loader/)
      .use('css-loader')
        .loader('css-loader')
```

#### module rules oneOfs (conditional rules)

```js
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')
```

#### module rules oneOfs (conditional rules): ordering before

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

```js
config.module.rule(name).oneOf(name).before();

// Example

config.module
  .rule('scss')
  .test(/\.scss$/)
  .oneOf('normal')
  .use('sass')
  .loader('sass-loader')
  .end()
  .end()
  .oneOf('sass-vars')
  .before('normal')
  .resourceQuery(/\?sassvars/)
  .use('sass-vars')
  .loader('sass-vars-to-js-loader');
```

#### module rules oneOfs (conditional rules): ordering after

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

```js
config.module.rule(name).oneOf(name).after();

// Example

config.module
  .rule('scss')
  .test(/\.scss$/)
  .oneOf('vue')
  .resourceQuery(/\?vue/)
  .use('vue-style')
  .loader('vue-style-loader')
  .end()
  .end()
  .oneOf('normal')
  .use('sass')
  .loader('sass-loader')
  .end()
  .end()
  .oneOf('sass-vars')
  .after('vue')
  .resourceQuery(/\?sassvars/)
  .use('sass-vars')
  .loader('sass-vars-to-js-loader');
```

#### module rules resolve

Specify a resolve configuration to be merged over the default `config.resolve`
for modules that match the rule.

See "RspackChain resolve" sections above for full syntax.

```js
config.module.rule(name).resolve;

// Example

config.module
  .rule('scss')
  .test(/\.scss$/)
  .resolve.symlinks(true);
```

---

### Merging configuration

rspack-chain supports merging in an object to the configuration instance which
matches a layout similar to how the rspack-chain schema is laid out.

**Note:** This object does not match the Rspack configuration schema exactly
(for example the `[name]` keys for entry/rules/plugins), so you may need to transform
Rspack configuration objects (such as those output by rspack-chain's `.toConfig()`)
to match the layout below prior to passing to `.merge()`.

```js
config.merge({ devtool: 'source-map' });

config.get('devtool'); // "source-map"
```

```js
config.merge({
  [key]: value,

  amd,
  bail,
  cache,
  context,
  devtool,
  externals,
  loader,
  mode,
  stats,
  target,
  watch,
  watchOptions,

  entry: {
    [name]: [...values],
  },

  plugin: {
    [name]: {
      plugin: RspackPlugin,
      args: [...args],
      before,
      after,
    },
  },

  devServer: {
    [key]: value,

    allowedHosts,
    app,
    client,
    compress,
    devMiddleware,
    headers,
    historyApiFallback,
    host,
    hot,
    ipc,
    liveReload,
    onListening,
    open,
    port,
    proxy,
    server,
    setupExitSignals,
    setupMiddlewares,
    static,
    watchFiles,
    webSocketServer,
  },

  node: {
    [key]: value,
  },

  optimization: {
    concatenateModules,
    mergeDuplicateChunks,
    minimize,
    minimizer: {
      [name]: {
        plugin: RspackPlugin,
        args: [...args],
        before,
        after,
      },
    },
    namedChunks,
    namedModules,
    nodeEnv,
    noEmitOnErrors,
    occurrenceOrder,
    providedExports,
    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: RspackPlugin,
        args: [...args],
        before,
        after,
      },
    },
  },

  resolveLoader: {
    [key]: value,

    alias: {
      [key]: value,
    },
    aliasFields: [...values],
    descriptionFields: [...values],
    extensions: [...values],
    mainFields: [...values],
    mainFiles: [...values],
    modules: [...values],
    moduleExtensions: [...values],
    packageMains: [...values],

    plugin: {
      [name]: {
        plugin: RspackPlugin,
        args: [...args],
        before,
        after,
      },
    },
  },

  module: {
    [key]: value,

    rule: {
      [name]: {
        [key]: value,

        dependency,
        enforce,
        issuer,
        issuerLayer,
        mimetype,
        parser,
        resource,
        resourceFragment,
        resourceQuery,
        with,
        test,

        include: [...paths],
        exclude: [...paths],

        rules: {
          [name]: Rule,
        },

        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.

```js
// Example: Only add minify plugin during production
config.when(process.env.NODE_ENV === 'production', (config) => {
  config.plugin('minify').use(BabiliRspackPlugin);
});
```

```js
// 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(BabiliRspackPlugin),
  (config) => config.devtool('source-map'),
);
```

### Inspecting generated configuration

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

```js
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 Rspack config
if it contains objects and plugins that need external bindings. In order to
generate usable config, you can customize how objects and plugins are
stringified by setting a special `__expression` property on them:

```js
import sass from 'sass';
import MyPlugin from 'my-plugin';
import myFunction from 'my-function';

sass.__expression = 'sass';
MyPlugin.__expression = 'MyPlugin';
myFunction.__expression = 'myFunction';

config
  .plugin('example')
  .use(MyPlugin, [{ fn: myFunction, implementation: sass }]);

config.toString();

/*
{
  plugins: [
    new MyPlugin({
      fn: myFunction,
      implementation: sass
    })
  ]
}
*/
```

You can also call `toString` as a static method on `RspackChain` in order to
modify the configuration object prior to stringifying.

```js
RspackChain.toString({
  ...config.toConfig(),
  module: {
    defaultRules: [
      {
        use: [
          {
            loader: 'banner-loader',
            options: { prefix: 'banner-prefix.txt' },
          },
        ],
      },
    ],
  },
});
```

```js
{
  plugins: [
    /* config.plugin('foo') */
    new TestPlugin()
  ],
  module: {
    defaultRules: [
      {
        use: [
          {
            loader: 'banner-loader',
            options: {
              prefix: 'banner-prefix.txt'
            }
          }
        ]
      }
    ]
  }
}
```
