Configuring the bundler
Customize rspack.config.ts
Each configuration builder in rspack.config.ts accepts an optional second
argument: a chain callback. Bullet builds every configuration with
rspack-chain, a fluent API
for editing an Rspack configuration by reference. The callback runs after Bullet
applies its own defaults, so you can add new rules and plugins or adjust the ones
Bullet already registered.
The chain callback
The callback receives the configuration chain and a context object:
type ChainCallback = (
chain: RspackChain,
context: { CHAIN_ID: ChainIdentifier; platform: Platform; mode: Mode },
) => void;chainis therspack-chaininstance for the current configuration.context.CHAIN_IDholds the identifiers Bullet used to register its built-in rules, loaders, and plugins. Use them to find and edit existing entries.context.platformisweb,android,ios, ornode.context.modeisdevelopmentorproduction.
Adding to the chain
Add new entries the same way Bullet does internally. This example adds a ~
alias that points at your source directory:
import { resolve } from "node:path";
import { createClientConfiguration } from "@clutchify/bullet-core";
createClientConfiguration(
{ name: "client", entry: "./src/index.tsx" },
(chain) => {
chain.resolve.alias.set("~", resolve("./src"));
},
);Modifying existing loaders and plugins
Because Bullet registers its loaders and plugins under stable identifiers, you
can reach into them through context.CHAIN_ID and change their options. Use
.tap() to receive the current options and return the updated set.
This example turns off lazyImports on the SWC loader that Bullet applies to
your JavaScript and TypeScript files:
import { createClientConfiguration } from "@clutchify/bullet-core";
createClientConfiguration(
{ name: "client", entry: "./src/index.tsx" },
(chain, { CHAIN_ID }) => {
chain.module
.rule(CHAIN_ID.RULE.JS)
.use(CHAIN_ID.USE.REPACK_BABEL_SWC)
.tap((options) => ({ ...options, lazyImports: false }));
},
);You can share one callback across every platform and branch on
context.platform or context.mode when a change applies to a single target:
import type { ChainCallback } from "@clutchify/bullet-core";
import { resolve } from "node:path";
import {
createClientConfiguration,
createNativeConfiguration,
createServerConfiguration,
} from "@clutchify/bullet-core";
import { defineConfig } from "@rspack/cli";
const customize: ChainCallback = (chain, { CHAIN_ID, platform }) => {
chain.resolve.alias.set("~", resolve("./src"));
if (platform === "web") {
chain.module
.rule(CHAIN_ID.RULE.JS)
.use(CHAIN_ID.USE.REPACK_BABEL_SWC)
.tap((options) => ({ ...options, lazyImports: false }));
}
};
export default defineConfig([
createClientConfiguration({ name: "client", entry: "./src/index.tsx" }, customize),
createServerConfiguration({ name: "server", entry: "./src/index.server.tsx" }, customize),
createNativeConfiguration({ name: "android", entry: "./src/index.tsx", platform: "android" }, customize),
createNativeConfiguration({ name: "ios", entry: "./src/index.tsx", platform: "ios" }, customize),
]);Chain identifiers
context.CHAIN_ID groups the built-in identifiers by kind. Pass them to
chain.plugin(), chain.module.rule(), and .use() to select an existing
entry.
| Kind | Identifier | Refers to |
|---|---|---|
PLUGIN | REPACK | The core Repack plugin. |
PLUGIN | MANIFEST | The asset manifest plugin. |
PLUGIN | REACT_REFRESH | The React Refresh plugin. |
PLUGIN | HOT_MODULE_REPLACEMENT | The hot module replacement plugin. |
PLUGIN | HOT_MODULE_REPLACEMENT_CLIENT_ENTRY | The plugin that injects the HMR client entry. |
PLUGIN | REPACK_DEFINE | The DefinePlugin that sets __DEV__ and EXPO_OS. |
RULE | JS | The rule that matches JavaScript, TypeScript, and JSX. |
RULE | ASSET | The rule that matches images, fonts, and other assets. |
USE | REPACK_BABEL_SWC | The SWC loader applied to the JS rule. |
USE | ASSET | The loader applied to the ASSET rule. |