fix: `needCreateRequire` should be set to true only when module output and node target (#19761)

This commit is contained in:
hai-x 2025-08-26 02:23:34 +08:00 committed by GitHub
parent 08599420db
commit 076863133b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 113 additions and 29 deletions

View File

@ -30,11 +30,9 @@ const GetFullHashRuntimeModule = require("./runtime/GetFullHashRuntimeModule");
/** @typedef {import("./javascript/JavascriptParser").Range} Range */
/**
* @param {boolean | undefined} module true if ES module
* @param {string} importMetaName `import.meta` name
* @returns {Record<string, {expr: string, req: string[] | null, type?: string, assign: boolean}>} replacements
*/
function getReplacements(module, importMetaName) {
function getReplacements() {
return {
__webpack_require__: {
expr: RuntimeGlobals.require,
@ -67,9 +65,7 @@ function getReplacements(module, importMetaName) {
assign: true
},
__non_webpack_require__: {
expr: module
? `__WEBPACK_EXTERNAL_createRequire(${importMetaName}.url)`
: "require",
expr: "require",
req: null,
type: undefined, // type is not known, depends on environment
assign: true
@ -132,19 +128,9 @@ function getReplacements(module, importMetaName) {
const PLUGIN_NAME = "APIPlugin";
/**
* @typedef {object} APIPluginOptions
* @property {boolean=} module the output filename
*/
const moduleCreateRequire = "__WEBPACK_EXTERNAL_createRequire";
class APIPlugin {
/**
* @param {APIPluginOptions=} options options
*/
constructor(options = {}) {
this.options = options;
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
@ -155,10 +141,14 @@ class APIPlugin {
PLUGIN_NAME,
(compilation, { normalModuleFactory }) => {
const importMetaName = compilation.outputOptions.importMetaName;
const REPLACEMENTS = getReplacements(
this.options.module,
importMetaName
);
const moduleOutput = compilation.options.output.module;
const nodeTarget = compiler.platform.node;
const nodeEsm = moduleOutput && nodeTarget;
const REPLACEMENTS = getReplacements();
if (nodeEsm) {
REPLACEMENTS.__non_webpack_require__.expr = `${moduleCreateRequire}(${importMetaName}.url)`;
}
compilation.dependencyTemplates.set(
ConstDependency,
@ -190,7 +180,7 @@ class APIPlugin {
if (/** @type {BuildInfo} */ (module.buildInfo).needCreateRequire) {
const chunkInitFragments = [
new InitFragment(
`import { createRequire as __WEBPACK_EXTERNAL_createRequire } from ${renderContext.runtimeTemplate.renderNodePrefixForCoreModule(
`import { createRequire as ${moduleCreateRequire} } from ${renderContext.runtimeTemplate.renderNodePrefixForCoreModule(
"module"
)};\n`,
InitFragment.STAGE_HARMONY_IMPORTS,
@ -215,9 +205,20 @@ class APIPlugin {
parser.hooks.expression.for(key).tap(PLUGIN_NAME, (expression) => {
const dep = toConstantDependency(parser, info.expr, info.req);
if (key === "__non_webpack_require__" && this.options.module) {
/** @type {BuildInfo} */
(parser.state.module.buildInfo).needCreateRequire = true;
if (key === "__non_webpack_require__" && moduleOutput) {
if (nodeTarget) {
/** @type {BuildInfo} */
(parser.state.module.buildInfo).needCreateRequire = true;
} else {
const warning = new WebpackError(
`${PLUGIN_NAME}\n__non_webpack_require__ is only allowed in target node`
);
warning.loc = /** @type {DependencyLocation} */ (
expression.loc
);
warning.module = parser.state.module;
compilation.warnings.push(warning);
}
}
return dep(expression);

View File

@ -473,9 +473,7 @@ class WebpackOptionsApply extends OptionsApply {
new NodeStuffPlugin(options.node).apply(compiler);
}
new APIPlugin({
module: options.output.module
}).apply(compiler);
new APIPlugin().apply(compiler);
new ExportsInfoApiPlugin().apply(compiler);
new WebpackIsIncludedPlugin().apply(compiler);
new ConstPlugin().apply(compiler);

View File

@ -884,7 +884,7 @@ class ConcatenatedModule extends Module {
}
}
const { assets, assetsInfo, topLevelDeclarations } =
const { assets, assetsInfo, topLevelDeclarations, needCreateRequire } =
/** @type {BuildInfo} */ (m.buildInfo);
const buildInfo = /** @type {BuildInfo} */ (this.buildInfo);
@ -901,6 +901,11 @@ class ConcatenatedModule extends Module {
buildInfo.topLevelDeclarations = undefined;
}
// populate needCreateRequire
if (needCreateRequire) {
this.buildInfo.needCreateRequire = true;
}
// populate assets
if (assets) {
if (buildInfo.assets === undefined) {

View File

@ -0,0 +1 @@
export default __non_webpack_require__("./bar");

View File

@ -0,0 +1,5 @@
import foo from "./foo";
it("should generate createRequire in concatenated modules", function () {
expect(foo).toBe(1);
});

View File

@ -0,0 +1,5 @@
"use strict";
const supportsRequireInModule = require("../../../helpers/supportsRequireInModule");
module.exports = () => supportsRequireInModule();

View File

@ -0,0 +1,16 @@
"use strict";
const inCacheTest = (options) => {
if (Array.isArray(options)) {
return options.some((o) => o.cache);
}
return options.cache;
};
module.exports = (options) => {
if (inCacheTest(options)) {
// We will pre-compile twice, and the module cache will result in no warnings in the stats during the third compilation
return [];
}
return [[/__non_webpack_require__ is only allowed in target node/]];
};

View File

@ -0,0 +1,53 @@
"use strict";
const webpack = require("../../../../");
/** @type {import("../../../../").Configuration[]} */
module.exports = [
{
output: {
module: true
},
target: ["node"],
experiments: {
outputModule: true
},
plugins: [
{
apply(compiler) {
compiler.hooks.compilation.tap("Test", (compilation) => {
compilation.hooks.processAssets.tap(
{
name: "copy-webpack-plugin",
stage:
compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
},
() => {
compilation.emitAsset(
"bar.js",
new webpack.sources.RawSource("module.exports = 1;")
);
}
);
});
}
}
]
},
{
output: {
module: true
},
target: "web",
experiments: {
outputModule: true
},
plugins: [
new webpack.BannerPlugin({
raw: true,
banner:
'import { createRequire } from "module"; const require = createRequire(import.meta.url)'
})
]
}
];