Skip to content
Bullet

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;
  • chain is the rspack-chain instance for the current configuration.
  • context.CHAIN_ID holds the identifiers Bullet used to register its built-in rules, loaders, and plugins. Use them to find and edit existing entries.
  • context.platform is web, android, ios, or node.
  • context.mode is development or production.

Adding to the chain

Add new entries the same way Bullet does internally. This example adds a ~ alias that points at your source directory:

rspack.config.ts
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:

rspack.config.ts
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:

rspack.config.ts
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.

KindIdentifierRefers to
PLUGINREPACKThe core Repack plugin.
PLUGINMANIFESTThe asset manifest plugin.
PLUGINREACT_REFRESHThe React Refresh plugin.
PLUGINHOT_MODULE_REPLACEMENTThe hot module replacement plugin.
PLUGINHOT_MODULE_REPLACEMENT_CLIENT_ENTRYThe plugin that injects the HMR client entry.
PLUGINREPACK_DEFINEThe DefinePlugin that sets __DEV__ and EXPO_OS.
RULEJSThe rule that matches JavaScript, TypeScript, and JSX.
RULEASSETThe rule that matches images, fonts, and other assets.
USEREPACK_BABEL_SWCThe SWC loader applied to the JS rule.
USEASSETThe loader applied to the ASSET rule.