Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: compatibility __non_webpack_require__ with ES modules #17308

Merged
merged 8 commits into from Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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(
TheLarkInn marked this conversation as resolved.
Show resolved Hide resolved
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) => {`,
TheLarkInn marked this conversation as resolved.
Show resolved Hide resolved
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