From 72d0ecfbbef6ac789fdf243ddbbaefac0e63a13b Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Wed, 27 May 2026 10:12:12 +0800 Subject: [PATCH 01/10] docs: add output.autoExternal guidance --- website/docs/en/config/lib/auto-external.mdx | 4 ++++ website/docs/en/config/rsbuild/output.mdx | 17 ++++++++++++++++- website/docs/zh/config/lib/auto-external.mdx | 4 ++++ website/docs/zh/config/rsbuild/output.mdx | 17 ++++++++++++++++- 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/website/docs/en/config/lib/auto-external.mdx b/website/docs/en/config/lib/auto-external.mdx index c4c0fe33b..c9ee9e45c 100644 --- a/website/docs/en/config/lib/auto-external.mdx +++ b/website/docs/en/config/lib/auto-external.mdx @@ -5,6 +5,10 @@ overviewHeaders: [2, 3] # lib.autoExternal +:::warning +`lib.autoExternal` is deprecated. Please use [output.autoExternal](/config/rsbuild/output#outputautoexternal) instead. +::: + :::info `autoExternal` is a specific configuration for bundle mode. It will not take effect in bundleless mode (set [lib.bundle](/config/lib/bundle) to `false`) since deps will not be bundled in bundleless mode. diff --git a/website/docs/en/config/rsbuild/output.mdx b/website/docs/en/config/rsbuild/output.mdx index 8624f321f..5eb683769 100644 --- a/website/docs/en/config/rsbuild/output.mdx +++ b/website/docs/en/config/rsbuild/output.mdx @@ -21,6 +21,21 @@ When `output.assetPrefix` is set to `"auto"`, Rslib defaults to setting [importM When `output.assetPrefix` is set to a specific path, the static asset import statements in JavaScript files will no longer be preserved and will be replaced with URLs prefixed by that path. Additionally, the static asset paths in CSS files will be substituted with paths that include this prefix. +## output.autoExternal + +- **CLI:** `--auto-external` / `--no-auto-external` + +Whether to automatically externalize dependencies of different dependency types and do not bundle them. This is a specific configuration for bundle mode, it will not take effect in bundleless mode. + +In Rslib, the default value for this option depends on [format](/config/lib/format): + +- When `format` is `cjs` or `esm`, the default value is `true`. +- When `format` is `umd`, `mf`, or `iife`, the default value is `false`. + +When set to `true`, the `dependencies`, `optionalDependencies`, and `peerDependencies` listed in `package.json` will be automatically externalized. + +For more details about handling third-party dependencies, please refer to [Handle Third-party Dependencies](/guide/advanced/third-party-deps). + ## output.charset The `charset` config allows you to specify the [character encoding](https://developer.mozilla.org/en-US/docs/Glossary/Character_encoding) for output files to ensure they are displayed correctly in different environments. @@ -83,7 +98,7 @@ Whether to emit CSS to the output bundles. At build time, prevent some `import` dependencies from being packed into bundles in your code, and instead fetch them externally at runtime. -In bundle mode, Rslib will automatically add the dependencies listed in the `dependencies`, `optionalDependencies`, and `peerDependencies` fields of `package.json` to `output.externals`. See [lib.autoExternal](/config/lib/auto-external) for more information. +In bundle mode, Rslib will automatically externalize the dependencies listed in `package.json` based on [output.autoExternal](#outputautoexternal). See [output.autoExternal](#outputautoexternal) for more information. :::note It is important to note that `output.externals` differs from [resolve.alias](/config/rsbuild/resolve#resolvealias). Check out [resolve.alias](/config/rsbuild/resolve#resolvealias) documentation for more information. diff --git a/website/docs/zh/config/lib/auto-external.mdx b/website/docs/zh/config/lib/auto-external.mdx index ed381026d..8c0127f68 100644 --- a/website/docs/zh/config/lib/auto-external.mdx +++ b/website/docs/zh/config/lib/auto-external.mdx @@ -5,6 +5,10 @@ overviewHeaders: [2, 3] # lib.autoExternal +:::warning +`lib.autoExternal` 已废弃,请使用 [output.autoExternal](/config/rsbuild/output#outputautoexternal) 代替。 +::: + :::info `autoExternal` 是 bundle 模式的特定配置。在 bundleless 模式(将 [lib.bundle](/config/lib/bundle) 设置为 `false`)下不会生效,因为 bundleless 模式下依赖不会被打包。 diff --git a/website/docs/zh/config/rsbuild/output.mdx b/website/docs/zh/config/rsbuild/output.mdx index 298bb8737..f16b8024f 100644 --- a/website/docs/zh/config/rsbuild/output.mdx +++ b/website/docs/zh/config/rsbuild/output.mdx @@ -21,6 +21,21 @@ import { RsbuildDocBadge } from '@components/RsbuildDocBadge'; 当 `output.assetPrefix` 设置为具体的路径时,JavaScript 文件中引用静态资源的 `import` 或 `require` 语句会被替换为包含该前缀的 URL。同时,CSS 文件中的静态资源会被直接替换为带有该前缀的路径。 +## output.autoExternal + +- **命令行:** `--auto-external` / `--no-auto-external` + +是否自动对不同依赖类型的依赖进行外部化处理,不将其打包。该配置仅在 bundle 模式下生效,bundleless 模式下不会生效。 + +在 Rslib 中,该选项的默认值由 [format](/config/lib/format) 决定: + +- 当 `format` 为 `cjs` 或 `esm` 时,默认值为 `true`。 +- 当 `format` 为 `umd`、`mf` 或 `iife` 时,默认值为 `false`。 + +设置为 `true` 时,`package.json` 中 `dependencies`、`optionalDependencies` 和 `peerDependencies` 字段下的依赖将被自动外部化。 + +关于第三方依赖处理的更多细节,请参考 [处理第三方依赖](/guide/advanced/third-party-deps)。 + ## output.charset 指定输出文件的 [字符编码](https://developer.mozilla.org/en-US/docs/Glossary/Character_encoding),以确保它们在不同的环境中能够正确显示。 @@ -79,7 +94,7 @@ const defaultDistPath = { 在构建时,防止将代码中某些 `import` 的依赖包打包到 bundle 中,而是在运行时再去从外部获取这些依赖。 -在 bundle 模式下,Rslib 会默认将 `package.json` 中 `dependencies`、`optionalDependencies` 和 `peerDependencies` 字段下的三方依赖添加到 `output.externals` 中, 查看 [lib.autoExternal](/config/lib/auto-external) 了解更多信息。 +在 bundle 模式下,Rslib 会根据 [output.autoExternal](#outputautoexternal) 自动将 `package.json` 中的依赖进行外部化处理,查看 [output.autoExternal](#outputautoexternal) 了解更多信息。 :::note 需要注意的是,`output.externals` 与 [resolve.alias](/config/rsbuild/resolve#resolvealias) 有所不同。请查看 [resolve.alias](/config/rsbuild/resolve#resolvealias) 文档以了解更多信息。 From 05a3a0671b7f64ddaa20d0d705ef490c452cc0cb Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Wed, 27 May 2026 10:12:30 +0800 Subject: [PATCH 02/10] test: update autoExternal coverage --- .../tests/__snapshots__/config.test.ts.snap | 56 ++-- packages/core/tests/cli.test.ts | 2 +- packages/core/tests/config.test.ts | 3 +- packages/core/tests/external.test.ts | 288 ------------------ tests/integration/auto-external/index.test.ts | 40 +-- tests/integration/externals/index.test.ts | 36 --- 6 files changed, 24 insertions(+), 401 deletions(-) delete mode 100644 packages/core/tests/external.test.ts diff --git a/packages/core/tests/__snapshots__/config.test.ts.snap b/packages/core/tests/__snapshots__/config.test.ts.snap index c6259a780..2b4c97151 100644 --- a/packages/core/tests/__snapshots__/config.test.ts.snap +++ b/packages/core/tests/__snapshots__/config.test.ts.snap @@ -321,15 +321,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i ] }, externals: [ - function () { /* omitted long function */ }, - /^@rsbuild\\/core($|\\/|\\\\)/, - /^rsbuild-plugin-dts($|\\/|\\\\)/, - /^@microsoft\\/api-extractor($|\\/|\\\\)/, - /^typescript($|\\/|\\\\)/, - '@rsbuild/core', - 'rsbuild-plugin-dts', - '@microsoft/api-extractor', - 'typescript', 'assert', 'assert/strict', 'async_hooks', @@ -385,7 +376,15 @@ 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($|\\/|\\\\)/, + '@rsbuild/core', + 'rsbuild-plugin-dts', + '@microsoft/api-extractor', + 'typescript' ], node: { __dirname: false, @@ -1092,14 +1091,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', @@ -1155,7 +1146,15 @@ 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($|\\/|\\\\)/, + '@rsbuild/core', + 'rsbuild-plugin-dts', + '@microsoft/api-extractor', + 'typescript' ], output: { devtoolModuleFilenameTemplate: '[relative-resource-path]', @@ -3789,6 +3788,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i "config": { "output": { "assetPrefix": "auto", + "autoExternal": true, "dataUriLimit": 0, "distPath": { "css": "./", @@ -3797,15 +3797,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i "jsAsync": "./", }, "externals": [ - [Function], - /\\^@rsbuild\\\\/core\\(\\$\\|\\\\/\\|\\\\\\\\\\)/, - /\\^rsbuild-plugin-dts\\(\\$\\|\\\\/\\|\\\\\\\\\\)/, - /\\^@microsoft\\\\/api-extractor\\(\\$\\|\\\\/\\|\\\\\\\\\\)/, - /\\^typescript\\(\\$\\|\\\\/\\|\\\\\\\\\\)/, - "@rsbuild/core", - "rsbuild-plugin-dts", - "@microsoft/api-extractor", - "typescript", "assert", "assert/strict", "async_hooks", @@ -4067,6 +4058,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i "config": { "output": { "assetPrefix": "auto", + "autoExternal": true, "dataUriLimit": 0, "distPath": { "css": "./", @@ -4075,14 +4067,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", diff --git a/packages/core/tests/cli.test.ts b/packages/core/tests/cli.test.ts index afa848b3e..e9a868bcf 100644 --- a/packages/core/tests/cli.test.ts +++ b/packages/core/tests/cli.test.ts @@ -130,7 +130,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({ diff --git a/packages/core/tests/config.test.ts b/packages/core/tests/config.test.ts index 6fc43a4d6..9bc19ebbc 100644 --- a/packages/core/tests/config.test.ts +++ b/packages/core/tests/config.test.ts @@ -397,10 +397,11 @@ describe('CLI options', () => { "lib": [ { "autoExtension": false, - "autoExternal": false, + "autoExternal": true, "bundle": false, "dts": true, "output": { + "autoExternal": false, "cleanDistPath": true, "distPath": { "root": "build", diff --git a/packages/core/tests/external.test.ts b/packages/core/tests/external.test.ts deleted file mode 100644 index dacecd499..000000000 --- a/packages/core/tests/external.test.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { describe, expect, it, rs } from '@rstest/core'; -import { composeAutoExternalConfig } from '../src/config'; - -rs.mock('rslog'); - -describe('should composeAutoExternalConfig correctly', () => { - it('autoExternal default value', () => { - const esmResult = composeAutoExternalConfig({ - bundle: true, - format: 'esm', - autoExternal: undefined, - pkgJson: { - name: 'esm', - dependencies: { - foo: '1.0.0', - }, - }, - }); - - const cjsResult = composeAutoExternalConfig({ - bundle: true, - format: 'cjs', - autoExternal: undefined, - pkgJson: { - name: 'cjs', - dependencies: { - foo: '1.0.0', - }, - }, - }); - - const umdResult = composeAutoExternalConfig({ - bundle: true, - format: 'umd', - autoExternal: undefined, - pkgJson: { - name: 'umd', - dependencies: { - foo: '1.0.0', - }, - }, - }); - - const mfResult = composeAutoExternalConfig({ - bundle: true, - format: 'mf', - autoExternal: undefined, - pkgJson: { - name: 'mf', - dependencies: { - foo: '1.0.0', - }, - }, - }); - - expect(esmResult).toMatchInlineSnapshot(` - { - "output": { - "externals": [ - /\\^foo\\(\\$\\|\\\\/\\|\\\\\\\\\\)/, - "foo", - ], - }, - } - `); - - expect(cjsResult).toMatchInlineSnapshot(` - { - "output": { - "externals": [ - /\\^foo\\(\\$\\|\\\\/\\|\\\\\\\\\\)/, - "foo", - ], - }, - } - `); - - expect(umdResult).toMatchInlineSnapshot('{}'); - expect(mfResult).toMatchInlineSnapshot('{}'); - }); - - it('autoExternal is true', () => { - const result = composeAutoExternalConfig({ - bundle: true, - format: 'esm', - autoExternal: true, - pkgJson: { - name: 'esm', - dependencies: { - foo: '1.0.0', - foo1: '1.0.0', - }, - devDependencies: { - bar: '1.0.0', - }, - peerDependencies: { - baz: '1.0.0', - }, - }, - }); - - expect(result).toEqual({ - output: { - externals: [ - /^foo($|\/|\\)/, - /^foo1($|\/|\\)/, - /^baz($|\/|\\)/, - 'foo', - 'foo1', - 'baz', - ], - }, - }); - }); - - it('autoExternal is true when format is umd or mf', () => { - const umdResult = composeAutoExternalConfig({ - bundle: true, - format: 'umd', - autoExternal: true, - pkgJson: { - name: 'umd', - dependencies: { - foo: '1.0.0', - }, - }, - }); - - expect(umdResult).toMatchInlineSnapshot(` - { - "output": { - "externals": [ - /\\^foo\\(\\$\\|\\\\/\\|\\\\\\\\\\)/, - "foo", - ], - }, - } - `); - - const mfResult = composeAutoExternalConfig({ - bundle: true, - format: 'mf', - autoExternal: true, - pkgJson: { - name: 'mf', - dependencies: { - foo: '1.0.0', - }, - }, - }); - - expect(mfResult).toMatchInlineSnapshot(` - { - "output": { - "externals": [ - /\\^foo\\(\\$\\|\\\\/\\|\\\\\\\\\\)/, - "foo", - ], - }, - } - `); - }); - - it('autoExternal will deduplication ', () => { - const result = composeAutoExternalConfig({ - bundle: true, - format: 'esm', - autoExternal: true, - pkgJson: { - name: 'esm', - dependencies: { - foo: '1.0.0', - foo1: '1.0.0', - }, - devDependencies: { - bar: '1.0.0', - }, - peerDependencies: { - baz: '1.0.0', - foo: '1.0.0', - foo1: '1.0.0', - }, - }, - }); - - expect(result).toEqual({ - output: { - externals: [ - /^foo($|\/|\\)/, - /^foo1($|\/|\\)/, - /^baz($|\/|\\)/, - 'foo', - 'foo1', - 'baz', - ], - }, - }); - }); - - it('autoExternal is object', () => { - const result = composeAutoExternalConfig({ - bundle: true, - format: 'esm', - autoExternal: { - peerDependencies: false, - devDependencies: true, - }, - pkgJson: { - name: 'esm', - dependencies: { - foo: '1.0.0', - }, - devDependencies: { - bar: '1.0.0', - }, - peerDependencies: { - baz: '1.0.0', - }, - }, - }); - - expect(result).toEqual({ - output: { - externals: [/^foo($|\/|\\)/, /^bar($|\/|\\)/, 'foo', 'bar'], - }, - }); - }); - - it('autoExternal is false', () => { - const result = composeAutoExternalConfig({ - bundle: true, - format: 'esm', - autoExternal: false, - pkgJson: { - name: 'esm', - dependencies: { - foo: '1.0.0', - }, - }, - }); - - expect(result).toEqual({}); - }); - - it('autoExternal with user externals object', () => { - const result = composeAutoExternalConfig({ - bundle: true, - format: 'esm', - autoExternal: true, - pkgJson: { - name: 'esm', - dependencies: { - foo: '1.0.0', - bar: '1.0.0', - }, - }, - userExternals: { - foo: 'foo-1', - }, - }); - - expect(result).toEqual({ - output: { - externals: [/^bar($|\/|\\)/, 'bar'], - }, - }); - }); - - it('read package.json failed', () => { - const result = composeAutoExternalConfig({ - bundle: true, - format: 'esm', - autoExternal: true, - }); - - expect(result).toEqual({}); - }); - - it('bundleless', () => { - const result = composeAutoExternalConfig({ - bundle: false, - format: 'esm', - autoExternal: true, - }); - - expect(result).toStrictEqual({}); - }); -}); diff --git a/tests/integration/auto-external/index.test.ts b/tests/integration/auto-external/index.test.ts index c4c58b74a..8117f4e93 100644 --- a/tests/integration/auto-external/index.test.ts +++ b/tests/integration/auto-external/index.test.ts @@ -1,9 +1,6 @@ import { join } from 'node:path'; -import { stripVTControlCharacters as stripAnsi } from 'node:util'; import { expect, test } from '@rstest/core'; -import { buildAndGetResults, proxyConsole } from 'test-helper'; - -import { composeModuleImportWarn } from '../../../packages/core/src/config'; +import { buildAndGetResults } from 'test-helper'; test('auto external default should works', async () => { const fixturePath = join(__dirname, 'default'); @@ -73,38 +70,3 @@ test('externals should overrides auto external', async () => { 'const external_react1_namespaceObject = require("react1");', ); }); - -test('should get warn when use require in ESM', async () => { - const { logs, restore } = proxyConsole(); - const fixturePath = join(__dirname, 'module-import-warn'); - const { entries } = await buildAndGetResults({ fixturePath }); - const logStrings = logs.map((log) => stripAnsi(log)); - - const shouldWarn = ['react', 'e2', 'e3', 'e5', 'e6', 'e7']; - const shouldNotWarn = ['e1', 'e4', 'e8', 'lodash/add', 'lodash/drop']; - const issuer = join(fixturePath, 'src/index.ts'); - - for (const item of shouldWarn) { - expect(entries.esm).toContain( - `import * as __rspack_external_${item} from "${item}"`, - ); - } - - for (const request of shouldWarn) { - expect( - logStrings.some((l) => - l.includes(stripAnsi(composeModuleImportWarn(request, issuer))), - ), - ).toBe(true); - } - - for (const request of shouldNotWarn) { - expect( - logStrings.some((l) => - l.includes(stripAnsi(composeModuleImportWarn(request, issuer))), - ), - ).toBe(false); - } - - restore(); -}); diff --git a/tests/integration/externals/index.test.ts b/tests/integration/externals/index.test.ts index a277aaf35..e3086893e 100644 --- a/tests/integration/externals/index.test.ts +++ b/tests/integration/externals/index.test.ts @@ -4,8 +4,6 @@ import { stripVTControlCharacters as stripAnsi } from 'node:util'; import { expect, test } from '@rstest/core'; import { buildAndGetResults, proxyConsole, queryContent } from 'test-helper'; -import { composeModuleImportWarn } from '../../../packages/core/src/config'; - test('should fail to build when `output.target` is not "node"', async () => { const fixturePath = join(__dirname, 'browser'); const { restore } = proxyConsole(); @@ -106,40 +104,6 @@ test('should remap node built-ins with user externals', async () => { expect(typeof esmOutput.loadPathSep).toBe('function'); }); -test('should get warn when use require in ESM', async () => { - const { logs, restore } = proxyConsole(); - const fixturePath = join(__dirname, 'module-import-warn'); - const { entries } = await buildAndGetResults({ fixturePath }); - const logStrings = logs.map((log) => stripAnsi(log)); - const issuer = join(fixturePath, 'src/index.ts'); - - for (const external of [ - 'import * as __rspack_external_bar from "bar";', - 'import * as __rspack_external_foo from "foo";', - 'import * as __rspack_external_qux from "qux";', - ]) { - expect(entries.esm).toContain(external); - } - - for (const external of ['foo', 'bar', 'qux']) { - expect( - logStrings.some((l) => - l.includes(stripAnsi(composeModuleImportWarn(external, issuer))), - ), - ).toBe(true); - } - - for (const external of ['./baz', 'quxx']) { - expect( - logStrings.some((l) => - l.includes(stripAnsi(composeModuleImportWarn(external, issuer))), - ), - ).toBe(false); - } - - restore(); -}); - test('require ESM from CJS', async () => { const fixturePath = join(__dirname, 'node'); const { restore } = proxyConsole(); From 3f14a22baa335049541fb289c5cf0c1d6ebe4b52 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Wed, 27 May 2026 10:12:37 +0800 Subject: [PATCH 03/10] refactor: migrate autoExternal to Rsbuild output --- packages/core/src/cli/init.ts | 4 +- packages/core/src/config.ts | 287 +++--------------------------- packages/core/src/types/config.ts | 6 + 3 files changed, 31 insertions(+), 266 deletions(-) diff --git a/packages/core/src/cli/init.ts b/packages/core/src/cli/init.ts index 4f2ad165d..d6b998e95 100644 --- a/packages/core/src/cli/init.ts +++ b/packages/core/src/cli/init.ts @@ -92,9 +92,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; diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 1b41cc936..c29951dee 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -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, @@ -56,7 +55,6 @@ import { isDirectory, isEmptyObject, isIntermediateOutputFormat, - isObject, nodeBuiltInModules, normalizeSlash, omit, @@ -71,241 +69,6 @@ import { } from './utils/syntax'; import { loadTsconfig } from './utils/tsconfig'; -// Match logic is derived from https://github.com/webpack/webpack/blob/94aba382eccf3de1004d235045d4462918dfdbb7/lib/ExternalModuleFactoryPlugin.js#L89-L158 -const handleMatchedExternal = ( - value: string | string[] | boolean | Record, - request: string, -): boolean => { - if (typeof value === 'boolean') { - return value; - } - - if (typeof value === 'string') { - const [first, second] = value.split(' '); - const hasType = !!second; - const _request = second ? second : first; - - // Don't need to warn explicit declared external type. - if (!hasType) { - return request === _request; - } - - return false; - } - - if (Array.isArray(value)) { - return handleMatchedExternal(value[0] ?? '', request); - } - - if (typeof value === 'object') { - return false; - } - - return false; -}; - -const composeExternalsWarnConfig = ( - format: Format, - ...externalsArray: NonNullable['externals'][] -): EnvironmentConfig => { - if (format !== 'esm') { - return {}; - } - - const externals: NonNullable['externals'] = []; - for (const e of externalsArray.filter(Boolean)) { - if (Array.isArray(e)) { - externals.push(...e); - } else { - // @ts-expect-error - externals.push(e); - } - } - - // Match logic is derived from https://github.com/webpack/webpack/blob/94aba382eccf3de1004d235045d4462918dfdbb7/lib/ExternalModuleFactoryPlugin.js#L166-L293. - const matchUserExternals = ( - externals: NonNullable['externals'], - request: string, - callback: (matched: boolean, shouldWarn?: boolean) => void, - ) => { - // string - if (typeof externals === 'string') { - if (handleMatchedExternal(externals, request)) { - callback(true, true); - return; - } - } - // array - if (Array.isArray(externals)) { - let i = 0; - const next = () => { - let asyncFlag: boolean; - const handleExternalsAndCallback = ( - matched: boolean, - shouldWarn?: boolean, - ) => { - if (!matched) { - if (asyncFlag) { - asyncFlag = false; - return; - } - next(); - return; - } - - callback(matched, shouldWarn); - }; - - do { - asyncFlag = true; - if (i >= externals.length) { - callback(false); - return; - } - matchUserExternals( - externals[i++], - request, - handleExternalsAndCallback, - ); - } while (!asyncFlag); - asyncFlag = false; - }; - - next(); - return; - } - // regexp - if (externals instanceof RegExp) { - if (externals.test(request)) { - callback(true, true); - return; - } - } - // function - else if (typeof externals === 'function') { - // TODO: Support function - } - // object - else if (typeof externals === 'object') { - if (Object.hasOwn(externals, request)) { - if (handleMatchedExternal(externals[request]!, request)) { - callback(true, true); - } else { - callback(true); - } - return; - } - } - - callback(false); - }; - - return { - output: { - externals: [ - ({ request, dependencyType, contextInfo }: any, callback: any) => { - let shouldWarn = false; - const _callback = (_matched: boolean, _shouldWarn?: boolean) => { - if (_shouldWarn) { - shouldWarn = true; - } - }; - - if (contextInfo.issuer && dependencyType === 'commonjs') { - matchUserExternals(externals, request, _callback); - if (shouldWarn) { - logger.warn(composeModuleImportWarn(request, contextInfo.issuer)); - } - } - callback(); - }, - ], - }, - }; -}; - -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['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((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; @@ -561,6 +324,7 @@ const composeFormatConfig = ({ return { output: { filenameHash: false, + ...(bundle && { autoExternal: true }), }, tools: { rspack: { @@ -605,6 +369,7 @@ const composeFormatConfig = ({ output: { module: false, filenameHash: false, + ...(bundle && { autoExternal: true }), }, tools: { rspack: { @@ -940,13 +705,6 @@ const composeShimsConfig = ( return { rsbuildConfig, enabledShims }; }; -export const composeModuleImportWarn = ( - request: string, - issuer: string, -): string => { - return `The externalized commonjs request ${color.green(`"${request}"`)} from ${color.green(issuer)} will use ${color.blue('"module"')} external type in ESM format. If you want to specify other external type, consider setting the request and type with ${color.blue('"output.externals"')}.`; -}; - const composeExternalsConfig = ( format: Format, externals: NonNullable['externals'], @@ -1609,7 +1367,7 @@ const composeDtsConfig = async ( format: Format, dtsExtension: string, ): Promise => { - const { autoExternal, banner, footer, redirect } = libConfig; + const { banner, footer, redirect } = libConfig; let { dts } = libConfig; @@ -1632,7 +1390,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), alias: dts?.alias, isolated: dts?.isolated, banner: banner?.dts, @@ -1765,13 +1526,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, @@ -1793,6 +1554,10 @@ async function composeLibRsbuildConfig( sourceEntry: config.source?.entry, }); const userExternals = hasExe ? undefined : config.output?.externals; + // Exe bundles everything into a single binary, so disable automatic externals here. + const exeExternalOverride: EnvironmentConfig = hasExe + ? { output: { autoExternal: false } } + : {}; const externalHelpersConfig = composeExternalHelpersConfig( hasExe ? false : externalHelpers, pkgJson, @@ -1834,13 +1599,6 @@ async function composeLibRsbuildConfig( outBase, ); const syntaxConfig = composeSyntaxConfig(target, config?.syntax); - const autoExternalConfig = composeAutoExternalConfig({ - bundle, - format, - autoExternal: hasExe ? false : autoExternal, - pkgJson, - userExternals, - }); const cssConfig = composeCssConfig( outBase, cssModulesAuto, @@ -1855,11 +1613,6 @@ async function composeLibRsbuildConfig( contextToWatch: outBase, }); const dtsConfig = await composeDtsConfig(config, format, dtsExtension); - const externalsWarnConfig = composeExternalsWarnConfig( - format, - userExternalsConfig?.output?.externals, - autoExternalConfig?.output?.externals, - ); const minifyConfig = composeMinifyConfig(config); const bannerFooterConfig = composeBannerFooterConfig(banner, footer); const decoratorsConfig = composeDecoratorsConfig( @@ -1871,6 +1624,7 @@ async function composeLibRsbuildConfig( return mergeRsbuildConfig( bundleConfig, formatConfig, + exeExternalOverride, // outputConfig, shimsConfig, syntaxConfig, @@ -1879,14 +1633,11 @@ async function composeLibRsbuildConfig( targetConfig, // #region Externals configs // The order of the externals config should come in the following order: - // 1. `externalsWarnConfig` should come before other externals config to touch the externalized modules first. - // 2. `userExternalsConfig` should present at first to takes effect earlier than others. - // 3. The externals config in `bundlelessExternalConfig` should present after other externals config as + // 1. `userExternalsConfig` should present at first to takes effect earlier than others. + // 2. The externals config in `bundlelessExternalConfig` should present after other externals config as // it relies on other externals config to bail out the externalized modules first then resolve // the correct path for relative imports. - externalsWarnConfig, userExternalsConfig, - autoExternalConfig, targetExternalsConfig, bundlelessExternalConfig, // #endregion @@ -1955,6 +1706,14 @@ export async function composeCreateRsbuildConfig( userConfig.output ??= {}; delete userConfig.output.externals; + // Deprecated lib.autoExternal → output.autoExternal shim + if (userConfig.autoExternal !== undefined) { + logger.warn( + `${color.yellow('lib.autoExternal')} is deprecated, use ${color.yellow('output.autoExternal')} instead.`, + ); + userConfig.output.autoExternal ??= userConfig.autoExternal; + } + const config: RsbuildConfigWithLibInfo = { format: libConfig.format ?? 'esm', // The merge order represents the priority of the configuration diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts index 5ec2a373e..98e7b2577 100644 --- a/packages/core/src/types/config.ts +++ b/packages/core/src/types/config.ts @@ -400,6 +400,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} */ @@ -506,6 +507,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 { From f1729be2f93ca6632c26f83d4748298fed8c5a9e Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Wed, 27 May 2026 10:39:23 +0800 Subject: [PATCH 04/10] test: cover autoExternal merge precedence --- packages/core/tests/config.test.ts | 37 +++++++++++++++++++++++++++++- packages/core/tests/exe.test.ts | 30 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/packages/core/tests/config.test.ts b/packages/core/tests/config.test.ts index 9bc19ebbc..130ae6c09 100644 --- a/packages/core/tests/config.test.ts +++ b/packages/core/tests/config.test.ts @@ -16,7 +16,24 @@ import { } from '../src/mergeConfig'; import type { RslibConfig } from '../src/types/config'; -rs.mock('rslog'); +rs.mock('rslog', () => ({ + color: { + blue: (text: string) => text, + cyan: (text: string) => text, + dim: (text: string) => text, + gray: (text: string) => text, + green: (text: string) => text, + magenta: (text: string) => text, + yellow: (text: string) => text, + }, + logger: { + level: 'info', + debug: rs.fn(), + error: rs.fn(), + override: rs.fn(), + warn: rs.fn(), + }, +})); describe('Should load config file correctly', () => { test('Load config.js in cjs project', async () => { @@ -564,6 +581,24 @@ describe('Should compose create Rsbuild config correctly', () => { } `); }); + + test('per-lib deprecated autoExternal should override shared output.autoExternal', async () => { + const rslibConfig: RslibConfig = { + output: { + autoExternal: false, + }, + lib: [ + { + format: 'esm', + autoExternal: true, + }, + ], + }; + + const [config] = await composeCreateRsbuildConfig(rslibConfig); + + expect(config?.config.output?.autoExternal).toBe(true); + }); }); describe('runtimeChunk', () => { diff --git a/packages/core/tests/exe.test.ts b/packages/core/tests/exe.test.ts index ce02272b8..7931266cc 100644 --- a/packages/core/tests/exe.test.ts +++ b/packages/core/tests/exe.test.ts @@ -175,6 +175,36 @@ describe('experiments.exe', () => { ).resolves.toBeTruthy(); }); + test('should force disable output.autoExternal for executables', async () => { + const [config] = await withSupportedNodeRuntime(() => + composeTestRslibConfig({ + format: 'esm', + output: { + autoExternal: true, + }, + experiments: { + exe: true, + }, + }), + ); + + expect(config?.config.output?.autoExternal).toBe(false); + }); + + test('should force disable deprecated lib.autoExternal for executables', async () => { + const [config] = await withSupportedNodeRuntime(() => + composeTestRslibConfig({ + format: 'esm', + autoExternal: true, + experiments: { + exe: true, + }, + }), + ); + + expect(config?.config.output?.autoExternal).toBe(false); + }); + test('should resolve outputPath separately from fileName', () => { const resolved = resolveExecutableOutputPath({ environment: { From 99d1fa613d81f400c1e2a7d6c0ae3c83ee64e04d Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Wed, 27 May 2026 10:42:54 +0800 Subject: [PATCH 05/10] fix: force disable autoExternal for exe builds --- packages/core/src/config.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index c29951dee..a9d2efd69 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1554,10 +1554,6 @@ async function composeLibRsbuildConfig( sourceEntry: config.source?.entry, }); const userExternals = hasExe ? undefined : config.output?.externals; - // Exe bundles everything into a single binary, so disable automatic externals here. - const exeExternalOverride: EnvironmentConfig = hasExe - ? { output: { autoExternal: false } } - : {}; const externalHelpersConfig = composeExternalHelpersConfig( hasExe ? false : externalHelpers, pkgJson, @@ -1624,7 +1620,6 @@ async function composeLibRsbuildConfig( return mergeRsbuildConfig( bundleConfig, formatConfig, - exeExternalOverride, // outputConfig, shimsConfig, syntaxConfig, @@ -1714,6 +1709,12 @@ export async function composeCreateRsbuildConfig( userConfig.output.autoExternal ??= userConfig.autoExternal; } + // Exe bundles everything into a single binary, so disable automatic externals + // after user config is merged to prevent users from re-enabling it. + const exeExternalOverride: EnvironmentConfig = userConfig.experiments?.exe + ? { output: { autoExternal: false } } + : {}; + const config: RsbuildConfigWithLibInfo = { format: libConfig.format ?? 'esm', // The merge order represents the priority of the configuration @@ -1744,6 +1745,7 @@ export async function composeCreateRsbuildConfig( outBase: true, experiments: true, }), + exeExternalOverride, ), }; From f28abb2b4c07808444206e4f98ddd8b7e6f94129 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Wed, 27 May 2026 10:46:15 +0800 Subject: [PATCH 06/10] fix: preserve deprecated autoExternal precedence --- packages/core/src/config.ts | 24 +++++++++++++++++++++--- packages/core/tests/config.test.ts | 18 ++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index a9d2efd69..0c962015e 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1649,6 +1649,25 @@ async function composeLibRsbuildConfig( ); } +const normalizeDeprecatedAutoExternal = >( + config: T, +): T => { + if ( + config.autoExternal === undefined || + config.output?.autoExternal !== undefined + ) { + return config; + } + + return { + ...config, + output: { + ...config.output, + autoExternal: config.autoExternal, + }, + }; +}; + export async function composeCreateRsbuildConfig( rslibConfig: RslibConfig, ): Promise { @@ -1678,8 +1697,8 @@ export async function composeCreateRsbuildConfig( const libConfigPromises = libConfigsArray.map(async (libConfig, index) => { const userConfig = mergeRsbuildConfig( - sharedRsbuildConfig, - libConfig, + normalizeDeprecatedAutoExternal(sharedRsbuildConfig), + normalizeDeprecatedAutoExternal(libConfig), ); // Merge the configuration of each environment based on the shared Rsbuild @@ -1706,7 +1725,6 @@ export async function composeCreateRsbuildConfig( logger.warn( `${color.yellow('lib.autoExternal')} is deprecated, use ${color.yellow('output.autoExternal')} instead.`, ); - userConfig.output.autoExternal ??= userConfig.autoExternal; } // Exe bundles everything into a single binary, so disable automatic externals diff --git a/packages/core/tests/config.test.ts b/packages/core/tests/config.test.ts index 130ae6c09..50f4b4aaf 100644 --- a/packages/core/tests/config.test.ts +++ b/packages/core/tests/config.test.ts @@ -599,6 +599,24 @@ describe('Should compose create Rsbuild config correctly', () => { expect(config?.config.output?.autoExternal).toBe(true); }); + + test('output.autoExternal should override deprecated autoExternal in the same config', async () => { + const rslibConfig: RslibConfig = { + lib: [ + { + format: 'esm', + autoExternal: true, + output: { + autoExternal: false, + }, + }, + ], + }; + + const [config] = await composeCreateRsbuildConfig(rslibConfig); + + expect(config?.config.output?.autoExternal).toBe(false); + }); }); describe('runtimeChunk', () => { From 72c73b67241e9f42b03b2b111590797e8db3339b Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Wed, 27 May 2026 15:47:11 +0800 Subject: [PATCH 07/10] docs: update autoExternal references to output.autoExternal --- website/docs/en/config/lib/dts.mdx | 2 +- website/docs/en/config/lib/experiments.mdx | 2 +- website/docs/en/guide/faq/features.mdx | 2 +- website/docs/en/guide/migration/tsup.mdx | 2 +- website/docs/zh/config/lib/dts.mdx | 2 +- website/docs/zh/config/lib/experiments.mdx | 2 +- website/docs/zh/guide/faq/features.mdx | 2 +- website/docs/zh/guide/migration/tsup.mdx | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/website/docs/en/config/lib/dts.mdx b/website/docs/en/config/lib/dts.mdx index d1dd6d874..ceb1415c8 100644 --- a/website/docs/en/config/lib/dts.mdx +++ b/website/docs/en/config/lib/dts.mdx @@ -104,7 +104,7 @@ Specifies the dependencies whose declaration files should be bundled. This confi By default, Rslib determines externalized dependencies based on the following configurations. For details, refer to [Handle third-party dependencies](/guide/advanced/third-party-deps). -- [autoExternal](/config/lib/auto-external) configuration +- [output.autoExternal](/config/rsbuild/output#outputautoexternal) configuration - [output.externals](/config/rsbuild/output#outputexternals) configuration Direct dependencies (declared in `package.json`) that are not externalized will be automatically added to `bundledPackages`, and their declaration files will be bundled into the final output. diff --git a/website/docs/en/config/lib/experiments.mdx b/website/docs/en/config/lib/experiments.mdx index 457c8a289..806a36011 100644 --- a/website/docs/en/config/lib/experiments.mdx +++ b/website/docs/en/config/lib/experiments.mdx @@ -107,7 +107,7 @@ To enable `experiments.exe`, make sure that: After enabling `experiments.exe`, Rslib will also: - Disable code splitting -- Ignore [`autoExternal`](/config/lib/auto-external), [`output.externals`](/config/rsbuild/output#outputexternals), [`externalHelpers`](/config/lib/external-helpers), and related options, and bundle all modules directly into the executable +- Ignore [`output.autoExternal`](/config/rsbuild/output#outputautoexternal), [`output.externals`](/config/rsbuild/output#outputexternals), [`externalHelpers`](/config/lib/external-helpers), and related options, and bundle all modules directly into the executable ### Boolean type diff --git a/website/docs/en/guide/faq/features.mdx b/website/docs/en/guide/faq/features.mdx index 87365a4b0..cd66fc0e3 100644 --- a/website/docs/en/guide/faq/features.mdx +++ b/website/docs/en/guide/faq/features.mdx @@ -205,7 +205,7 @@ export default { Rslib uses [rsbuild-plugin-dts](https://github.com/web-infra-dev/rslib/blob/main/packages/plugin-dts/README.md) to generate declaration files, which supports configuration via [output.externals](/config/rsbuild/output#outputexternals) for excluding certain dependencies from bundled declaration files. -For example, if `@types/foo` is only declared in `devDependencies`, according to the dependency handling logic of [autoExternal](/config/lib/auto-external), Rslib will try to bundle `@types/foo` into the declaration output files during the build. In this case, you can exclude `@types/foo` by configuring [output.externals](/config/rsbuild/output#outputexternals). +For example, if `@types/foo` is only declared in `devDependencies`, according to the dependency handling logic of [output.autoExternal](/config/rsbuild/output#outputautoexternal), Rslib will try to bundle `@types/foo` into the declaration output files during the build. In this case, you can exclude `@types/foo` by configuring [output.externals](/config/rsbuild/output#outputexternals). ```ts title="rslib.config.ts" export default { diff --git a/website/docs/en/guide/migration/tsup.mdx b/website/docs/en/guide/migration/tsup.mdx index c495f02cf..0ceff7c8a 100644 --- a/website/docs/en/guide/migration/tsup.mdx +++ b/website/docs/en/guide/migration/tsup.mdx @@ -68,7 +68,7 @@ Here is the corresponding Rslib configuration for tsup configuration: | define | [source.define](/config/rsbuild/source#sourcedefine) | | dts | [lib.dts](/config/lib/dts) | | entry | [source.entry](/config/rsbuild/source#sourceentry) | -| external | [output.externals](/config/rsbuild/output#outputexternals) / [lib.autoExternal](/config/lib/auto-external) | +| external | [output.externals](/config/rsbuild/output#outputexternals) / [output.autoExternal](/config/rsbuild/output#outputautoexternal) | | format | [lib.format](/config/lib/format) | | footer | [lib.footer](/config/lib/footer) | | minify | [output.minify](/config/rsbuild/output#outputminify) | diff --git a/website/docs/zh/config/lib/dts.mdx b/website/docs/zh/config/lib/dts.mdx index 0642d4e26..ba25d65ae 100644 --- a/website/docs/zh/config/lib/dts.mdx +++ b/website/docs/zh/config/lib/dts.mdx @@ -104,7 +104,7 @@ Rslib 基于 API Extractor 对类型声明文件进行打包,可能会存在 默认情况下,Rslib 会根据以下配置确定需要外部化的依赖项,详见 [处理第三方依赖](/guide/advanced/third-party-deps)。 -- [autoExternal](/config/lib/auto-external) 配置 +- [output.autoExternal](/config/rsbuild/output#outputautoexternal) 配置 - [output.externals](/config/rsbuild/output#outputexternals) 配置 那些没有被外部化的直接依赖项(在 `package.json` 中声明)会被添加到 `bundledPackages` 中,这些包的类型声明文件将会被打包到最终的产物中。 diff --git a/website/docs/zh/config/lib/experiments.mdx b/website/docs/zh/config/lib/experiments.mdx index 4c72d4403..593b67273 100644 --- a/website/docs/zh/config/lib/experiments.mdx +++ b/website/docs/zh/config/lib/experiments.mdx @@ -107,7 +107,7 @@ type ExeOptions = 开启 `experiments.exe` 后,Rslib 还会: - 禁用代码拆分 -- 忽略 [`autoExternal`](/config/lib/auto-external)、[`output.externals`](/config/rsbuild/output#outputexternals)、[`externalHelpers`](/config/lib/external-helpers) 等配置,将所有模块直接打包进可执行文件 +- 忽略 [`output.autoExternal`](/config/rsbuild/output#outputautoexternal)、[`output.externals`](/config/rsbuild/output#outputexternals)、[`externalHelpers`](/config/lib/external-helpers) 等配置,将所有模块直接打包进可执行文件 ### 布尔类型 diff --git a/website/docs/zh/guide/faq/features.mdx b/website/docs/zh/guide/faq/features.mdx index ca2cd713f..fd55db537 100644 --- a/website/docs/zh/guide/faq/features.mdx +++ b/website/docs/zh/guide/faq/features.mdx @@ -205,7 +205,7 @@ export default { Rslib 通过 [rsbuild-plugin-dts](https://github.com/web-infra-dev/rslib/blob/main/packages/plugin-dts/README.md) 生成类型声明文件,该插件支持通过 [output.externals](/config/rsbuild/output#outputexternals) 配置,从打包后的类型声明文件中排除指定的依赖。 -举个例子,若 `@types/foo` 仅声明在 `devDependencies` 中,按照 [autoExternal](/config/lib/auto-external) 处理依赖的逻辑,在打包时 Rslib 会尝试将 `@types/foo` 一同打包进类型声明文件产物中,此时可以通过配置 [output.externals](/config/rsbuild/output#outputexternals) 来排除 `@types/foo`。 +举个例子,若 `@types/foo` 仅声明在 `devDependencies` 中,按照 [output.autoExternal](/config/rsbuild/output#outputautoexternal) 处理依赖的逻辑,在打包时 Rslib 会尝试将 `@types/foo` 一同打包进类型声明文件产物中,此时可以通过配置 [output.externals](/config/rsbuild/output#outputexternals) 来排除 `@types/foo`。 ```ts title="rslib.config.ts" export default { diff --git a/website/docs/zh/guide/migration/tsup.mdx b/website/docs/zh/guide/migration/tsup.mdx index f483cc043..640e1ae05 100644 --- a/website/docs/zh/guide/migration/tsup.mdx +++ b/website/docs/zh/guide/migration/tsup.mdx @@ -68,7 +68,7 @@ export default defineConfig({}); | define | [source.define](/config/rsbuild/source#sourcedefine) | | dts | [lib.dts](/config/lib/dts) | | entry | [source.entry](/config/rsbuild/source#sourceentry) | -| external | [output.externals](/config/rsbuild/output#outputexternals) / [lib.autoExternal](/config/lib/auto-external) | +| external | [output.externals](/config/rsbuild/output#outputexternals) / [output.autoExternal](/config/rsbuild/output#outputautoexternal) | | format | [lib.format](/config/lib/format) | | footer | [lib.footer](/config/lib/footer) | | minify | [output.minify](/config/rsbuild/output#outputminify) | From ebc1ad95fb3d7d79715b22f16f19f61289f5cc98 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Wed, 27 May 2026 17:17:12 +0800 Subject: [PATCH 08/10] docs: update third-party dependency handling to reflect output.autoExternal and output.externals --- .../en/guide/advanced/third-party-deps.mdx | 31 ++++++++++++------- .../zh/guide/advanced/third-party-deps.mdx | 30 ++++++++++-------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/website/docs/en/guide/advanced/third-party-deps.mdx b/website/docs/en/guide/advanced/third-party-deps.mdx index 575dc72ff..31eadc07e 100644 --- a/website/docs/en/guide/advanced/third-party-deps.mdx +++ b/website/docs/en/guide/advanced/third-party-deps.mdx @@ -59,30 +59,39 @@ console.info(foo); If you want to modify the default processing, you can use the following API: -- [lib.autoExternal](/config/lib/auto-external) +- [output.autoExternal](/config/rsbuild/output#outputautoexternal) - [output.externals](/config/rsbuild/output#outputexternals) -## Exclude specified third-party dependencies +## Customize third-party dependency handling -The configuration described above allows you to implement more fine-grained handling of third-party dependencies. +You can use the following two options to adjust the default behavior: -For example, when we need to leave only certain dependencies unbundled, we can configure it as follows. +- [output.autoExternal](/config/rsbuild/output#outputautoexternal): automatically externalizes dependencies based on `package.json`. +- [output.externals](/config/rsbuild/output#outputexternals): manually marks matched import requests as external. -:::tip -In this case, some dependencies may not be suitable for bundling. If so, you can handle it as follows. -::: +### Bundle dependencies from package.json + +For ESM and CJS bundle outputs, Rslib enables `output.autoExternal` by default. This means dependencies in `dependencies`, `optionalDependencies`, and `peerDependencies` are externalized automatically. + +If you want these dependencies to be bundled into the output, you can configure [output.autoExternal](/config/rsbuild/output#outputautoexternal) to disable automatic externalization, or enable it only for specific dependency types. + +### Externalize specified imports + +Use [output.externals](/config/rsbuild/output#outputexternals) when you need to manually externalize an import, or change the request path after externalization: ```ts export default defineConfig({ lib: [ { - // ... - autoExternal: true, output: { - externals: ['pkg-1', /pkg-2/], + externals: { + react: 'react', + 'react/jsx-runtime': 'react/jsx-runtime', + }, }, - // ... }, ], }); ``` + +If you need precise control over which imports are externalized, we recommend disabling `output.autoExternal` and explicitly listing them with `output.externals`. If a subpath import such as `react/jsx-runtime` also needs to be externalized or remapped, add the subpath to `output.externals` as well. diff --git a/website/docs/zh/guide/advanced/third-party-deps.mdx b/website/docs/zh/guide/advanced/third-party-deps.mdx index 81ebe6dc0..9e09af806 100644 --- a/website/docs/zh/guide/advanced/third-party-deps.mdx +++ b/website/docs/zh/guide/advanced/third-party-deps.mdx @@ -57,32 +57,36 @@ import foo from 'foo'; console.info(foo); ``` -如果想要修改默认的处理方式,可以通过下面的 API 进行修改: +## 自定义三方依赖处理 -- [lib.autoExternal](/config/lib/auto-external) -- [output.externals](/config/rsbuild/output#outputexternals) +可以使用下面两个配置调整默认行为: -## 排除指定三方依赖 +- [output.autoExternal](/config/rsbuild/output#outputautoexternal):根据 `package.json` 自动外部化依赖。 +- [output.externals](/config/rsbuild/output#outputexternals):手动将匹配到的 import 请求标记为 external。 -上面介绍的配置可以让你实现对三方依赖更细微的处理。 +### 打包 package.json 中的依赖 -例如当我们需要仅对某些依赖不进行打包处理的时候,可以按照如下方式进行配置: +对于 ESM 和 CJS 的 bundle 产物,Rslib 默认启用 `output.autoExternal`。这意味着 `dependencies`、`optionalDependencies` 和 `peerDependencies` 中的依赖会被自动外部化。 -:::tip -在这种情况下,某些依赖可能不适合打包。如果遇到这种情况,则可以按照下面的方式进行处理。 -::: +如果希望这些依赖被打包进产物,通过配置 [output.autoExternal](/config/rsbuild/output#outputautoexternal) 可以禁用自动外部化,或者只对指定依赖类型启用。 + +### 外部化指定 import + +当你需要手动外部化某个 import,或需要改变 external 后的请求路径时,可以使用 [output.externals](/config/rsbuild/output#outputexternals): ```ts export default defineConfig({ lib: [ { - // ... - autoExternal: true, output: { - externals: ['pkg-1', /pkg-2/], + externals: { + react: 'react', + 'react/jsx-runtime': 'react/jsx-runtime', + }, }, - // ... }, ], }); ``` + +如果你需要精确控制哪些 import 被 external,建议禁用 `output.autoExternal`,并使用 `output.externals` 显式列出它们。如果 `react/jsx-runtime` 这类子路径 import 也需要 external 或改名,也需要把子路径一起添加到 `output.externals` 中。 From 4079f62103c9965959cfea39128b3e018f87d3f7 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Thu, 28 May 2026 12:05:23 +0800 Subject: [PATCH 09/10] fix: move autoExternal normalization earlier Co-Authored-By: Aiden --- packages/core/src/config.ts | 62 ++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 0c962015e..5c6b41e66 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1649,25 +1649,6 @@ async function composeLibRsbuildConfig( ); } -const normalizeDeprecatedAutoExternal = >( - config: T, -): T => { - if ( - config.autoExternal === undefined || - config.output?.autoExternal !== undefined - ) { - return config; - } - - return { - ...config, - output: { - ...config.output, - autoExternal: config.autoExternal, - }, - }; -}; - export async function composeCreateRsbuildConfig( rslibConfig: RslibConfig, ): Promise { @@ -1696,11 +1677,36 @@ 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 normalizedLibConfig = + libConfig.autoExternal !== undefined && + libConfig.output?.autoExternal === undefined + ? { + ...libConfig, + output: { + ...libConfig.output, + autoExternal: libConfig.autoExternal, + }, + } + : libConfig; + const userConfig = mergeRsbuildConfig( - normalizeDeprecatedAutoExternal(sharedRsbuildConfig), - normalizeDeprecatedAutoExternal(libConfig), + sharedRsbuildConfig, + normalizedLibConfig, ); + // 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( @@ -1720,19 +1726,6 @@ export async function composeCreateRsbuildConfig( userConfig.output ??= {}; delete userConfig.output.externals; - // Deprecated lib.autoExternal → output.autoExternal shim - if (userConfig.autoExternal !== undefined) { - logger.warn( - `${color.yellow('lib.autoExternal')} is deprecated, use ${color.yellow('output.autoExternal')} instead.`, - ); - } - - // Exe bundles everything into a single binary, so disable automatic externals - // after user config is merged to prevent users from re-enabling it. - const exeExternalOverride: EnvironmentConfig = userConfig.experiments?.exe - ? { output: { autoExternal: false } } - : {}; - const config: RsbuildConfigWithLibInfo = { format: libConfig.format ?? 'esm', // The merge order represents the priority of the configuration @@ -1763,7 +1756,6 @@ export async function composeCreateRsbuildConfig( outBase: true, experiments: true, }), - exeExternalOverride, ), }; From 37cf0234004a8231e45a4d1225a1381ca7aa9d12 Mon Sep 17 00:00:00 2001 From: Elecmonkey Date: Thu, 28 May 2026 16:59:58 +0800 Subject: [PATCH 10/10] docs: clarify third-party dependency handling --- .../en/guide/advanced/third-party-deps.mdx | 123 ++++++++++++++++-- .../zh/guide/advanced/third-party-deps.mdx | 123 ++++++++++++++++-- 2 files changed, 226 insertions(+), 20 deletions(-) diff --git a/website/docs/en/guide/advanced/third-party-deps.mdx b/website/docs/en/guide/advanced/third-party-deps.mdx index 31eadc07e..da1923749 100644 --- a/website/docs/en/guide/advanced/third-party-deps.mdx +++ b/website/docs/en/guide/advanced/third-party-deps.mdx @@ -64,20 +64,80 @@ If you want to modify the default processing, you can use the following API: ## Customize third-party dependency handling -You can use the following two options to adjust the default behavior: +Rslib mainly uses [output.autoExternal](/config/rsbuild/output#outputautoexternal) and [output.externals](/config/rsbuild/output#outputexternals) to control whether third-party dependencies are bundled. -- [output.autoExternal](/config/rsbuild/output#outputautoexternal): automatically externalizes dependencies based on `package.json`. -- [output.externals](/config/rsbuild/output#outputexternals): manually marks matched import requests as external. +### Override the default auto external behavior -### Bundle dependencies from package.json +For ESM and CJS bundle outputs, Rslib enables `output.autoExternal` by default. It automatically externalizes dependencies declared in `dependencies`, `optionalDependencies`, and `peerDependencies`. -For ESM and CJS bundle outputs, Rslib enables `output.autoExternal` by default. This means dependencies in `dependencies`, `optionalDependencies`, and `peerDependencies` are externalized automatically. +If you want these dependencies to be bundled into the output, disable auto externalization: -If you want these dependencies to be bundled into the output, you can configure [output.autoExternal](/config/rsbuild/output#outputautoexternal) to disable automatic externalization, or enable it only for specific dependency types. +```ts +export default defineConfig({ + lib: [ + { + output: { + autoExternal: false, + }, + }, + ], +}); +``` + +If you only want to adjust certain dependency types, use the object form: + +```ts +export default defineConfig({ + lib: [ + { + output: { + autoExternal: { + dependencies: true, + optionalDependencies: true, + peerDependencies: true, + devDependencies: false, + }, + }, + }, + ], +}); +``` + +If you only want a few dependencies to skip auto externalization, use `exclude`. Excluded dependencies and their subpath imports will be bundled into the output: + +```ts +export default defineConfig({ + lib: [ + { + output: { + autoExternal: { + exclude: ['react', /^@scope\//], + }, + }, + }, + ], +}); +``` ### Externalize specified imports -Use [output.externals](/config/rsbuild/output#outputexternals) when you need to manually externalize an import, or change the request path after externalization: +Use [output.externals](/config/rsbuild/output#outputexternals) when you need to specify imports that should not be bundled by Rslib, or when you need to change the request path after externalization. + +The array form is useful when you want to keep the original request paths: + +```ts +export default defineConfig({ + lib: [ + { + output: { + externals: ['react', 'react/jsx-runtime'], + }, + }, + ], +}); +``` + +The object form can specify the request path after externalization, and is commonly used to rename externals: ```ts export default defineConfig({ @@ -85,8 +145,51 @@ export default defineConfig({ { output: { externals: { - react: 'react', - 'react/jsx-runtime': 'react/jsx-runtime', + react: 'react-18', + 'react/jsx-runtime': 'react-18/jsx-runtime', + }, + }, + }, + ], +}); +``` + +Subpath imports such as `react/jsx-runtime` need to be handled separately. Configuring only `react` does not mean `react/jsx-runtime` will use the same external rule. + +If you want to match a group of imports, use a regular expression: + +```ts +export default defineConfig({ + lib: [ + { + output: { + externals: [/^react($|\/)/], + }, + }, + ], +}); +``` + +### Configure more complex external rules + +If you need to decide whether to externalize a module based on the request issuer, context, or other information, configure Rspack's `externals` via [tools.rspack](/config/rsbuild/tools#toolsrspack): + +```ts +export default defineConfig({ + lib: [ + { + tools: { + rspack: { + externals: [ + ({ request }, callback) => { + if (request?.startsWith('react')) { + callback(null, request); + return; + } + + callback(); + }, + ], }, }, }, @@ -94,4 +197,4 @@ export default defineConfig({ }); ``` -If you need precise control over which imports are externalized, we recommend disabling `output.autoExternal` and explicitly listing them with `output.externals`. If a subpath import such as `react/jsx-runtime` also needs to be externalized or remapped, add the subpath to `output.externals` as well. +For most cases, prefer `output.autoExternal` and `output.externals`. Use `tools.rspack.externals` only when you need more advanced Rspack external capabilities. For more details, see the Rspack [Externals](https://rspack.rs/config/externals) documentation. diff --git a/website/docs/zh/guide/advanced/third-party-deps.mdx b/website/docs/zh/guide/advanced/third-party-deps.mdx index 9e09af806..16ce7139f 100644 --- a/website/docs/zh/guide/advanced/third-party-deps.mdx +++ b/website/docs/zh/guide/advanced/third-party-deps.mdx @@ -59,20 +59,80 @@ console.info(foo); ## 自定义三方依赖处理 -可以使用下面两个配置调整默认行为: +Rslib 主要通过 [output.autoExternal](/config/rsbuild/output#outputautoexternal) 和 [output.externals](/config/rsbuild/output#outputexternals) 控制三方依赖是否被打包。 -- [output.autoExternal](/config/rsbuild/output#outputautoexternal):根据 `package.json` 自动外部化依赖。 -- [output.externals](/config/rsbuild/output#outputexternals):手动将匹配到的 import 请求标记为 external。 +### 覆盖默认自动 external 行为 -### 打包 package.json 中的依赖 +对于 ESM 和 CJS 的 bundle 产物,Rslib 默认启用 `output.autoExternal`,会自动 external `dependencies`、`optionalDependencies` 和 `peerDependencies` 中声明的依赖。 -对于 ESM 和 CJS 的 bundle 产物,Rslib 默认启用 `output.autoExternal`。这意味着 `dependencies`、`optionalDependencies` 和 `peerDependencies` 中的依赖会被自动外部化。 +如果希望这些依赖被打包进产物,可以禁用自动 external: -如果希望这些依赖被打包进产物,通过配置 [output.autoExternal](/config/rsbuild/output#outputautoexternal) 可以禁用自动外部化,或者只对指定依赖类型启用。 +```ts +export default defineConfig({ + lib: [ + { + output: { + autoExternal: false, + }, + }, + ], +}); +``` + +如果只想调整某几类依赖,可以使用对象形式: + +```ts +export default defineConfig({ + lib: [ + { + output: { + autoExternal: { + dependencies: true, + optionalDependencies: true, + peerDependencies: true, + devDependencies: false, + }, + }, + }, + ], +}); +``` + +如果只想让少数依赖不被自动 external,可以使用 `exclude`。被排除的依赖及其子路径会被打包进产物: + +```ts +export default defineConfig({ + lib: [ + { + output: { + autoExternal: { + exclude: ['react', /^@scope\//], + }, + }, + }, + ], +}); +``` ### 外部化指定 import -当你需要手动外部化某个 import,或需要改变 external 后的请求路径时,可以使用 [output.externals](/config/rsbuild/output#outputexternals): +当你需要指定某些 import 不被 Rslib 打包,或需要改变 external 后的请求路径时,可以使用 [output.externals](/config/rsbuild/output#outputexternals)。 + +数组形式适合保留原始请求路径: + +```ts +export default defineConfig({ + lib: [ + { + output: { + externals: ['react', 'react/jsx-runtime'], + }, + }, + ], +}); +``` + +对象形式可以指定 external 后的请求路径,常用于给 external 改名: ```ts export default defineConfig({ @@ -80,8 +140,51 @@ export default defineConfig({ { output: { externals: { - react: 'react', - 'react/jsx-runtime': 'react/jsx-runtime', + react: 'react-18', + 'react/jsx-runtime': 'react-18/jsx-runtime', + }, + }, + }, + ], +}); +``` + +`react/jsx-runtime` 这类子路径 import 需要单独处理。只配置 `react` 不代表 `react/jsx-runtime` 也会使用相同的 external 规则。 + +如果要匹配一组 import,可以使用正则: + +```ts +export default defineConfig({ + lib: [ + { + output: { + externals: [/^react($|\/)/], + }, + }, + ], +}); +``` + +### 配置更复杂的 external 规则 + +如果需要根据请求来源、上下文等信息决定是否 external,可以通过 [tools.rspack](/config/rsbuild/tools#toolsrspack) 配置 Rspack 的 `externals`: + +```ts +export default defineConfig({ + lib: [ + { + tools: { + rspack: { + externals: [ + ({ request }, callback) => { + if (request?.startsWith('react')) { + callback(null, request); + return; + } + + callback(); + }, + ], }, }, }, @@ -89,4 +192,4 @@ export default defineConfig({ }); ``` -如果你需要精确控制哪些 import 被 external,建议禁用 `output.autoExternal`,并使用 `output.externals` 显式列出它们。如果 `react/jsx-runtime` 这类子路径 import 也需要 external 或改名,也需要把子路径一起添加到 `output.externals` 中。 +大多数场景优先使用 `output.autoExternal` 和 `output.externals`。只有在需要更复杂的 Rspack external 能力时,才建议使用 `tools.rspack.externals`。更多用法可参考 Rspack 的 [Externals](https://rspack.rs/zh/config/externals) 文档。