From 1328dcb024447bafe464ef3c13682edb6b262c55 Mon Sep 17 00:00:00 2001 From: Alexander Akait <4567934+alexander-akait@users.noreply.github.com> Date: Wed, 12 Nov 2025 09:43:04 +0300 Subject: [PATCH] fix: properly handle external presets for universal target --- lib/WebpackOptionsApply.js | 10 +++++- lib/config/defaults.js | 33 ++++++++++++++----- lib/node/NodeTargetPlugin.js | 10 +++++- test/Defaults.unittest.js | 5 +-- test/configCases/target/universal/index.js | 12 +++++++ .../target/universal/test.filter.js | 5 +++ .../target/universal/webpack.config.js | 4 +-- types.d.ts | 3 +- 8 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 test/configCases/target/universal/test.filter.js diff --git a/lib/WebpackOptionsApply.js b/lib/WebpackOptionsApply.js index 156cbe11f..37a10ba7a 100644 --- a/lib/WebpackOptionsApply.js +++ b/lib/WebpackOptionsApply.js @@ -110,7 +110,15 @@ class WebpackOptionsApply extends OptionsApply { if (options.externalsPresets.node) { const NodeTargetPlugin = require("./node/NodeTargetPlugin"); - new NodeTargetPlugin().apply(compiler); + // Some older versions of Node.js don't support all built-in modules via import, only via `require`, + // but шt seems like there shouldn't be a warning here since these versions are rarely used in real applications + new NodeTargetPlugin( + options.output.module && + compiler.platform.node === null && + compiler.platform.web === null + ? "module-import" + : "node-commonjs" + ).apply(compiler); // Handle external CSS `@import` and `url()` if (options.experiments.css) { diff --git a/lib/config/defaults.js b/lib/config/defaults.js index e46e004ee..b875a2127 100644 --- a/lib/config/defaults.js +++ b/lib/config/defaults.js @@ -411,7 +411,10 @@ const applyWebpackOptionsDefaults = (options, compilerIndex) => { applyExternalsPresetsDefaults(options.externalsPresets, { targetProperties, - buildHttp: Boolean(options.experiments.buildHttp) + buildHttp: Boolean(options.experiments.buildHttp), + outputModule: + /** @type {NonNullable} */ + (options.output.module) }); applyLoaderDefaults( @@ -1551,35 +1554,47 @@ const applyOutputDefaults = ( * @param {object} options options * @param {TargetProperties | false} options.targetProperties target properties * @param {boolean} options.buildHttp buildHttp experiment enabled + * @param {boolean} options.outputModule is output type is module * @returns {void} */ const applyExternalsPresetsDefaults = ( externalsPresets, - { targetProperties, buildHttp } + { targetProperties, buildHttp, outputModule } ) => { + /** + * @param {keyof TargetProperties} key a key + * @returns {boolean} true when target is universal, otherwise false + */ + const isUniversal = (key) => + Boolean(outputModule && targetProperties && targetProperties[key] === null); + D( externalsPresets, "web", /** @type {boolean | undefined} */ - (!buildHttp && targetProperties && targetProperties.web) + ( + !buildHttp && + targetProperties && + (targetProperties.web || isUniversal("node")) + ) ); D( externalsPresets, "node", /** @type {boolean | undefined} */ - (targetProperties && targetProperties.node) + (targetProperties && (targetProperties.node || isUniversal("node"))) ); D( externalsPresets, "nwjs", /** @type {boolean | undefined} */ - (targetProperties && targetProperties.nwjs) + (targetProperties && (targetProperties.nwjs || isUniversal("nwjs"))) ); D( externalsPresets, "electron", /** @type {boolean | undefined} */ - (targetProperties && targetProperties.electron) + ((targetProperties && targetProperties.electron) || isUniversal("electron")) ); D( externalsPresets, @@ -1588,7 +1603,7 @@ const applyExternalsPresetsDefaults = ( ( targetProperties && targetProperties.electron && - targetProperties.electronMain + (targetProperties.electronMain || isUniversal("electronMain")) ) ); D( @@ -1598,7 +1613,7 @@ const applyExternalsPresetsDefaults = ( ( targetProperties && targetProperties.electron && - targetProperties.electronPreload + (targetProperties.electronPreload || isUniversal("electronPreload")) ) ); D( @@ -1608,7 +1623,7 @@ const applyExternalsPresetsDefaults = ( ( targetProperties && targetProperties.electron && - targetProperties.electronRenderer + (targetProperties.electronRenderer || isUniversal("electronRenderer")) ) ); }; diff --git a/lib/node/NodeTargetPlugin.js b/lib/node/NodeTargetPlugin.js index 1cc01810d..28ab03a6b 100644 --- a/lib/node/NodeTargetPlugin.js +++ b/lib/node/NodeTargetPlugin.js @@ -7,6 +7,7 @@ const ExternalsPlugin = require("../ExternalsPlugin"); +/** @typedef {import("../../declarations/WebpackOptions").ExternalsType} ExternalsType */ /** @typedef {import("../Compiler")} Compiler */ const builtins = [ @@ -72,13 +73,20 @@ const builtins = [ ]; class NodeTargetPlugin { + /** + * @param {ExternalsType} type default external type + */ + constructor(type = "node-commonjs") { + this.type = type; + } + /** * Apply the plugin * @param {Compiler} compiler the compiler instance * @returns {void} */ apply(compiler) { - new ExternalsPlugin("node-commonjs", builtins).apply(compiler); + new ExternalsPlugin(this.type, builtins).apply(compiler); } } diff --git a/test/Defaults.unittest.js b/test/Defaults.unittest.js index 31bf58e7c..6f1dba854 100644 --- a/test/Defaults.unittest.js +++ b/test/Defaults.unittest.js @@ -1820,10 +1820,7 @@ describe("snapshots", () => { + "outputModule": true, @@ ... @@ - "node": false, - + "node": null, - @@ ... @@ - - "web": true, - + "web": null, + + "node": true, @@ ... @@ - "externalsType": "var", + "externalsType": "module-import", diff --git a/test/configCases/target/universal/index.js b/test/configCases/target/universal/index.js index 0d68af7df..bcf3fd7ea 100644 --- a/test/configCases/target/universal/index.js +++ b/test/configCases/target/universal/index.js @@ -1,6 +1,14 @@ import value from "./separate"; import { test as t } from "external-self"; +function isBrowser() { + return typeof globalThis.window !== 'undefined' && typeof globalThis.document !== 'undefined'; +} + +it("should compile check", () => { + expect(isBrowser() ? "web" : "node").toBe(!isBrowser() ? "node" : "web"); +}); + it("should compile", () => { expect(value).toBe(42); }); @@ -15,6 +23,10 @@ it("work with URL", () => { expect(/[a-f0-9]{20}\.png/.test(url)).toBe(true); }); +it("work with node.js modules", async () => { + expect(typeof (isBrowser() ? URL : (await import("url")).URL)).toBe("function"); +}); + function test() { return 42; } diff --git a/test/configCases/target/universal/test.filter.js b/test/configCases/target/universal/test.filter.js new file mode 100644 index 000000000..8686fce34 --- /dev/null +++ b/test/configCases/target/universal/test.filter.js @@ -0,0 +1,5 @@ +"use strict"; + +const supportsGlobalThis = require("../../../helpers/supportsGlobalThis"); + +module.exports = () => supportsGlobalThis(); diff --git a/test/configCases/target/universal/webpack.config.js b/test/configCases/target/universal/webpack.config.js index dd69f571b..2a2fc1b52 100644 --- a/test/configCases/target/universal/webpack.config.js +++ b/test/configCases/target/universal/webpack.config.js @@ -15,7 +15,7 @@ module.exports = [ outputModule: true }, optimization: { - minimize: true, + minimize: false, runtimeChunk: "single", splitChunks: { cacheGroups: { @@ -45,7 +45,7 @@ module.exports = [ outputModule: true }, optimization: { - minimize: true, + minimize: false, runtimeChunk: "single", splitChunks: { cacheGroups: { diff --git a/types.d.ts b/types.d.ts index 8de26f3b8..ff0ebba1a 100644 --- a/types.d.ts +++ b/types.d.ts @@ -11429,7 +11429,8 @@ declare class NodeSourcePlugin { apply(compiler: Compiler): void; } declare class NodeTargetPlugin { - constructor(); + constructor(type?: ExternalsType); + type: ExternalsType; /** * Apply the plugin