Skip to content

Commit

Permalink
Merge pull request #17308 from webpack/issue-17306
Browse files Browse the repository at this point in the history
fix: compatibility `__non_webpack_require__` with ES modules
  • Loading branch information
TheLarkInn committed Jun 5, 2023
2 parents 9f837fc + ce9c21e commit f1ea815
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 204 deletions.
249 changes: 150 additions & 99 deletions lib/APIPlugin.js
Expand Up @@ -5,6 +5,7 @@

"use strict";

const InitFragment = require("./InitFragment");
const {
JAVASCRIPT_MODULE_TYPE_AUTO,
JAVASCRIPT_MODULE_TYPE_DYNAMIC,
Expand All @@ -14,6 +15,7 @@ const RuntimeGlobals = require("./RuntimeGlobals");
const WebpackError = require("./WebpackError");
const ConstDependency = require("./dependencies/ConstDependency");
const BasicEvaluatedExpression = require("./javascript/BasicEvaluatedExpression");
const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
const {
toConstantDependency,
evaluateToString
Expand All @@ -24,103 +26,121 @@ const GetFullHashRuntimeModule = require("./runtime/GetFullHashRuntimeModule");
/** @typedef {import("./Compiler")} Compiler */
/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */

/* eslint-disable camelcase */
const REPLACEMENTS = {
__webpack_require__: {
expr: RuntimeGlobals.require,
req: [RuntimeGlobals.require],
type: "function",
assign: false
},
__webpack_public_path__: {
expr: RuntimeGlobals.publicPath,
req: [RuntimeGlobals.publicPath],
type: "string",
assign: true
},
__webpack_base_uri__: {
expr: RuntimeGlobals.baseURI,
req: [RuntimeGlobals.baseURI],
type: "string",
assign: true
},
__webpack_modules__: {
expr: RuntimeGlobals.moduleFactories,
req: [RuntimeGlobals.moduleFactories],
type: "object",
assign: false
},
__webpack_chunk_load__: {
expr: RuntimeGlobals.ensureChunk,
req: [RuntimeGlobals.ensureChunk],
type: "function",
assign: true
},
__non_webpack_require__: {
expr: "require",
req: null,
type: undefined, // type is not known, depends on environment
assign: true
},
__webpack_nonce__: {
expr: RuntimeGlobals.scriptNonce,
req: [RuntimeGlobals.scriptNonce],
type: "string",
assign: true
},
__webpack_hash__: {
expr: `${RuntimeGlobals.getFullHash}()`,
req: [RuntimeGlobals.getFullHash],
type: "string",
assign: false
},
__webpack_chunkname__: {
expr: RuntimeGlobals.chunkName,
req: [RuntimeGlobals.chunkName],
type: "string",
assign: false
},
__webpack_get_script_filename__: {
expr: RuntimeGlobals.getChunkScriptFilename,
req: [RuntimeGlobals.getChunkScriptFilename],
type: "function",
assign: true
},
__webpack_runtime_id__: {
expr: RuntimeGlobals.runtimeId,
req: [RuntimeGlobals.runtimeId],
assign: false
},
"require.onError": {
expr: RuntimeGlobals.uncaughtErrorHandler,
req: [RuntimeGlobals.uncaughtErrorHandler],
type: undefined, // type is not known, could be function or undefined
assign: true // is never a pattern
},
__system_context__: {
expr: RuntimeGlobals.systemContext,
req: [RuntimeGlobals.systemContext],
type: "object",
assign: false
},
__webpack_share_scopes__: {
expr: RuntimeGlobals.shareScopeMap,
req: [RuntimeGlobals.shareScopeMap],
type: "object",
assign: false
},
__webpack_init_sharing__: {
expr: RuntimeGlobals.initializeSharing,
req: [RuntimeGlobals.initializeSharing],
type: "function",
assign: true
}
};
/* eslint-enable camelcase */
/**
* @param {boolean} module true if ES module
* @param {string} importMetaName `import.meta` name
* @returns {Record<string, {expr: string, req: string[], type?: string, assign: boolean}>} replacements
*/
function getReplacements(module, importMetaName) {
return {
__webpack_require__: {
expr: RuntimeGlobals.require,
req: [RuntimeGlobals.require],
type: "function",
assign: false
},
__webpack_public_path__: {
expr: RuntimeGlobals.publicPath,
req: [RuntimeGlobals.publicPath],
type: "string",
assign: true
},
__webpack_base_uri__: {
expr: RuntimeGlobals.baseURI,
req: [RuntimeGlobals.baseURI],
type: "string",
assign: true
},
__webpack_modules__: {
expr: RuntimeGlobals.moduleFactories,
req: [RuntimeGlobals.moduleFactories],
type: "object",
assign: false
},
__webpack_chunk_load__: {
expr: RuntimeGlobals.ensureChunk,
req: [RuntimeGlobals.ensureChunk],
type: "function",
assign: true
},
__non_webpack_require__: {
expr: module
? `__WEBPACK_EXTERNAL_createRequire(${importMetaName}.url)`
: "require",
req: null,
type: undefined, // type is not known, depends on environment
assign: true
},
__webpack_nonce__: {
expr: RuntimeGlobals.scriptNonce,
req: [RuntimeGlobals.scriptNonce],
type: "string",
assign: true
},
__webpack_hash__: {
expr: `${RuntimeGlobals.getFullHash}()`,
req: [RuntimeGlobals.getFullHash],
type: "string",
assign: false
},
__webpack_chunkname__: {
expr: RuntimeGlobals.chunkName,
req: [RuntimeGlobals.chunkName],
type: "string",
assign: false
},
__webpack_get_script_filename__: {
expr: RuntimeGlobals.getChunkScriptFilename,
req: [RuntimeGlobals.getChunkScriptFilename],
type: "function",
assign: true
},
__webpack_runtime_id__: {
expr: RuntimeGlobals.runtimeId,
req: [RuntimeGlobals.runtimeId],
assign: false
},
"require.onError": {
expr: RuntimeGlobals.uncaughtErrorHandler,
req: [RuntimeGlobals.uncaughtErrorHandler],
type: undefined, // type is not known, could be function or undefined
assign: true // is never a pattern
},
__system_context__: {
expr: RuntimeGlobals.systemContext,
req: [RuntimeGlobals.systemContext],
type: "object",
assign: false
},
__webpack_share_scopes__: {
expr: RuntimeGlobals.shareScopeMap,
req: [RuntimeGlobals.shareScopeMap],
type: "object",
assign: false
},
__webpack_init_sharing__: {
expr: RuntimeGlobals.initializeSharing,
req: [RuntimeGlobals.initializeSharing],
type: "function",
assign: true
}
};
}

const PLUGIN_NAME = "APIPlugin";

/**
* @typedef {Object} APIPluginOptions
* @property {boolean} [module] the output filename
*/

class APIPlugin {
/**
* @param {APIPluginOptions} [options] options
*/
constructor(options = {}) {
this.options = options;
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
Expand All @@ -130,6 +150,12 @@ class APIPlugin {
compiler.hooks.compilation.tap(
PLUGIN_NAME,
(compilation, { normalModuleFactory }) => {
const { importMetaName } = compilation.outputOptions;
const REPLACEMENTS = getReplacements(
this.options.module,
importMetaName
);

compilation.dependencyTemplates.set(
ConstDependency,
new ConstDependency.Template()
Expand All @@ -152,18 +178,43 @@ class APIPlugin {
return true;
});

const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation);

hooks.renderModuleContent.tap(
PLUGIN_NAME,
(source, module, renderContext) => {
if (module.buildInfo.needCreateRequire) {
const chunkInitFragments = [
new InitFragment(
'import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "module";\n',
InitFragment.STAGE_HARMONY_IMPORTS,
0,
"external module node-commonjs"
)
];

renderContext.chunkInitFragments.push(...chunkInitFragments);
}

return source;
}
);

/**
* @param {JavascriptParser} parser the parser
*/
const handler = parser => {
Object.keys(REPLACEMENTS).forEach(key => {
const info = REPLACEMENTS[key];
parser.hooks.expression
.for(key)
.tap(
PLUGIN_NAME,
toConstantDependency(parser, info.expr, info.req)
);
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) {
parser.state.module.buildInfo.needCreateRequire = true;
}

return dep(expression);
});
if (info.assign === false) {
parser.hooks.assign.for(key).tap(PLUGIN_NAME, expr => {
const err = new WebpackError(`${key} must not be assigned`);
Expand Down
23 changes: 15 additions & 8 deletions lib/ExternalModule.js
Expand Up @@ -104,9 +104,13 @@ const getSourceForCommonJsExternal = moduleAndSpecifiers => {

/**
* @param {string|string[]} moduleAndSpecifiers the module request
* @param {string} importMetaName import.meta name
* @returns {SourceData} the generated source
*/
const getSourceForCommonJsExternalInNodeModule = moduleAndSpecifiers => {
const getSourceForCommonJsExternalInNodeModule = (
moduleAndSpecifiers,
importMetaName
) => {
const chunkInitFragments = [
new InitFragment(
'import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "module";\n',
Expand All @@ -117,18 +121,18 @@ const getSourceForCommonJsExternalInNodeModule = moduleAndSpecifiers => {
];
if (!Array.isArray(moduleAndSpecifiers)) {
return {
expression: `__WEBPACK_EXTERNAL_createRequire(import.meta.url)(${JSON.stringify(
chunkInitFragments,
expression: `__WEBPACK_EXTERNAL_createRequire(${importMetaName}.url)(${JSON.stringify(
moduleAndSpecifiers
)})`,
chunkInitFragments
)})`
};
}
const moduleName = moduleAndSpecifiers[0];
return {
expression: `__WEBPACK_EXTERNAL_createRequire(import.meta.url)(${JSON.stringify(
chunkInitFragments,
expression: `__WEBPACK_EXTERNAL_createRequire(${importMetaName}.url)(${JSON.stringify(
moduleName
)})${propertyAccess(moduleAndSpecifiers, 1)}`,
chunkInitFragments
)})${propertyAccess(moduleAndSpecifiers, 1)}`
};
};

Expand Down Expand Up @@ -557,7 +561,10 @@ class ExternalModule extends Module {
return getSourceForCommonJsExternal(request);
case "node-commonjs":
return this.buildInfo.module
? getSourceForCommonJsExternalInNodeModule(request)
? getSourceForCommonJsExternalInNodeModule(
request,
runtimeTemplate.outputOptions.importMetaName
)
: getSourceForCommonJsExternal(request);
case "amd":
case "amd-require":
Expand Down
4 changes: 3 additions & 1 deletion lib/WebpackOptionsApply.js
Expand Up @@ -368,7 +368,9 @@ class WebpackOptionsApply extends OptionsApply {
const NodeStuffPlugin = require("./NodeStuffPlugin");
new NodeStuffPlugin(options.node).apply(compiler);
}
new APIPlugin().apply(compiler);
new APIPlugin({
module: options.output.module
}).apply(compiler);
new ExportsInfoApiPlugin().apply(compiler);
new WebpackIsIncludedPlugin().apply(compiler);
new ConstPlugin().apply(compiler);
Expand Down
1 change: 1 addition & 0 deletions lib/javascript/JavascriptModulesPlugin.js
Expand Up @@ -937,6 +937,7 @@ class JavascriptModulesPlugin {
"JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().renderContent plugins should return something"
);
}

finalSource = InitFragment.addToSource(
finalSource,
chunkRenderContext.chunkInitFragments,
Expand Down
3 changes: 2 additions & 1 deletion lib/node/ReadFileCompileAsyncWasmPlugin.js
Expand Up @@ -40,6 +40,7 @@ class ReadFileCompileAsyncWasmPlugin {
: globalWasmLoading;
return wasmLoading === this._type;
};
const { importMetaName } = compilation.outputOptions;
/**
* @type {(path: string) => string}
*/
Expand All @@ -48,7 +49,7 @@ class ReadFileCompileAsyncWasmPlugin {
Template.asString([
"Promise.all([import('fs'), import('url')]).then(([{ readFile }, { URL }]) => new Promise((resolve, reject) => {",
Template.indent([
`readFile(new URL(${path}, import.meta.url), (err, buffer) => {`,
`readFile(new URL(${path}, ${importMetaName}.url), (err, buffer) => {`,
Template.indent([
"if (err) return reject(err);",
"",
Expand Down
3 changes: 3 additions & 0 deletions test/ConfigTestCases.template.js
Expand Up @@ -441,6 +441,9 @@ const describeCases = config => {
) {
baseModuleScope.window = globalContext;
baseModuleScope.self = globalContext;
baseModuleScope.document = globalContext.document;
baseModuleScope.setTimeout = globalContext.setTimeout;
baseModuleScope.clearTimeout = globalContext.clearTimeout;
baseModuleScope.URL = URL;
baseModuleScope.Worker =
require("./helpers/createFakeWorker")({
Expand Down

0 comments on commit f1ea815

Please sign in to comment.