From 6c7f6146b1dd865d6d849709a3a2b321bf246f8e Mon Sep 17 00:00:00 2001 From: Timeless0911 <1604889533@qq.com> Date: Tue, 26 May 2026 20:07:33 +0800 Subject: [PATCH 1/4] feat(core): use modern-module externals type for esm --- packages/core/src/config.ts | 180 +----------------- .../tests/__snapshots__/config.test.ts.snap | 22 +-- pnpm-lock.yaml | 15 +- tests/integration/auto-external/index.test.ts | 40 +--- .../module-import-warn/src/index.ts | 15 -- tests/integration/externals/index.test.ts | 58 +++--- .../modern-module-auto-external}/package.json | 3 +- .../rslib.config.ts | 13 +- .../modern-module-auto-external/src/index.ts | 31 +++ .../src/local-false.ts | 1 + .../externals/module-import-warn/package.json | 6 - .../module-import-warn/rslib.config.ts | 20 -- .../externals/module-import-warn/src/baz.ts | 1 - .../externals/module-import-warn/src/index.ts | 13 -- tests/integration/externals/node/src/index.ts | 2 +- website/docs/en/config/rsbuild/output.mdx | 7 + website/docs/zh/config/rsbuild/output.mdx | 7 + 17 files changed, 106 insertions(+), 328 deletions(-) delete mode 100644 tests/integration/auto-external/module-import-warn/src/index.ts rename tests/integration/{auto-external/module-import-warn => externals/modern-module-auto-external}/package.json (61%) rename tests/integration/{auto-external/module-import-warn => externals/modern-module-auto-external}/rslib.config.ts (71%) create mode 100644 tests/integration/externals/modern-module-auto-external/src/index.ts create mode 100644 tests/integration/externals/modern-module-auto-external/src/local-false.ts delete mode 100644 tests/integration/externals/module-import-warn/package.json delete mode 100644 tests/integration/externals/module-import-warn/rslib.config.ts delete mode 100644 tests/integration/externals/module-import-warn/src/baz.ts delete mode 100644 tests/integration/externals/module-import-warn/src/index.ts diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 1b41cc936..c33156e6e 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -71,159 +71,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, @@ -511,7 +358,6 @@ export async function createConstantRsbuildConfig(): Promise const composeFormatConfig = ({ format, - target, bundle = true, umdName, pkgJson, @@ -520,7 +366,6 @@ const composeFormatConfig = ({ sourceEntry, }: { format: Format; - target: RsbuildConfigOutputTarget; pkgJson: PkgJson; bundle?: boolean; umdName?: Rspack.LibraryName; @@ -552,7 +397,6 @@ const composeFormatConfig = ({ new rspack.experiments.RslibPlugin({ interceptApiPlugin: true, forceNodeShims: enabledShims.esm.__dirname || enabledShims.esm.__filename, - autoCjsNodeBuiltin: format === 'esm' && target === 'node', }), ].filter(Boolean); @@ -940,13 +784,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'], @@ -956,7 +793,7 @@ const composeExternalsConfig = ( // should to be unified and merged together in the future. const externalsTypeMap: Record = { - esm: 'module-import', + esm: 'modern-module', cjs: 'commonjs-import', umd: 'umd', // If use 'var', when projects import an external package like '@pkg', this will cause a syntax error such as 'var pkg = @pkg'. @@ -1684,8 +1521,9 @@ const composeTargetConfig = ( target: 'node', externalsConfig: { output: { - // When output.target is 'node', Node.js's built-in will be treated as externals of type `node-commonjs`. // Keep Node.js built-in modules externalized for all Node.js targets. + // The generated code follows the current format's externalsType, + // such as `modern-module` for ESM and `commonjs-import` for CJS. // https://github.com/webpack/webpack/blob/dd44b206a9c50f4b4cb4d134e1a0bd0387b159a3/lib/node/NodeTargetPlugin.js#L81 externals: nodeBuiltInModules, }, @@ -1784,7 +1622,6 @@ async function composeLibRsbuildConfig( } = composeTargetConfig(config.output?.target, format); const formatConfig = composeFormatConfig({ format, - target, pkgJson: pkgJson!, bundle, umdName, @@ -1855,11 +1692,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( @@ -1879,12 +1711,10 @@ 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, diff --git a/packages/core/tests/__snapshots__/config.test.ts.snap b/packages/core/tests/__snapshots__/config.test.ts.snap index c6259a780..2d8ffa3b4 100644 --- a/packages/core/tests/__snapshots__/config.test.ts.snap +++ b/packages/core/tests/__snapshots__/config.test.ts.snap @@ -321,7 +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($|\\/|\\\\)/, @@ -1036,8 +1035,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i _args: [ { interceptApiPlugin: true, - forceNodeShims: false, - autoCjsNodeBuiltin: true + forceNodeShims: false } ] }, @@ -1059,7 +1057,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i externalsPresets: { node: false }, - externalsType: 'module-import' + externalsType: 'modern-module' }" `; @@ -1801,8 +1799,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i _args: [ { interceptApiPlugin: true, - forceNodeShims: false, - autoCjsNodeBuiltin: false + forceNodeShims: false } ] }, @@ -2471,8 +2468,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i _args: [ { interceptApiPlugin: true, - forceNodeShims: false, - autoCjsNodeBuiltin: false + forceNodeShims: false } ] }, @@ -3144,8 +3140,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i _args: [ { interceptApiPlugin: true, - forceNodeShims: false, - autoCjsNodeBuiltin: false + forceNodeShims: false } ] }, @@ -3797,7 +3792,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i "jsAsync": "./", }, "externals": [ - [Function], /\\^@rsbuild\\\\/core\\(\\$\\|\\\\/\\|\\\\\\\\\\)/, /\\^rsbuild-plugin-dts\\(\\$\\|\\\\/\\|\\\\\\\\\\)/, /\\^@microsoft\\\\/api-extractor\\(\\$\\|\\\\/\\|\\\\\\\\\\)/, @@ -4004,7 +3998,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i RslibPlugin { "_args": [ { - "autoCjsNodeBuiltin": true, "forceNodeShims": false, "interceptApiPlugin": true, }, @@ -4029,7 +4022,7 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i ], }, { - "externalsType": "module-import", + "externalsType": "modern-module", }, { "plugins": [ @@ -4282,7 +4275,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i RslibPlugin { "_args": [ { - "autoCjsNodeBuiltin": false, "forceNodeShims": false, "interceptApiPlugin": true, }, @@ -4519,7 +4511,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i RslibPlugin { "_args": [ { - "autoCjsNodeBuiltin": false, "forceNodeShims": false, "interceptApiPlugin": true, }, @@ -4760,7 +4751,6 @@ exports[`Should compose create Rsbuild config correctly > Merge Rsbuild config i RslibPlugin { "_args": [ { - "autoCjsNodeBuiltin": false, "forceNodeShims": false, "interceptApiPlugin": true, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec770b723..27fb43c6c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -641,15 +641,6 @@ importers: specifier: ^19.2.6 version: 19.2.6 - tests/integration/auto-external/module-import-warn: - dependencies: - lodash: - specifier: ^4.18.1 - version: 4.18.1 - react: - specifier: ^19.2.6 - version: 19.2.6 - tests/integration/auto-external/with-externals: dependencies: react: @@ -931,7 +922,11 @@ importers: tests/integration/externals/esm-node-builtin: {} - tests/integration/externals/module-import-warn: {} + tests/integration/externals/modern-module-auto-external: + dependencies: + react: + specifier: ^19.2.6 + version: 19.2.6 tests/integration/externals/node: {} 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/auto-external/module-import-warn/src/index.ts b/tests/integration/auto-external/module-import-warn/src/index.ts deleted file mode 100644 index ab25d5ee9..000000000 --- a/tests/integration/auto-external/module-import-warn/src/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* rslint-disable @typescript-eslint/no-require-imports */ -export const foo = () => { - const React = require('react'); - const e1 = require('e1'); - const e2 = require('e2'); - const e3 = require('e3'); - const e4 = require('e4'); - const e5 = require('e5'); - const e6 = require('e6'); - const e7 = require('e7'); - const e8 = require('e8'); - const add = require('lodash/add'); - const drop = require('lodash/drop'); - return React.version + e1 + e2 + e3 + e4 + e5 + e6 + e7 + e8 + add + drop; -}; diff --git a/tests/integration/externals/index.test.ts b/tests/integration/externals/index.test.ts index a277aaf35..bead8bf88 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(); @@ -21,11 +19,13 @@ test('auto externalize Node.js built-in modules when `output.target` is "node"', restore(); for (const external of [ - 'import * as __rspack_external_bar from "bar";', 'import { createRequire as __rspack_createRequire } from "node:module";', 'import node_assert from "node:assert";', 'import fs from "fs";', 'import react from "react";', + 'const __rspack_createRequire_require = __rspack_createRequire(import.meta.url);', + 'module.exports = __rspack_createRequire_require("foo");', + 'module.exports = __rspack_createRequire_require("bar");', ]) { expect(entries.esm).toContain(external); } @@ -106,38 +106,42 @@ 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'); +test('modern-module externals type', async () => { + const fixturePath = join(__dirname, 'modern-module-auto-external'); 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); + expect(entries.esm).toContain( + 'import { createRequire as __rspack_createRequire } from "node:module";', + ); + expect(entries.esm).toContain( + 'const __rspack_createRequire_require = __rspack_createRequire(import.meta.url);', + ); + + for (const request of ['react', 'e2', 'e3', 'e5', 'e6', 'e7']) { + expect(entries.esm).toContain( + `module.exports = __rspack_createRequire_require("${request}");`, + ); } - for (const external of ['foo', 'bar', 'qux']) { - expect( - logStrings.some((l) => - l.includes(stripAnsi(composeModuleImportWarn(external, issuer))), - ), - ).toBe(true); + for (const request of ['e1', 'e4']) { + expect(entries.esm).toContain(`module.exports = require("${request}");`); } - for (const external of ['./baz', 'quxx']) { - expect( - logStrings.some((l) => - l.includes(stripAnsi(composeModuleImportWarn(external, issuer))), - ), - ).toBe(false); + expect(entries.esm).toContain('import * as __rspack_external_e8 from "e8";'); + expect(entries.esm).toContain('module.exports = __rspack_external_e8;'); + expect(entries.esm).toContain('"./src/local-false.ts"'); + expect(entries.esm).toContain( + 'const localFalse = __webpack_require__("./src/local-false.ts");', + ); + expect(entries.esm).not.toContain('require("./local-false")'); + + for (const request of ['e9', 'e10']) { + expect(entries.esm).toContain( + `module.exports = __rspack_createRequire_require("${request}");`, + ); } - restore(); + expect(entries.esm).toContain('const e11 = await import("e11");'); }); test('require ESM from CJS', async () => { diff --git a/tests/integration/auto-external/module-import-warn/package.json b/tests/integration/externals/modern-module-auto-external/package.json similarity index 61% rename from tests/integration/auto-external/module-import-warn/package.json rename to tests/integration/externals/modern-module-auto-external/package.json index 4318f2aeb..c9224f6ee 100644 --- a/tests/integration/auto-external/module-import-warn/package.json +++ b/tests/integration/externals/modern-module-auto-external/package.json @@ -1,9 +1,8 @@ { - "name": "module-import-warn", + "name": "externals-modern-module-auto-external-test", "private": true, "type": "module", "dependencies": { - "lodash": "^4.18.1", "react": "^19.2.6" } } diff --git a/tests/integration/auto-external/module-import-warn/rslib.config.ts b/tests/integration/externals/modern-module-auto-external/rslib.config.ts similarity index 71% rename from tests/integration/auto-external/module-import-warn/rslib.config.ts rename to tests/integration/externals/modern-module-auto-external/rslib.config.ts index 438ac421c..bbec33c33 100644 --- a/tests/integration/auto-external/module-import-warn/rslib.config.ts +++ b/tests/integration/externals/modern-module-auto-external/rslib.config.ts @@ -2,7 +2,13 @@ import { defineConfig } from '@rslib/core'; import { generateBundleEsmConfig } from 'test-helper'; export default defineConfig({ - lib: [generateBundleEsmConfig()], + lib: [ + generateBundleEsmConfig({ + output: { + externals: { e9: 'e9' }, + }, + }), + ], source: { entry: { index: './src/index.ts', @@ -16,12 +22,13 @@ export default defineConfig({ e3: true, e4: ['commonjs e4'], e5: ['e5'], - 'lodash/add': false, - 'lodash/drop': 'commonjs lodash/drop', + './local-false': false, e8: ['module e8'], }, /e6/, 'e7', + /^e10$/, + 'e11', ], }, }); diff --git a/tests/integration/externals/modern-module-auto-external/src/index.ts b/tests/integration/externals/modern-module-auto-external/src/index.ts new file mode 100644 index 000000000..ec49030d2 --- /dev/null +++ b/tests/integration/externals/modern-module-auto-external/src/index.ts @@ -0,0 +1,31 @@ +/* rslint-disable @typescript-eslint/no-require-imports */ +export const foo = async () => { + const React = require('react'); + const e1 = require('e1'); + const e2 = require('e2'); + const e3 = require('e3'); + const e4 = require('e4'); + const e5 = require('e5'); + const e6 = require('e6'); + const e7 = require('e7'); + const e8 = require('e8'); + const e9 = require('e9'); + const e10 = require('e10'); + const e11 = await import('e11'); + const localFalse = require('./local-false'); + return ( + React.version + + e1 + + e2 + + e3 + + e4 + + e5 + + e6 + + e7 + + e8 + + e9 + + e10 + + e11 + + localFalse.localFalse + ); +}; diff --git a/tests/integration/externals/modern-module-auto-external/src/local-false.ts b/tests/integration/externals/modern-module-auto-external/src/local-false.ts new file mode 100644 index 000000000..c229e03fa --- /dev/null +++ b/tests/integration/externals/modern-module-auto-external/src/local-false.ts @@ -0,0 +1 @@ +export const localFalse = 'local-false'; diff --git a/tests/integration/externals/module-import-warn/package.json b/tests/integration/externals/module-import-warn/package.json deleted file mode 100644 index 767a6cb52..000000000 --- a/tests/integration/externals/module-import-warn/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "externals-module-import-warn-test", - "version": "1.0.0", - "private": true, - "type": "module" -} diff --git a/tests/integration/externals/module-import-warn/rslib.config.ts b/tests/integration/externals/module-import-warn/rslib.config.ts deleted file mode 100644 index bcef068da..000000000 --- a/tests/integration/externals/module-import-warn/rslib.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { defineConfig } from '@rslib/core'; -import { generateBundleEsmConfig } from 'test-helper'; - -export default defineConfig({ - lib: [ - generateBundleEsmConfig({ - output: { - externals: { foo: 'foo' }, - }, - }), - ], - source: { - entry: { - index: './src/index.ts', - }, - }, - output: { - externals: ['bar', /^qux$/, 'quxx'], - }, -}); diff --git a/tests/integration/externals/module-import-warn/src/baz.ts b/tests/integration/externals/module-import-warn/src/baz.ts deleted file mode 100644 index cc40a4649..000000000 --- a/tests/integration/externals/module-import-warn/src/baz.ts +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {}; diff --git a/tests/integration/externals/module-import-warn/src/index.ts b/tests/integration/externals/module-import-warn/src/index.ts deleted file mode 100644 index dee182a76..000000000 --- a/tests/integration/externals/module-import-warn/src/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* rslint-disable @typescript-eslint/no-require-imports */ -export const baz = async () => { - const foo = require('foo'); - const bar = require('bar'); - const baz = require('./baz'); // not externalized - const qux = require('qux'); - const quxx = await import('quxx'); - foo(); - bar(); - baz(); - qux(); - quxx(); -}; diff --git a/tests/integration/externals/node/src/index.ts b/tests/integration/externals/node/src/index.ts index 9d4941e84..19866f3ab 100644 --- a/tests/integration/externals/node/src/index.ts +++ b/tests/integration/externals/node/src/index.ts @@ -11,7 +11,7 @@ export const foo = async () => { export const bar = async () => { assert(React.version); - const barModule = require('bar'); // ESM: fallback to "module" when not specify externals type + const barModule = require('bar'); // ESM: follows modern-module externals type barModule(); }; diff --git a/website/docs/en/config/rsbuild/output.mdx b/website/docs/en/config/rsbuild/output.mdx index 8624f321f..9e51f0f56 100644 --- a/website/docs/en/config/rsbuild/output.mdx +++ b/website/docs/en/config/rsbuild/output.mdx @@ -85,6 +85,13 @@ At build time, prevent some `import` dependencies from being packed into bundles 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 addition, Rslib sets the default Rspack `externalsType` based on the output format: + +- `esm`: [modern-module](https://rspack.rs/config/externals#externalstypemodern-module). Static `import` externals are emitted as ESM imports, dynamic `import()` externals stay dynamic, and CommonJS `require()` externals are loaded through `createRequire`. +- `cjs`: [commonjs-import](https://rspack.rs/config/externals#externalstypecommonjs-import). +- `umd`: [umd](https://rspack.rs/config/externals#externalstype). +- `mf` / `iife`: [global](https://rspack.rs/config/externals#externalstypeglobal). + :::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/rsbuild/output.mdx b/website/docs/zh/config/rsbuild/output.mdx index 298bb8737..b4c40746f 100644 --- a/website/docs/zh/config/rsbuild/output.mdx +++ b/website/docs/zh/config/rsbuild/output.mdx @@ -81,6 +81,13 @@ const defaultDistPath = { 在 bundle 模式下,Rslib 会默认将 `package.json` 中 `dependencies`、`optionalDependencies` 和 `peerDependencies` 字段下的三方依赖添加到 `output.externals` 中, 查看 [lib.autoExternal](/config/lib/auto-external) 了解更多信息。 +此外,Rslib 会根据产物格式设置 Rspack 的 `externalsType` 默认值: + +- `esm`:[modern-module](https://rspack.rs/zh/config/externals#externalstypemodern-module),静态 `import` 的 external 会被输出为 ESM import,动态 `import()` 会保持动态导入,而 CommonJS `require()` 的 external 会通过 `createRequire` 加载。 +- `cjs`:[commonjs-import](https://rspack.rs/zh/config/externals#externalstypecommonjs-import)。 +- `umd`:[umd](https://rspack.rs/zh/config/externals#externalstype)。 +- `mf` / `iife`:[global](https://rspack.rs/zh/config/externals#externalstypeglobal)。 + :::note 需要注意的是,`output.externals` 与 [resolve.alias](/config/rsbuild/resolve#resolvealias) 有所不同。请查看 [resolve.alias](/config/rsbuild/resolve#resolvealias) 文档以了解更多信息。 ::: From f296867fc742055f9ed536de405073f791ad1912 Mon Sep 17 00:00:00 2001 From: Timeless0911 <1604889533@qq.com> Date: Wed, 27 May 2026 13:42:47 +0800 Subject: [PATCH 2/4] fix(core): gate modern-module externals by target --- packages/core/src/config.ts | 9 +++- tests/integration/externals/index.test.ts | 44 ++++++++++++++----- .../rslib.config.ts | 10 +++++ website/docs/en/config/rsbuild/output.mdx | 5 ++- website/docs/zh/config/rsbuild/output.mdx | 5 ++- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index c33156e6e..24c2d735c 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -786,6 +786,7 @@ const composeShimsConfig = ( const composeExternalsConfig = ( format: Format, + target: RsbuildConfigOutputTarget, externals: NonNullable['externals'], ): EnvironmentConfig => { // TODO: Define the internal externals config in Rsbuild's externals instead @@ -793,7 +794,7 @@ const composeExternalsConfig = ( // should to be unified and merged together in the future. const externalsTypeMap: Record = { - esm: 'modern-module', + esm: target === 'node' ? 'modern-module' : 'module-import', cjs: 'commonjs-import', umd: 'umd', // If use 'var', when projects import an external package like '@pkg', this will cause a syntax error such as 'var pkg = @pkg'. @@ -1634,7 +1635,11 @@ async function composeLibRsbuildConfig( hasExe ? false : externalHelpers, pkgJson, ); - const userExternalsConfig = composeExternalsConfig(format, userExternals); + const userExternalsConfig = composeExternalsConfig( + format, + target, + userExternals, + ); const { config: outputFilenameConfig, jsExtension, diff --git a/tests/integration/externals/index.test.ts b/tests/integration/externals/index.test.ts index bead8bf88..e56e49b29 100644 --- a/tests/integration/externals/index.test.ts +++ b/tests/integration/externals/index.test.ts @@ -106,42 +106,62 @@ test('should remap node built-ins with user externals', async () => { expect(typeof esmOutput.loadPathSep).toBe('function'); }); -test('modern-module externals type', async () => { +test('ESM externals type should follow output.target', async () => { const fixturePath = join(__dirname, 'modern-module-auto-external'); const { entries } = await buildAndGetResults({ fixturePath }); + const nodeOutput = entries.esm0!; + const webOutput = entries.esm1!; - expect(entries.esm).toContain( + expect(nodeOutput).toContain( 'import { createRequire as __rspack_createRequire } from "node:module";', ); - expect(entries.esm).toContain( + expect(nodeOutput).toContain( 'const __rspack_createRequire_require = __rspack_createRequire(import.meta.url);', ); for (const request of ['react', 'e2', 'e3', 'e5', 'e6', 'e7']) { - expect(entries.esm).toContain( + expect(nodeOutput).toContain( `module.exports = __rspack_createRequire_require("${request}");`, ); } for (const request of ['e1', 'e4']) { - expect(entries.esm).toContain(`module.exports = require("${request}");`); + expect(nodeOutput).toContain(`module.exports = require("${request}");`); } - expect(entries.esm).toContain('import * as __rspack_external_e8 from "e8";'); - expect(entries.esm).toContain('module.exports = __rspack_external_e8;'); - expect(entries.esm).toContain('"./src/local-false.ts"'); - expect(entries.esm).toContain( + expect(nodeOutput).toContain('import * as __rspack_external_e8 from "e8";'); + expect(nodeOutput).toContain('module.exports = __rspack_external_e8;'); + expect(nodeOutput).toContain('"./src/local-false.ts"'); + expect(nodeOutput).toContain( 'const localFalse = __webpack_require__("./src/local-false.ts");', ); - expect(entries.esm).not.toContain('require("./local-false")'); + expect(nodeOutput).not.toContain('require("./local-false")'); for (const request of ['e9', 'e10']) { - expect(entries.esm).toContain( + expect(nodeOutput).toContain( `module.exports = __rspack_createRequire_require("${request}");`, ); } - expect(entries.esm).toContain('const e11 = await import("e11");'); + expect(nodeOutput).toContain('const e11 = await import("e11");'); + + expect(webOutput).not.toContain('node:module'); + expect(webOutput).not.toContain('__rspack_createRequire'); + + for (const request of ['react', 'e2', 'e3', 'e5', 'e6', 'e7', 'e9', 'e10']) { + expect(webOutput).toContain( + `import * as __rspack_external_${request} from "${request}";`, + ); + expect(webOutput).toContain( + `module.exports = __rspack_external_${request};`, + ); + } + + for (const request of ['e1', 'e4']) { + expect(webOutput).toContain(`module.exports = require("${request}");`); + } + + expect(webOutput).toContain('const e11 = await import("e11");'); }); test('require ESM from CJS', async () => { diff --git a/tests/integration/externals/modern-module-auto-external/rslib.config.ts b/tests/integration/externals/modern-module-auto-external/rslib.config.ts index bbec33c33..e4d85676e 100644 --- a/tests/integration/externals/modern-module-auto-external/rslib.config.ts +++ b/tests/integration/externals/modern-module-auto-external/rslib.config.ts @@ -4,7 +4,17 @@ import { generateBundleEsmConfig } from 'test-helper'; export default defineConfig({ lib: [ generateBundleEsmConfig({ + id: 'esm-node', output: { + distPath: './dist/esm-node', + externals: { e9: 'e9' }, + }, + }), + generateBundleEsmConfig({ + id: 'esm-web', + output: { + target: 'web', + distPath: './dist/esm-web', externals: { e9: 'e9' }, }, }), diff --git a/website/docs/en/config/rsbuild/output.mdx b/website/docs/en/config/rsbuild/output.mdx index 9e51f0f56..26306d4b4 100644 --- a/website/docs/en/config/rsbuild/output.mdx +++ b/website/docs/en/config/rsbuild/output.mdx @@ -85,9 +85,10 @@ At build time, prevent some `import` dependencies from being packed into bundles 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 addition, Rslib sets the default Rspack `externalsType` based on the output format: +In addition, Rslib sets the default Rspack `externalsType` based on the output format and `output.target`: -- `esm`: [modern-module](https://rspack.rs/config/externals#externalstypemodern-module). Static `import` externals are emitted as ESM imports, dynamic `import()` externals stay dynamic, and CommonJS `require()` externals are loaded through `createRequire`. +- `esm` with `output.target` set to `node` (default): [modern-module](https://rspack.rs/config/externals#externalstypemodern-module). Static `import` externals are emitted as ESM imports, dynamic `import()` externals stay dynamic, and CommonJS `require()` externals are loaded through `createRequire`. +- `esm` with `output.target` set to `web`: [module-import](https://rspack.rs/config/externals#externalstypemodule-import), preserving a safer external output for browser ESM bundles. - `cjs`: [commonjs-import](https://rspack.rs/config/externals#externalstypecommonjs-import). - `umd`: [umd](https://rspack.rs/config/externals#externalstype). - `mf` / `iife`: [global](https://rspack.rs/config/externals#externalstypeglobal). diff --git a/website/docs/zh/config/rsbuild/output.mdx b/website/docs/zh/config/rsbuild/output.mdx index b4c40746f..a3ded18cd 100644 --- a/website/docs/zh/config/rsbuild/output.mdx +++ b/website/docs/zh/config/rsbuild/output.mdx @@ -81,9 +81,10 @@ const defaultDistPath = { 在 bundle 模式下,Rslib 会默认将 `package.json` 中 `dependencies`、`optionalDependencies` 和 `peerDependencies` 字段下的三方依赖添加到 `output.externals` 中, 查看 [lib.autoExternal](/config/lib/auto-external) 了解更多信息。 -此外,Rslib 会根据产物格式设置 Rspack 的 `externalsType` 默认值: +此外,Rslib 会根据产物格式和 `output.target` 设置 Rspack 的 `externalsType` 默认值: -- `esm`:[modern-module](https://rspack.rs/zh/config/externals#externalstypemodern-module),静态 `import` 的 external 会被输出为 ESM import,动态 `import()` 会保持动态导入,而 CommonJS `require()` 的 external 会通过 `createRequire` 加载。 +- `esm` 且 `output.target` 为 `node`(默认值):[modern-module](https://rspack.rs/zh/config/externals#externalstypemodern-module),静态 `import` 的 external 会被输出为 ESM import,动态 `import()` 会保持动态导入,而 CommonJS `require()` 的 external 会通过 `createRequire` 加载。 +- `esm` 且 `output.target` 为 `web`:[module-import](https://rspack.rs/zh/config/externals#externalstypemodule-import),保持对浏览器 ESM 产物更安全的 external 输出。 - `cjs`:[commonjs-import](https://rspack.rs/zh/config/externals#externalstypecommonjs-import)。 - `umd`:[umd](https://rspack.rs/zh/config/externals#externalstype)。 - `mf` / `iife`:[global](https://rspack.rs/zh/config/externals#externalstypeglobal)。 From 28f0e839d0135f35660e40f025c3c99004288ed9 Mon Sep 17 00:00:00 2001 From: Timeless0911 <1604889533@qq.com> Date: Wed, 27 May 2026 13:45:49 +0800 Subject: [PATCH 3/4] docs: link externals type references --- website/docs/en/config/rsbuild/output.mdx | 2 +- website/docs/zh/config/rsbuild/output.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/en/config/rsbuild/output.mdx b/website/docs/en/config/rsbuild/output.mdx index 26306d4b4..41eed8912 100644 --- a/website/docs/en/config/rsbuild/output.mdx +++ b/website/docs/en/config/rsbuild/output.mdx @@ -85,7 +85,7 @@ At build time, prevent some `import` dependencies from being packed into bundles 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 addition, Rslib sets the default Rspack `externalsType` based on the output format and `output.target`: +In addition, Rslib sets the default Rspack [`externalsType`](https://rspack.rs/config/externals#externalstype) based on the output format and [`output.target`](#outputtarget): - `esm` with `output.target` set to `node` (default): [modern-module](https://rspack.rs/config/externals#externalstypemodern-module). Static `import` externals are emitted as ESM imports, dynamic `import()` externals stay dynamic, and CommonJS `require()` externals are loaded through `createRequire`. - `esm` with `output.target` set to `web`: [module-import](https://rspack.rs/config/externals#externalstypemodule-import), preserving a safer external output for browser ESM bundles. diff --git a/website/docs/zh/config/rsbuild/output.mdx b/website/docs/zh/config/rsbuild/output.mdx index a3ded18cd..852d3642e 100644 --- a/website/docs/zh/config/rsbuild/output.mdx +++ b/website/docs/zh/config/rsbuild/output.mdx @@ -81,7 +81,7 @@ const defaultDistPath = { 在 bundle 模式下,Rslib 会默认将 `package.json` 中 `dependencies`、`optionalDependencies` 和 `peerDependencies` 字段下的三方依赖添加到 `output.externals` 中, 查看 [lib.autoExternal](/config/lib/auto-external) 了解更多信息。 -此外,Rslib 会根据产物格式和 `output.target` 设置 Rspack 的 `externalsType` 默认值: +此外,Rslib 会根据产物格式和 [`output.target`](#outputtarget) 设置 Rspack 的 [`externalsType`](https://rspack.rs/zh/config/externals#externalstype) 默认值: - `esm` 且 `output.target` 为 `node`(默认值):[modern-module](https://rspack.rs/zh/config/externals#externalstypemodern-module),静态 `import` 的 external 会被输出为 ESM import,动态 `import()` 会保持动态导入,而 CommonJS `require()` 的 external 会通过 `createRequire` 加载。 - `esm` 且 `output.target` 为 `web`:[module-import](https://rspack.rs/zh/config/externals#externalstypemodule-import),保持对浏览器 ESM 产物更安全的 external 输出。 From 7baca3eb91eeaf41f903a35a6cf0b1ef18c0ca66 Mon Sep 17 00:00:00 2001 From: Timeless0911 <1604889533@qq.com> Date: Fri, 29 May 2026 13:58:23 +0800 Subject: [PATCH 4/4] fix(core): use modern-module for all esm externals --- packages/core/src/config.ts | 9 ++------- website/docs/en/config/rsbuild/output.mdx | 5 ++--- website/docs/zh/config/rsbuild/output.mdx | 5 ++--- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 24c2d735c..c33156e6e 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -786,7 +786,6 @@ const composeShimsConfig = ( const composeExternalsConfig = ( format: Format, - target: RsbuildConfigOutputTarget, externals: NonNullable['externals'], ): EnvironmentConfig => { // TODO: Define the internal externals config in Rsbuild's externals instead @@ -794,7 +793,7 @@ const composeExternalsConfig = ( // should to be unified and merged together in the future. const externalsTypeMap: Record = { - esm: target === 'node' ? 'modern-module' : 'module-import', + esm: 'modern-module', cjs: 'commonjs-import', umd: 'umd', // If use 'var', when projects import an external package like '@pkg', this will cause a syntax error such as 'var pkg = @pkg'. @@ -1635,11 +1634,7 @@ async function composeLibRsbuildConfig( hasExe ? false : externalHelpers, pkgJson, ); - const userExternalsConfig = composeExternalsConfig( - format, - target, - userExternals, - ); + const userExternalsConfig = composeExternalsConfig(format, userExternals); const { config: outputFilenameConfig, jsExtension, diff --git a/website/docs/en/config/rsbuild/output.mdx b/website/docs/en/config/rsbuild/output.mdx index 41eed8912..ee5ae26ca 100644 --- a/website/docs/en/config/rsbuild/output.mdx +++ b/website/docs/en/config/rsbuild/output.mdx @@ -85,10 +85,9 @@ At build time, prevent some `import` dependencies from being packed into bundles 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 addition, Rslib sets the default Rspack [`externalsType`](https://rspack.rs/config/externals#externalstype) based on the output format and [`output.target`](#outputtarget): +In addition, Rslib sets the default Rspack [`externalsType`](https://rspack.rs/config/externals#externalstype) based on the output format: -- `esm` with `output.target` set to `node` (default): [modern-module](https://rspack.rs/config/externals#externalstypemodern-module). Static `import` externals are emitted as ESM imports, dynamic `import()` externals stay dynamic, and CommonJS `require()` externals are loaded through `createRequire`. -- `esm` with `output.target` set to `web`: [module-import](https://rspack.rs/config/externals#externalstypemodule-import), preserving a safer external output for browser ESM bundles. +- `esm`: [modern-module](https://rspack.rs/config/externals#externalstypemodern-module). Static `import` externals are emitted as ESM imports, and dynamic `import()` externals stay dynamic. CommonJS `require()` externals load through `createRequire` for Node-like [`output.target`](#outputtarget) values, while other targets preserve a bare `require()` call. - `cjs`: [commonjs-import](https://rspack.rs/config/externals#externalstypecommonjs-import). - `umd`: [umd](https://rspack.rs/config/externals#externalstype). - `mf` / `iife`: [global](https://rspack.rs/config/externals#externalstypeglobal). diff --git a/website/docs/zh/config/rsbuild/output.mdx b/website/docs/zh/config/rsbuild/output.mdx index 852d3642e..0f96151e3 100644 --- a/website/docs/zh/config/rsbuild/output.mdx +++ b/website/docs/zh/config/rsbuild/output.mdx @@ -81,10 +81,9 @@ const defaultDistPath = { 在 bundle 模式下,Rslib 会默认将 `package.json` 中 `dependencies`、`optionalDependencies` 和 `peerDependencies` 字段下的三方依赖添加到 `output.externals` 中, 查看 [lib.autoExternal](/config/lib/auto-external) 了解更多信息。 -此外,Rslib 会根据产物格式和 [`output.target`](#outputtarget) 设置 Rspack 的 [`externalsType`](https://rspack.rs/zh/config/externals#externalstype) 默认值: +此外,Rslib 会根据产物格式设置 Rspack 的 [`externalsType`](https://rspack.rs/zh/config/externals#externalstype) 默认值: -- `esm` 且 `output.target` 为 `node`(默认值):[modern-module](https://rspack.rs/zh/config/externals#externalstypemodern-module),静态 `import` 的 external 会被输出为 ESM import,动态 `import()` 会保持动态导入,而 CommonJS `require()` 的 external 会通过 `createRequire` 加载。 -- `esm` 且 `output.target` 为 `web`:[module-import](https://rspack.rs/zh/config/externals#externalstypemodule-import),保持对浏览器 ESM 产物更安全的 external 输出。 +- `esm`:[modern-module](https://rspack.rs/zh/config/externals#externalstypemodern-module),静态 `import` 的 external 会被输出为 ESM import,动态 `import()` 会保持动态导入;CommonJS `require()` 的 external 在 Node-like [`output.target`](#outputtarget) 下会通过 `createRequire` 加载,在其他 target 下会保留裸 `require()` 调用。 - `cjs`:[commonjs-import](https://rspack.rs/zh/config/externals#externalstypecommonjs-import)。 - `umd`:[umd](https://rspack.rs/zh/config/externals#externalstype)。 - `mf` / `iife`:[global](https://rspack.rs/zh/config/externals#externalstypeglobal)。