Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
72d0ecf
docs: add output.autoExternal guidance
elecmonkey May 27, 2026
05a3a06
test: update autoExternal coverage
elecmonkey May 27, 2026
3f14a22
refactor: migrate autoExternal to Rsbuild output
elecmonkey May 27, 2026
f1729be
test: cover autoExternal merge precedence
elecmonkey May 27, 2026
99d1fa6
fix: force disable autoExternal for exe builds
elecmonkey May 27, 2026
f28abb2
fix: preserve deprecated autoExternal precedence
elecmonkey May 27, 2026
72c73b6
docs: update autoExternal references to output.autoExternal
elecmonkey May 27, 2026
ebc1ad9
docs: update third-party dependency handling to reflect output.autoEx…
elecmonkey May 27, 2026
4079f62
fix: move autoExternal normalization earlier
elecmonkey May 28, 2026
46d48fb
Merge remote-tracking branch 'origin/main' into refactor/migrate-auto…
elecmonkey May 28, 2026
37cf023
docs: clarify third-party dependency handling
elecmonkey May 28, 2026
a67a5cd
Merge remote-tracking branch 'origin/main' into refactor/migrate-auto…
elecmonkey Jun 29, 2026
204311f
test: update auto external snapshots
elecmonkey Jun 29, 2026
dddc7b2
Merge remote-tracking branch 'origin/main' into refactor/migrate-auto…
elecmonkey Jul 17, 2026
f0a56ba
Merge remote-tracking branch 'origin/main' into refactor/migrate-auto…
elecmonkey Jul 18, 2026
140a9a2
weird mock of rslog
elecmonkey Jul 18, 2026
d445104
revert deleted test
elecmonkey Jul 18, 2026
a12ea92
Merge remote-tracking branch 'origin/main' into refactor/migrate-auto…
elecmonkey Jul 21, 2026
ecc78f5
refine docs
elecmonkey Jul 21, 2026
003de77
refactor: simplify autoExternal deprecation shim
elecmonkey Jul 21, 2026
4e6b349
docs: address reviewer feedback on third-party-deps guide
elecmonkey Jul 21, 2026
790f176
docs: merge externals sections and fix wording
elecmonkey Jul 21, 2026
c5fb629
docs: refine third-party dependency guide
Timeless0911 Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/core/src/cli/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ export const applyCliOptions = (
if (options.dts !== undefined) lib.dts = options.dts;
if (options.autoExtension !== undefined)
lib.autoExtension = options.autoExtension;
if (options.autoExternal !== undefined)
lib.autoExternal = options.autoExternal;
lib.output ??= {};
if (options.autoExternal !== undefined)
lib.output.autoExternal = options.autoExternal;
if (options.target !== undefined)
lib.output.target = options.target as RsbuildConfigOutputTarget;
if (options.minify !== undefined) lib.output.minify = options.minify;
Expand Down
126 changes: 31 additions & 95 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { composeExeConfig } from './exe';
import { composeEntryChunkConfig } from './plugins/EntryChunkPlugin';
import { pluginCjsShims, pluginEsmRequireShim } from './plugins/shims';
import type {
AutoExternal,
BannerAndFooter,
DeepRequired,
ExcludesFalse,
Expand Down Expand Up @@ -56,7 +55,6 @@ import {
isDirectory,
isEmptyObject,
isIntermediateOutputFormat,
isObject,
nodeBuiltInModules,
normalizeSlash,
omit,
Expand All @@ -72,88 +70,6 @@ import {
} from './utils/syntax';
import { loadTsconfig } from './utils/tsconfig';

const getAutoExternalDefaultValue = (
format: Format,
autoExternal?: AutoExternal,
): AutoExternal => {
return autoExternal ?? isIntermediateOutputFormat(format);
};

export const composeAutoExternalConfig = (options: {
bundle: boolean;
format: Format;
autoExternal?: AutoExternal;
pkgJson?: PkgJson;
userExternals?: NonNullable<EnvironmentConfig['output']>['externals'];
}): EnvironmentConfig => {
const { bundle, format, pkgJson, userExternals } = options;

// If bundle is false, autoExternal will be disabled
if (!bundle) {
return {};
}

const autoExternal = getAutoExternalDefaultValue(
format,
options.autoExternal,
);

if (autoExternal === false) {
return {};
}

if (!pkgJson) {
logger.warn(
'The `autoExternal` configuration will not be applied due to read package.json failed',
);
return {};
}

// User externals configuration has higher priority than autoExternal
// eg: autoExternal: ['react'], user: output: { externals: { react: 'react-1' } }
// Only handle the case where the externals type is object, string / string[] does not need to be processed, other types are too complex.
const userExternalKeys =
userExternals && isObject(userExternals) ? Object.keys(userExternals) : [];

const externalOptions = {
dependencies: true,
optionalDependencies: true,
peerDependencies: true,
devDependencies: false,
...(autoExternal === true ? {} : autoExternal),
};

const externals = (
[
'dependencies',
'peerDependencies',
'devDependencies',
'optionalDependencies',
] as const
)
.reduce<string[]>((prev, type) => {
if (externalOptions[type]) {
return pkgJson[type] ? prev.concat(Object.keys(pkgJson[type])) : prev;
}
return prev;
}, [])
.filter((name) => !userExternalKeys.includes(name));

const uniqueExternals = Array.from(new Set(externals));

return externals.length
? {
output: {
externals: [
// Exclude dependencies, e.g. `react`, `react/jsx-runtime`
...uniqueExternals.map((dep) => new RegExp(`^${dep}($|\\/|\\\\)`)),
...uniqueExternals,
],
},
}
: {};
};

export function composeMinifyConfig(config: LibConfig): EnvironmentConfig {
const minify = config.output?.minify;
const format = config.format;
Expand Down Expand Up @@ -406,6 +322,7 @@ const composeFormatConfig = ({
plugins: [modifyRsbuildDefaultPlugin({ disableUrlParse: true })],
output: {
filenameHash: false,
...(bundle && { autoExternal: true }),
Comment thread
elecmonkey marked this conversation as resolved.
},
tools: {
rspack: {
Expand Down Expand Up @@ -451,6 +368,7 @@ const composeFormatConfig = ({
output: {
module: false,
filenameHash: false,
...(bundle && { autoExternal: true }),
},
tools: {
rspack: {
Expand Down Expand Up @@ -1476,7 +1394,7 @@ const composeDtsConfig = async (
format: Format,
dtsExtension: string,
): Promise<EnvironmentConfig> => {
const { autoExternal, banner, footer, redirect } = libConfig;
const { banner, footer, redirect } = libConfig;

let { dts } = libConfig;

Expand All @@ -1499,7 +1417,10 @@ const composeDtsConfig = async (
build: dts?.build,
abortOnError: dts?.abortOnError,
dtsExtension: dts?.autoExtension ? dtsExtension : '.d.ts',
autoExternal: getAutoExternalDefaultValue(format, autoExternal),
autoExternal:
libConfig.output?.autoExternal ??
libConfig.autoExternal ??
isIntermediateOutputFormat(format),
Comment on lines +1420 to +1423

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor autoExternal exclude/packageJson for bundled dts

When dts.bundle is enabled and users configure Rsbuild-only options such as output.autoExternal: { exclude: ['foo'] } or packageJson, passing this object straight through makes rsbuild-plugin-dts ignore those fields: its bundled-package calculation only understands dependency booleans and always reads the root package.json. The JS build will bundle excluded packages (or read a different package file), but the declaration bundle will still treat them as external, so emitted types no longer match the JS dependency handling unless users manually duplicate dts.bundle.bundledPackages.

Useful? React with 👍 / 👎.

alias: dts?.alias,
isolated: dts?.isolated,
banner: banner?.dts,
Expand Down Expand Up @@ -1653,13 +1574,13 @@ async function composeLibRsbuildConfig(
banner = {},
footer = {},
autoExtension = true,
autoExternal,
externalHelpers = false,
redirect = {},
umdName,
experiments = {},
} = config;
const hasExe = Boolean(experiments.exe);

const { rsbuildConfig: bundleConfig } = composeBundleConfig(bundle);
const { rsbuildConfig: shimsConfig, enabledShims } = composeShimsConfig(
format,
Expand Down Expand Up @@ -1726,13 +1647,7 @@ async function composeLibRsbuildConfig(
config?.syntax,
pkgJson,
);
const autoExternalConfig = composeAutoExternalConfig({
bundle,
format,
autoExternal: hasExe ? false : autoExternal,
pkgJson,
userExternals,
});

const cssConfig = composeCssConfig(
outBase,
cssModulesAuto,
Expand Down Expand Up @@ -1772,7 +1687,6 @@ async function composeLibRsbuildConfig(
// it relies on other externals config to bail out the externalized modules first then resolve
// the correct path for relative imports.
userExternalsConfig,
autoExternalConfig,
targetExternalsConfig,
bundlelessExternalConfig,
// #endregion
Expand Down Expand Up @@ -1820,11 +1734,33 @@ export async function composeCreateRsbuildConfig(
}

const libConfigPromises = libConfigsArray.map(async (libConfig, index) => {
if (libConfig.autoExternal !== undefined) {
logger.warn(
`${color.yellow('lib.autoExternal')} is deprecated, use ${color.yellow('output.autoExternal')} instead.`,
);
}

const userConfig = mergeRsbuildConfig<LibConfig>(
sharedRsbuildConfig,
libConfig,
);

// Deprecation shim: migrate lib.autoExternal → output.autoExternal
if (
libConfig.autoExternal !== undefined &&
libConfig.output?.autoExternal === undefined
Comment on lines +1750 to +1751

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve shared deprecated autoExternal settings

When users still rely on the documented top-level deprecated setting, e.g. autoExternal: false as a shared lib option, this shim never copies it into userConfig.output.autoExternal because it only checks the per-lib item. The merged userConfig.autoExternal is later omitted from the final Rsbuild config, while composeFormatConfig adds output.autoExternal: true for ESM/CJS, so those projects silently start externalizing dependencies instead of bundling them.

Useful? React with 👍 / 👎.

) {
userConfig.output ??= {};
userConfig.output.autoExternal = libConfig.autoExternal;
}

// Exe bundles everything into a single binary, so disable automatic externals
// after user config is merged to prevent users from re-enabling it.
if (userConfig.experiments?.exe) {
userConfig.output ??= {};
userConfig.output.autoExternal = false;
}

// Merge the configuration of each environment based on the shared Rsbuild
// configuration and Lib configuration in the settings.
const libRsbuildConfig = await composeLibRsbuildConfig(
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ export interface LibConfig extends EnvironmentConfig {
autoExtension?: boolean;
/**
* Whether to automatically externalize dependencies of different dependency types and do not bundle them.
* @deprecated Use `output.autoExternal` instead.
* @defaultValue `true` when {@link format} is `cjs` or `esm`, `false` when {@link format} is `umd` or `mf`.
* @see {@link https://rslib.rs/config/lib/auto-external}
*/
Expand Down Expand Up @@ -521,6 +522,11 @@ interface RslibOutputConfig extends OutputConfig {
* @see {@link https://rslib.rs/config/rsbuild/output#outputminify}
*/
minify?: OutputConfig['minify'];
/**
* @override
* @default `true` for ESM/CJS, `false` for UMD/MF/IIFE
*/
autoExternal?: OutputConfig['autoExternal'];
}

export interface RslibConfig extends RsbuildConfig, SharedLibConfig {
Expand Down
46 changes: 12 additions & 34 deletions packages/core/tests/__snapshots__/config.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -324,14 +324,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
]
},
externals: [
/^@rsbuild\\/core($|\\/|\\\\)/,
/^rsbuild-plugin-dts($|\\/|\\\\)/,
/^@microsoft\\/api-extractor($|\\/|\\\\)/,
/^typescript($|\\/|\\\\)/,
'@rsbuild/core',
'rsbuild-plugin-dts',
'@microsoft/api-extractor',
'typescript',
'assert',
'assert/strict',
'async_hooks',
Expand Down Expand Up @@ -387,7 +379,11 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
'worker_threads',
'zlib',
/^node:/,
'pnpapi'
'pnpapi',
/^@rsbuild\\/core(?:$|[/\\\\])/,
/^rsbuild-plugin-dts(?:$|[/\\\\])/,
/^@microsoft\\/api-extractor(?:$|[/\\\\])/,
/^typescript(?:$|[/\\\\])/
],
node: {
__dirname: false,
Expand Down Expand Up @@ -1198,14 +1194,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
]
},
externals: [
/^@rsbuild\\/core($|\\/|\\\\)/,
/^rsbuild-plugin-dts($|\\/|\\\\)/,
/^@microsoft\\/api-extractor($|\\/|\\\\)/,
/^typescript($|\\/|\\\\)/,
'@rsbuild/core',
'rsbuild-plugin-dts',
'@microsoft/api-extractor',
'typescript',
'assert',
'assert/strict',
'async_hooks',
Expand Down Expand Up @@ -1261,7 +1249,11 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
'worker_threads',
'zlib',
/^node:/,
'pnpapi'
'pnpapi',
/^@rsbuild\\/core(?:$|[/\\\\])/,
/^rsbuild-plugin-dts(?:$|[/\\\\])/,
/^@microsoft\\/api-extractor(?:$|[/\\\\])/,
/^typescript(?:$|[/\\\\])/
],
output: {
devtoolModuleFilenameTemplate: '[relative-resource-path]',
Expand Down Expand Up @@ -4298,6 +4290,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
"config": {
"output": {
"assetPrefix": "auto",
"autoExternal": true,
"dataUriLimit": 0,
"distPath": {
"css": "./",
Expand All @@ -4306,14 +4299,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
"jsAsync": "./",
},
"externals": [
/\\^@rsbuild\\\\/core\\(\\$\\|\\\\/\\|\\\\\\\\\\)/,
/\\^rsbuild-plugin-dts\\(\\$\\|\\\\/\\|\\\\\\\\\\)/,
/\\^@microsoft\\\\/api-extractor\\(\\$\\|\\\\/\\|\\\\\\\\\\)/,
/\\^typescript\\(\\$\\|\\\\/\\|\\\\\\\\\\)/,
"@rsbuild/core",
"rsbuild-plugin-dts",
"@microsoft/api-extractor",
"typescript",
"assert",
"assert/strict",
"async_hooks",
Expand Down Expand Up @@ -4570,6 +4555,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
"config": {
"output": {
"assetPrefix": "auto",
"autoExternal": true,
"dataUriLimit": 0,
"distPath": {
"css": "./",
Expand All @@ -4578,14 +4564,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i
"jsAsync": "./",
},
"externals": [
/\\^@rsbuild\\\\/core\\(\\$\\|\\\\/\\|\\\\\\\\\\)/,
/\\^rsbuild-plugin-dts\\(\\$\\|\\\\/\\|\\\\\\\\\\)/,
/\\^@microsoft\\\\/api-extractor\\(\\$\\|\\\\/\\|\\\\\\\\\\)/,
/\\^typescript\\(\\$\\|\\\\/\\|\\\\\\\\\\)/,
"@rsbuild/core",
"rsbuild-plugin-dts",
"@microsoft/api-extractor",
"typescript",
"assert",
"assert/strict",
"async_hooks",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ describe('applyCliOptions', () => {
expect(lib.bundle).toBe(false);
expect(lib.dts).toBe(true);
expect(lib.autoExtension).toBe(false);
expect(lib.autoExternal).toBe(false);
expect(lib.output?.autoExternal).toBe(false);
expect(lib.syntax).toEqual(['node 18']);
expect(lib.source?.tsconfigPath).toBe('./tsconfig.build.json');
expect(lib.source?.entry).toEqual({
Expand Down
Loading