diff --git a/lib/APIPlugin.js b/lib/APIPlugin.js index 2abce1c43..bef0b2538 100644 --- a/lib/APIPlugin.js +++ b/lib/APIPlugin.js @@ -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} 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); diff --git a/lib/WebpackOptionsApply.js b/lib/WebpackOptionsApply.js index d6eadbcc3..676e4af5a 100644 --- a/lib/WebpackOptionsApply.js +++ b/lib/WebpackOptionsApply.js @@ -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); diff --git a/lib/optimize/ConcatenatedModule.js b/lib/optimize/ConcatenatedModule.js index b0f452323..0e2aa99bb 100644 --- a/lib/optimize/ConcatenatedModule.js +++ b/lib/optimize/ConcatenatedModule.js @@ -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) { diff --git a/test/configCases/module/non-webpack-require-warning/foo.js b/test/configCases/module/non-webpack-require-warning/foo.js new file mode 100644 index 000000000..d64de8ca8 --- /dev/null +++ b/test/configCases/module/non-webpack-require-warning/foo.js @@ -0,0 +1 @@ +export default __non_webpack_require__("./bar"); diff --git a/test/configCases/module/non-webpack-require-warning/index.js b/test/configCases/module/non-webpack-require-warning/index.js new file mode 100644 index 000000000..495db691c --- /dev/null +++ b/test/configCases/module/non-webpack-require-warning/index.js @@ -0,0 +1,5 @@ +import foo from "./foo"; + +it("should generate createRequire in concatenated modules", function () { + expect(foo).toBe(1); +}); diff --git a/test/configCases/module/non-webpack-require-warning/test.filter.js b/test/configCases/module/non-webpack-require-warning/test.filter.js new file mode 100644 index 000000000..3185ff623 --- /dev/null +++ b/test/configCases/module/non-webpack-require-warning/test.filter.js @@ -0,0 +1,5 @@ +"use strict"; + +const supportsRequireInModule = require("../../../helpers/supportsRequireInModule"); + +module.exports = () => supportsRequireInModule(); diff --git a/test/configCases/module/non-webpack-require-warning/warnings.js b/test/configCases/module/non-webpack-require-warning/warnings.js new file mode 100644 index 000000000..ed72f7dc5 --- /dev/null +++ b/test/configCases/module/non-webpack-require-warning/warnings.js @@ -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/]]; +}; diff --git a/test/configCases/module/non-webpack-require-warning/webpack.config.js b/test/configCases/module/non-webpack-require-warning/webpack.config.js new file mode 100644 index 000000000..1d80af917 --- /dev/null +++ b/test/configCases/module/non-webpack-require-warning/webpack.config.js @@ -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)' + }) + ] + } +];