Framework updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-03-04 23:20:19 +00:00
parent a89daf3d43
commit 3ed8517b2a
891 changed files with 11126 additions and 9600 deletions

View File

@@ -65,6 +65,10 @@ class CommonJsExportRequireDependency extends ModuleDependency {
return "cjs export require";
}
get category() {
return "commonjs";
}
/**
* @returns {boolean | TRANSITIVE} true, when changes to the referenced module could affect the referencing module; TRANSITIVE, when changes to the referenced module could affect referencing modules of the referencing module
*/

View File

@@ -5,13 +5,9 @@
"use strict";
const { fileURLToPath } = require("url");
const CommentCompilationWarning = require("../CommentCompilationWarning");
const RuntimeGlobals = require("../RuntimeGlobals");
const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
const WebpackError = require("../WebpackError");
const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression");
const { VariableInfo } = require("../javascript/JavascriptParser");
const {
evaluateToIdentifier,
evaluateToString,
@@ -36,6 +32,7 @@ const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency
/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
/** @typedef {import("../javascript/JavascriptParser").ImportSource} ImportSource */
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
/** @typedef {import("../javascript/JavascriptParser").Members} Members */
@@ -48,11 +45,300 @@ const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency
* @property {string} context
*/
const createRequireSpecifierTag = Symbol("createRequire");
const createdRequireIdentifierTag = Symbol("createRequire()");
const PLUGIN_NAME = "CommonJsImportsParserPlugin";
/**
* @param {JavascriptParser} parser parser
* @returns {(expr: Expression) => boolean} handler
*/
const createRequireCacheDependency = (parser) =>
toConstantDependency(parser, RuntimeGlobals.moduleCache, [
RuntimeGlobals.moduleCache,
RuntimeGlobals.moduleId,
RuntimeGlobals.moduleLoaded
]);
/**
* @param {JavascriptParser} parser parser
* @param {JavascriptParserOptions} options options
* @param {() => undefined | string} getContext context accessor
* @returns {(expr: Expression) => boolean} handler
*/
const createRequireAsExpressionHandler =
(parser, options, getContext) => (expr) => {
const dep = new CommonJsRequireContextDependency(
{
request: /** @type {string} */ (options.unknownContextRequest),
recursive: /** @type {boolean} */ (options.unknownContextRecursive),
regExp: /** @type {RegExp} */ (options.unknownContextRegExp),
mode: "sync"
},
/** @type {Range} */ (expr.range),
undefined,
parser.scope.inShorthand,
getContext()
);
dep.critical =
options.unknownContextCritical &&
"require function is used in a way in which dependencies cannot be statically extracted";
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.optional = Boolean(parser.scope.inTry);
parser.state.current.addDependency(dep);
return true;
};
/**
* @param {JavascriptParser} parser parser
* @param {JavascriptParserOptions} options options
* @param {() => undefined | string} getContext context accessor
* @returns {(callNew: boolean) => (expr: CallExpression | NewExpression) => (boolean | void)} handler factory
*/
const createRequireCallHandler = (parser, options, getContext) => {
/**
* @param {CallExpression | NewExpression} expr expression
* @param {BasicEvaluatedExpression} param param
* @returns {boolean | void} true when handled
*/
const processRequireItem = (expr, param) => {
if (param.isString()) {
const dep = new CommonJsRequireDependency(
/** @type {string} */ (param.string),
/** @type {Range} */ (param.range),
getContext()
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.optional = Boolean(parser.scope.inTry);
parser.state.current.addDependency(dep);
return true;
}
};
/**
* @param {CallExpression | NewExpression} expr expression
* @param {BasicEvaluatedExpression} param param
* @returns {boolean | void} true when handled
*/
const processRequireContext = (expr, param) => {
const dep = ContextDependencyHelpers.create(
CommonJsRequireContextDependency,
/** @type {Range} */ (expr.range),
param,
expr,
options,
{
category: "commonjs"
},
parser,
undefined,
getContext()
);
if (!dep) return;
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.optional = Boolean(parser.scope.inTry);
parser.state.current.addDependency(dep);
return true;
};
return (callNew) => (expr) => {
if (options.commonjsMagicComments) {
const { options: requireOptions, errors: commentErrors } =
parser.parseCommentOptions(/** @type {Range} */ (expr.range));
if (commentErrors) {
for (const e of commentErrors) {
const { comment } = e;
parser.state.module.addWarning(
new CommentCompilationWarning(
`Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
/** @type {DependencyLocation} */ (comment.loc)
)
);
}
}
if (requireOptions && requireOptions.webpackIgnore !== undefined) {
if (typeof requireOptions.webpackIgnore !== "boolean") {
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`,
/** @type {DependencyLocation} */ (expr.loc)
)
);
} else if (requireOptions.webpackIgnore) {
// Do not instrument `require()` if `webpackIgnore` is `true`
return true;
}
}
}
if (expr.arguments.length !== 1) return;
/** @type {null | LocalModule} */
let localModule;
const param = parser.evaluateExpression(expr.arguments[0]);
if (param.isConditional()) {
let isExpression = false;
for (const p of /** @type {BasicEvaluatedExpression[]} */ (
param.options
)) {
const result = processRequireItem(expr, p);
if (result === undefined) {
isExpression = true;
}
}
if (!isExpression) {
const dep = new RequireHeaderDependency(
/** @type {Range} */ (expr.callee.range)
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
return true;
}
}
if (
param.isString() &&
(localModule = getLocalModule(
parser.state,
/** @type {string} */ (param.string)
))
) {
localModule.flagUsed();
const dep = new LocalModuleDependency(
localModule,
/** @type {Range} */ (expr.range),
callNew
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
} else {
const result = processRequireItem(expr, param);
if (result === undefined) {
processRequireContext(expr, param);
} else {
const dep = new RequireHeaderDependency(
/** @type {Range} */ (expr.callee.range)
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
}
}
return true;
};
};
/**
* @param {JavascriptParser} parser parser
* @param {JavascriptParserOptions} options options
* @param {() => undefined | string} getContext context accessor
* @returns {(expr: CallExpression, weak: boolean) => (boolean | void)} resolver
*/
const createProcessResolveHandler = (parser, options, getContext) => {
/**
* @param {CallExpression} expr call expression
* @param {BasicEvaluatedExpression} param param
* @param {boolean} weak weak
* @returns {boolean | void} true when handled
*/
const processResolveItem = (expr, param, weak) => {
if (param.isString()) {
const dep = new RequireResolveDependency(
/** @type {string} */ (param.string),
/** @type {Range} */ (param.range),
getContext()
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.optional = Boolean(parser.scope.inTry);
dep.weak = weak;
parser.state.current.addDependency(dep);
return true;
}
};
/**
* @param {CallExpression} expr call expression
* @param {BasicEvaluatedExpression} param param
* @param {boolean} weak weak
* @returns {boolean | void} true when handled
*/
const processResolveContext = (expr, param, weak) => {
const dep = ContextDependencyHelpers.create(
RequireResolveContextDependency,
/** @type {Range} */ (param.range),
param,
expr,
options,
{
category: "commonjs",
mode: weak ? "weak" : "sync"
},
parser,
getContext()
);
if (!dep) return;
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.optional = Boolean(parser.scope.inTry);
parser.state.current.addDependency(dep);
return true;
};
return (expr, weak) => {
if (!weak && options.commonjsMagicComments) {
const { options: requireOptions, errors: commentErrors } =
parser.parseCommentOptions(/** @type {Range} */ (expr.range));
if (commentErrors) {
for (const e of commentErrors) {
const { comment } = e;
parser.state.module.addWarning(
new CommentCompilationWarning(
`Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
/** @type {DependencyLocation} */ (comment.loc)
)
);
}
}
if (requireOptions && requireOptions.webpackIgnore !== undefined) {
if (typeof requireOptions.webpackIgnore !== "boolean") {
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`,
/** @type {DependencyLocation} */ (expr.loc)
)
);
} else if (requireOptions.webpackIgnore) {
// Do not instrument `require()` if `webpackIgnore` is `true`
return true;
}
}
}
if (expr.arguments.length !== 1) return;
const param = parser.evaluateExpression(expr.arguments[0]);
if (param.isConditional()) {
for (const option of /** @type {BasicEvaluatedExpression[]} */ (
param.options
)) {
const result = processResolveItem(expr, option, weak);
if (result === undefined) {
processResolveContext(expr, option, weak);
}
}
const dep = new RequireResolveHeaderDependency(
/** @type {Range} */ (expr.callee.range)
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
return true;
}
const result = processResolveItem(expr, param, weak);
if (result === undefined) {
processResolveContext(expr, param, weak);
}
const dep = new RequireResolveHeaderDependency(
/** @type {Range} */ (expr.callee.range)
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
return true;
};
};
class CommonJsImportsParserPlugin {
/**
* @param {JavascriptParserOptions} options parser options
@@ -98,20 +384,6 @@ class CommonJsImportsParserPlugin {
evaluateToIdentifier(expression, "require", getMembers, true)
);
};
/**
* @param {string | symbol} tag tag
*/
const tapRequireExpressionTag = (tag) => {
parser.hooks.typeof
.for(tag)
.tap(
PLUGIN_NAME,
toConstantDependency(parser, JSON.stringify("function"))
);
parser.hooks.evaluateTypeof
.for(tag)
.tap(PLUGIN_NAME, evaluateToString("function"));
};
tapRequireExpression("require", () => []);
tapRequireExpression("require.resolve", () => ["resolve"]);
tapRequireExpression("require.resolveWeak", () => ["resolveWeak"]);
@@ -176,15 +448,7 @@ class CommonJsImportsParserPlugin {
// #endregion
// #region Inspection
const requireCache = toConstantDependency(
parser,
RuntimeGlobals.moduleCache,
[
RuntimeGlobals.moduleCache,
RuntimeGlobals.moduleId,
RuntimeGlobals.moduleLoaded
]
);
const requireCache = createRequireCacheDependency(parser);
parser.hooks.expression.for("require.cache").tap(PLUGIN_NAME, requireCache);
// #endregion
@@ -194,163 +458,26 @@ class CommonJsImportsParserPlugin {
* @param {Expression} expr expression
* @returns {boolean} true when handled
*/
const requireAsExpressionHandler = (expr) => {
const dep = new CommonJsRequireContextDependency(
{
request: /** @type {string} */ (options.unknownContextRequest),
recursive: /** @type {boolean} */ (options.unknownContextRecursive),
regExp: /** @type {RegExp} */ (options.unknownContextRegExp),
mode: "sync"
},
/** @type {Range} */ (expr.range),
undefined,
parser.scope.inShorthand,
getContext()
);
dep.critical =
options.unknownContextCritical &&
"require function is used in a way in which dependencies cannot be statically extracted";
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.optional = Boolean(parser.scope.inTry);
parser.state.current.addDependency(dep);
return true;
};
const requireAsExpressionHandler = createRequireAsExpressionHandler(
parser,
options,
getContext
);
parser.hooks.expression
.for("require")
.tap(PLUGIN_NAME, requireAsExpressionHandler);
// #endregion
// #region Require
/**
* @param {CallExpression | NewExpression} expr expression
* @param {BasicEvaluatedExpression} param param
* @returns {boolean | void} true when handled
*/
const processRequireItem = (expr, param) => {
if (param.isString()) {
const dep = new CommonJsRequireDependency(
/** @type {string} */ (param.string),
/** @type {Range} */ (param.range),
getContext()
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.optional = Boolean(parser.scope.inTry);
parser.state.current.addDependency(dep);
return true;
}
};
/**
* @param {CallExpression | NewExpression} expr expression
* @param {BasicEvaluatedExpression} param param
* @returns {boolean | void} true when handled
*/
const processRequireContext = (expr, param) => {
const dep = ContextDependencyHelpers.create(
CommonJsRequireContextDependency,
/** @type {Range} */ (expr.range),
param,
expr,
options,
{
category: "commonjs"
},
parser,
undefined,
getContext()
);
if (!dep) return;
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.optional = Boolean(parser.scope.inTry);
parser.state.current.addDependency(dep);
return true;
};
/**
* @param {boolean} callNew true, when require is called with new
* @returns {(expr: CallExpression | NewExpression) => (boolean | void)} handler
*/
const createRequireHandler = (callNew) => (expr) => {
if (options.commonjsMagicComments) {
const { options: requireOptions, errors: commentErrors } =
parser.parseCommentOptions(/** @type {Range} */ (expr.range));
if (commentErrors) {
for (const e of commentErrors) {
const { comment } = e;
parser.state.module.addWarning(
new CommentCompilationWarning(
`Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
/** @type {DependencyLocation} */ (comment.loc)
)
);
}
}
if (requireOptions && requireOptions.webpackIgnore !== undefined) {
if (typeof requireOptions.webpackIgnore !== "boolean") {
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`,
/** @type {DependencyLocation} */ (expr.loc)
)
);
} else if (requireOptions.webpackIgnore) {
// Do not instrument `require()` if `webpackIgnore` is `true`
return true;
}
}
}
if (expr.arguments.length !== 1) return;
/** @type {null | LocalModule} */
let localModule;
const param = parser.evaluateExpression(expr.arguments[0]);
if (param.isConditional()) {
let isExpression = false;
for (const p of /** @type {BasicEvaluatedExpression[]} */ (
param.options
)) {
const result = processRequireItem(expr, p);
if (result === undefined) {
isExpression = true;
}
}
if (!isExpression) {
const dep = new RequireHeaderDependency(
/** @type {Range} */ (expr.callee.range)
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
return true;
}
}
if (
param.isString() &&
(localModule = getLocalModule(
parser.state,
/** @type {string} */ (param.string)
))
) {
localModule.flagUsed();
const dep = new LocalModuleDependency(
localModule,
/** @type {Range} */ (expr.range),
callNew
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
} else {
const result = processRequireItem(expr, param);
if (result === undefined) {
processRequireContext(expr, param);
} else {
const dep = new RequireHeaderDependency(
/** @type {Range} */ (expr.callee.range)
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
}
}
return true;
};
const createRequireHandler = createRequireCallHandler(
parser,
options,
getContext
);
parser.hooks.call
.for("require")
.tap(PLUGIN_NAME, createRequireHandler(false));
@@ -460,112 +587,11 @@ class CommonJsImportsParserPlugin {
* @param {boolean} weak weak
* @returns {boolean | void} true when handled
*/
const processResolve = (expr, weak) => {
if (!weak && options.commonjsMagicComments) {
const { options: requireOptions, errors: commentErrors } =
parser.parseCommentOptions(/** @type {Range} */ (expr.range));
if (commentErrors) {
for (const e of commentErrors) {
const { comment } = e;
parser.state.module.addWarning(
new CommentCompilationWarning(
`Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
/** @type {DependencyLocation} */ (comment.loc)
)
);
}
}
if (requireOptions && requireOptions.webpackIgnore !== undefined) {
if (typeof requireOptions.webpackIgnore !== "boolean") {
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`,
/** @type {DependencyLocation} */ (expr.loc)
)
);
} else if (requireOptions.webpackIgnore) {
// Do not instrument `require()` if `webpackIgnore` is `true`
return true;
}
}
}
if (expr.arguments.length !== 1) return;
const param = parser.evaluateExpression(expr.arguments[0]);
if (param.isConditional()) {
for (const option of /** @type {BasicEvaluatedExpression[]} */ (
param.options
)) {
const result = processResolveItem(expr, option, weak);
if (result === undefined) {
processResolveContext(expr, option, weak);
}
}
const dep = new RequireResolveHeaderDependency(
/** @type {Range} */ (expr.callee.range)
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
return true;
}
const result = processResolveItem(expr, param, weak);
if (result === undefined) {
processResolveContext(expr, param, weak);
}
const dep = new RequireResolveHeaderDependency(
/** @type {Range} */ (expr.callee.range)
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
return true;
};
/**
* @param {CallExpression} expr call expression
* @param {BasicEvaluatedExpression} param param
* @param {boolean} weak weak
* @returns {boolean | void} true when handled
*/
const processResolveItem = (expr, param, weak) => {
if (param.isString()) {
const dep = new RequireResolveDependency(
/** @type {string} */ (param.string),
/** @type {Range} */ (param.range),
getContext()
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.optional = Boolean(parser.scope.inTry);
dep.weak = weak;
parser.state.current.addDependency(dep);
return true;
}
};
/**
* @param {CallExpression} expr call expression
* @param {BasicEvaluatedExpression} param param
* @param {boolean} weak weak
* @returns {boolean | void} true when handled
*/
const processResolveContext = (expr, param, weak) => {
const dep = ContextDependencyHelpers.create(
RequireResolveContextDependency,
/** @type {Range} */ (param.range),
param,
expr,
options,
{
category: "commonjs",
mode: weak ? "weak" : "sync"
},
parser,
getContext()
);
if (!dep) return;
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.optional = Boolean(parser.scope.inTry);
parser.state.current.addDependency(dep);
return true;
};
const processResolve = createProcessResolveHandler(
parser,
options,
getContext
);
parser.hooks.call
.for("require.resolve")
@@ -574,232 +600,12 @@ class CommonJsImportsParserPlugin {
.for("require.resolveWeak")
.tap(PLUGIN_NAME, (expr) => processResolve(expr, true));
// #endregion
// #region Create require
if (!options.createRequire) return;
/** @type {ImportSource[]} */
let moduleName = [];
/** @type {string | undefined} */
let specifierName;
if (options.createRequire === true) {
moduleName = ["module", "node:module"];
specifierName = "createRequire";
} else {
/** @type {undefined | string} */
let moduleName;
const match = /^(.*) from (.*)$/.exec(options.createRequire);
if (match) {
[, specifierName, moduleName] = match;
}
if (!specifierName || !moduleName) {
const err = new WebpackError(
`Parsing javascript parser option "createRequire" failed, got ${JSON.stringify(
options.createRequire
)}`
);
err.details =
'Expected string in format "createRequire from module", where "createRequire" is specifier name and "module" name of the module';
throw err;
}
}
tapRequireExpressionTag(createdRequireIdentifierTag);
tapRequireExpressionTag(createRequireSpecifierTag);
parser.hooks.evaluateCallExpression
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, (expr) => {
const context = parseCreateRequireArguments(expr);
if (context === undefined) return;
const ident = parser.evaluatedVariable({
tag: createdRequireIdentifierTag,
data: { context },
next: undefined
});
return new BasicEvaluatedExpression()
.setIdentifier(ident, ident, () => [])
.setSideEffects(false)
.setRange(/** @type {Range} */ (expr.range));
});
parser.hooks.unhandledExpressionMemberChain
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, (expr, members) =>
expressionIsUnsupported(
parser,
`createRequire().${members.join(".")} is not supported by webpack.`
)(expr)
);
parser.hooks.canRename
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, () => true);
parser.hooks.canRename
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, () => true);
parser.hooks.rename
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, defineUndefined);
parser.hooks.expression
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, requireAsExpressionHandler);
parser.hooks.call
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, createRequireHandler(false));
/**
* @param {CallExpression} expr call expression
* @returns {string | void} context
*/
const parseCreateRequireArguments = (expr) => {
const args = expr.arguments;
if (args.length !== 1) {
const err = new WebpackError(
"module.createRequire supports only one argument."
);
err.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addWarning(err);
return;
}
const arg = args[0];
const evaluated = parser.evaluateExpression(arg);
if (!evaluated.isString()) {
const err = new WebpackError(
"module.createRequire failed parsing argument."
);
err.loc = /** @type {DependencyLocation} */ (arg.loc);
parser.state.module.addWarning(err);
return;
}
const ctx = /** @type {string} */ (evaluated.string).startsWith("file://")
? fileURLToPath(/** @type {string} */ (evaluated.string))
: /** @type {string} */ (evaluated.string);
// argument always should be a filename
return ctx.slice(0, ctx.lastIndexOf(ctx.startsWith("/") ? "/" : "\\"));
};
parser.hooks.import.tap(
{
name: PLUGIN_NAME,
stage: -10
},
(statement, source) => {
if (
!moduleName.includes(source) ||
statement.specifiers.length !== 1 ||
statement.specifiers[0].type !== "ImportSpecifier" ||
statement.specifiers[0].imported.type !== "Identifier" ||
statement.specifiers[0].imported.name !== specifierName
) {
return;
}
// clear for 'import { createRequire as x } from "module"'
// if any other specifier was used import module
const clearDep = new ConstDependency(
parser.isAsiPosition(/** @type {Range} */ (statement.range)[0])
? ";"
: "",
/** @type {Range} */ (statement.range)
);
clearDep.loc = /** @type {DependencyLocation} */ (statement.loc);
parser.state.module.addPresentationalDependency(clearDep);
parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]);
return true;
}
);
parser.hooks.importSpecifier.tap(
{
name: PLUGIN_NAME,
stage: -10
},
(statement, source, id, name) => {
if (!moduleName.includes(source) || id !== specifierName) return;
parser.tagVariable(name, createRequireSpecifierTag);
return true;
}
);
parser.hooks.preDeclarator.tap(PLUGIN_NAME, (declarator) => {
if (
declarator.id.type !== "Identifier" ||
!declarator.init ||
declarator.init.type !== "CallExpression" ||
declarator.init.callee.type !== "Identifier"
) {
return;
}
const variableInfo = parser.getVariableInfo(declarator.init.callee.name);
if (
variableInfo instanceof VariableInfo &&
variableInfo.tagInfo &&
variableInfo.tagInfo.tag === createRequireSpecifierTag
) {
const context = parseCreateRequireArguments(declarator.init);
if (context === undefined) return;
parser.tagVariable(declarator.id.name, createdRequireIdentifierTag, {
name: declarator.id.name,
context
});
return true;
}
});
parser.hooks.memberChainOfCallMemberChain
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, (expr, calleeMembers, callExpr, members) => {
if (
calleeMembers.length !== 0 ||
members.length !== 1 ||
members[0] !== "cache"
) {
return;
}
// createRequire().cache
const context = parseCreateRequireArguments(callExpr);
if (context === undefined) return;
return requireCache(expr);
});
parser.hooks.callMemberChainOfCallMemberChain
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, (expr, calleeMembers, innerCallExpression, members) => {
if (
calleeMembers.length !== 0 ||
members.length !== 1 ||
members[0] !== "resolve"
) {
return;
}
// createRequire().resolve()
return processResolve(expr, false);
});
parser.hooks.expressionMemberChain
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, (expr, members) => {
// require.cache
if (members.length === 1 && members[0] === "cache") {
return requireCache(expr);
}
});
parser.hooks.callMemberChain
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, (expr, members) => {
// require.resolve()
if (members.length === 1 && members[0] === "resolve") {
return processResolve(expr, false);
}
});
parser.hooks.call
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, (expr) => {
const clearDep = new ConstDependency(
"/* createRequire() */ undefined",
/** @type {Range} */ (expr.range)
);
clearDep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(clearDep);
return true;
});
// #endregion
}
}
module.exports = CommonJsImportsParserPlugin;
module.exports.createProcessResolveHandler = createProcessResolveHandler;
module.exports.createRequireAsExpressionHandler =
createRequireAsExpressionHandler;
module.exports.createRequireCacheDependency = createRequireCacheDependency;
module.exports.createRequireHandler = createRequireCallHandler;

View File

@@ -0,0 +1,356 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { fileURLToPath } = require("url");
const WebpackError = require("../WebpackError");
const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression");
const { VariableInfo } = require("../javascript/JavascriptParser");
const {
evaluateToString,
expressionIsUnsupported,
toConstantDependency
} = require("../javascript/JavascriptParserHelpers");
const CommonJsImportsParserPlugin = require("./CommonJsImportsParserPlugin");
const ConstDependency = require("./ConstDependency");
/** @typedef {import("estree").CallExpression} CallExpression */
/** @typedef {import("estree").Expression} Expression */
/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
/** @typedef {import("../javascript/JavascriptParser").ImportSource} ImportSource */
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
/**
* @typedef {object} CommonJsImportSettings
* @property {string=} name
* @property {string} context
*/
const createRequireSpecifierTag = Symbol("createRequire");
const createdRequireIdentifierTag = Symbol("createRequire()");
const PLUGIN_NAME = "CreateRequireParserPlugin";
const {
createProcessResolveHandler,
createRequireAsExpressionHandler,
createRequireCacheDependency,
createRequireHandler
} = CommonJsImportsParserPlugin;
class CreateRequireParserPlugin {
/**
* @param {JavascriptParserOptions} options parser options
*/
constructor(options) {
this.options = options;
}
/**
* @param {JavascriptParser} parser the parser
* @returns {void}
*/
apply(parser) {
const options = this.options;
if (!options.createRequire) return;
const getContext = () => {
if (parser.currentTagData) {
const { context } =
/** @type {CommonJsImportSettings} */
(parser.currentTagData);
return context;
}
};
/**
* @param {string | symbol} tag tag
*/
const tapRequireExpressionTag = (tag) => {
parser.hooks.typeof
.for(tag)
.tap(
PLUGIN_NAME,
toConstantDependency(parser, JSON.stringify("function"))
);
parser.hooks.evaluateTypeof
.for(tag)
.tap(PLUGIN_NAME, evaluateToString("function"));
};
/**
* @param {Expression} expr expression
* @returns {boolean} true when set undefined
*/
const defineUndefined = (expr) => {
const dep = new ConstDependency(
"undefined",
/** @type {Range} */ (expr.range)
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(dep);
return false;
};
const requireCache = createRequireCacheDependency(parser);
const requireAsExpressionHandler = createRequireAsExpressionHandler(
parser,
options,
getContext
);
const createRequireCallHandler = createRequireHandler(
parser,
options,
getContext
);
const processResolve = createProcessResolveHandler(
parser,
options,
getContext
);
/** @type {ImportSource[]} */
let moduleNames = [];
/** @type {string | undefined} */
let specifierName;
if (options.createRequire === true) {
moduleNames = ["module", "node:module"];
specifierName = "createRequire";
} else if (typeof options.createRequire === "string") {
/** @type {undefined | string} */
let parsedModuleName;
const match = /^(.*) from (.*)$/.exec(options.createRequire);
if (match) {
[, specifierName, parsedModuleName] = match;
}
if (!specifierName || !parsedModuleName) {
const err = new WebpackError(
`Parsing javascript parser option "createRequire" failed, got ${JSON.stringify(
options.createRequire
)}`
);
err.details =
'Expected string in format "createRequire from module", where "createRequire" is specifier name and "module" name of the module';
throw err;
}
moduleNames = [parsedModuleName];
} else {
return;
}
/**
* @param {CallExpression} expr call expression
* @returns {string | void} context
*/
const parseCreateRequireArguments = (expr) => {
const args = expr.arguments;
if (args.length !== 1) {
const err = new WebpackError(
"module.createRequire supports only one argument."
);
err.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addWarning(err);
return;
}
const arg = args[0];
const evaluated = parser.evaluateExpression(arg);
if (!evaluated.isString()) {
const err = new WebpackError(
"module.createRequire failed parsing argument."
);
err.loc = /** @type {DependencyLocation} */ (arg.loc);
parser.state.module.addWarning(err);
return;
}
const ctx = /** @type {string} */ (evaluated.string).startsWith("file://")
? fileURLToPath(/** @type {string} */ (evaluated.string))
: /** @type {string} */ (evaluated.string);
// argument always should be a filename
return ctx.slice(0, ctx.lastIndexOf(ctx.startsWith("/") ? "/" : "\\"));
};
tapRequireExpressionTag(createdRequireIdentifierTag);
tapRequireExpressionTag(createRequireSpecifierTag);
parser.hooks.evaluateCallExpression
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, (expr) => {
const context = parseCreateRequireArguments(expr);
if (context === undefined) return;
const ident = parser.evaluatedVariable({
tag: createdRequireIdentifierTag,
data: { context },
next: undefined
});
return new BasicEvaluatedExpression()
.setIdentifier(ident, ident, () => [])
.setSideEffects(false)
.setRange(/** @type {Range} */ (expr.range));
});
parser.hooks.unhandledExpressionMemberChain
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, (expr, members) =>
expressionIsUnsupported(
parser,
`createRequire().${members.join(".")} is not supported by webpack.`
)(expr)
);
parser.hooks.canRename
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, () => true);
parser.hooks.canRename
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, () => true);
parser.hooks.rename
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, defineUndefined);
parser.hooks.expression
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, requireAsExpressionHandler);
parser.hooks.call
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, createRequireCallHandler(false));
parser.hooks.import.tap(
{
name: PLUGIN_NAME,
stage: -10
},
(statement, source) => {
if (
!moduleNames.includes(source) ||
statement.specifiers.length !== 1 ||
statement.specifiers[0].type !== "ImportSpecifier" ||
statement.specifiers[0].imported.type !== "Identifier" ||
statement.specifiers[0].imported.name !== specifierName
) {
return;
}
// clear for 'import { createRequire as x } from "module"'
// if any other specifier was used import module
const clearDep = new ConstDependency(
parser.isAsiPosition(/** @type {Range} */ (statement.range)[0])
? ";"
: "",
/** @type {Range} */ (statement.range)
);
clearDep.loc = /** @type {DependencyLocation} */ (statement.loc);
parser.state.module.addPresentationalDependency(clearDep);
parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]);
return true;
}
);
parser.hooks.importSpecifier.tap(
{
name: PLUGIN_NAME,
stage: -10
},
(statement, source, id, name) => {
if (!moduleNames.includes(source) || id !== specifierName) return;
parser.tagVariable(name, createRequireSpecifierTag);
return true;
}
);
parser.hooks.preDeclarator.tap(PLUGIN_NAME, (declarator) => {
if (
declarator.id.type !== "Identifier" ||
!declarator.init ||
declarator.init.type !== "CallExpression" ||
declarator.init.callee.type !== "Identifier"
) {
return;
}
const variableInfo = parser.getVariableInfo(declarator.init.callee.name);
if (
variableInfo instanceof VariableInfo &&
variableInfo.tagInfo &&
variableInfo.tagInfo.tag === createRequireSpecifierTag
) {
const context = parseCreateRequireArguments(declarator.init);
if (context === undefined) return;
parser.tagVariable(declarator.id.name, createdRequireIdentifierTag, {
name: declarator.id.name,
context
});
return true;
}
});
parser.hooks.memberChainOfCallMemberChain
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, (expr, calleeMembers, callExpr, members) => {
if (
calleeMembers.length !== 0 ||
members.length !== 1 ||
members[0] !== "cache"
) {
return;
}
// createRequire().cache
const context = parseCreateRequireArguments(callExpr);
if (context === undefined) return;
return requireCache(expr);
});
parser.hooks.callMemberChainOfCallMemberChain
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, (expr, calleeMembers, innerCallExpression, members) => {
if (
calleeMembers.length !== 0 ||
members.length !== 1 ||
members[0] !== "resolve"
) {
return;
}
// createRequire().resolve()
return processResolve(expr, false);
});
parser.hooks.expressionMemberChain
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, (expr, members) => {
// require.cache
if (members.length === 1 && members[0] === "cache") {
return requireCache(expr);
}
});
parser.hooks.callMemberChain
.for(createdRequireIdentifierTag)
.tap(PLUGIN_NAME, (expr, members) => {
// require.resolve()
if (members.length === 1 && members[0] === "resolve") {
return processResolve(expr, false);
}
});
parser.hooks.expression
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, (expr) => {
const clearDep = new ConstDependency(
"/* createRequire */ undefined",
/** @type {Range} */ (expr.range)
);
clearDep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(clearDep);
return true;
});
parser.hooks.call
.for(createRequireSpecifierTag)
.tap(PLUGIN_NAME, (expr) => {
const clearDep = new ConstDependency(
"/* createRequire() */ undefined",
/** @type {Range} */ (expr.range)
);
clearDep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addPresentationalDependency(clearDep);
return true;
});
}
}
module.exports = CreateRequireParserPlugin;

View File

@@ -39,14 +39,10 @@ module.exports = class HarmonyExportDependencyParserPlugin {
*/
constructor(options) {
this.options = options;
this.exportPresenceMode =
options.reexportExportsPresence !== undefined
? ExportPresenceModes.fromUserOption(options.reexportExportsPresence)
: options.exportsPresence !== undefined
? ExportPresenceModes.fromUserOption(options.exportsPresence)
: options.strictExportPresence
? ExportPresenceModes.ERROR
: ExportPresenceModes.AUTO;
this.exportPresenceMode = ExportPresenceModes.resolveFromOptions(
options.reexportExportsPresence,
options
);
}
/**

View File

@@ -53,9 +53,38 @@ const ExportPresenceModes = {
default:
throw new Error(`Invalid export presence value ${str}`);
}
},
/**
* Resolve export presence mode from parser options with a specific key and shared fallbacks.
* @param {string | false | undefined} specificValue the type-specific option value (e.g. importExportsPresence or reexportExportsPresence)
* @param {import("../../declarations/WebpackOptions").JavascriptParserOptions} options parser options
* @returns {ExportPresenceMode} resolved mode
*/
resolveFromOptions(specificValue, options) {
if (specificValue !== undefined) {
return ExportPresenceModes.fromUserOption(specificValue);
}
if (options.exportsPresence !== undefined) {
return ExportPresenceModes.fromUserOption(options.exportsPresence);
}
return options.strictExportPresence
? ExportPresenceModes.ERROR
: ExportPresenceModes.AUTO;
}
};
/**
* Get the non-optional leading part of a member chain.
* @param {string[]} members members
* @param {boolean[]} membersOptionals optionality for each member
* @returns {string[]} the non-optional prefix
*/
const getNonOptionalPart = (members, membersOptionals) => {
let i = 0;
while (i < members.length && membersOptionals[i] === false) i++;
return i !== members.length ? members.slice(0, i) : members;
};
/** @typedef {string[]} Ids */
class HarmonyImportDependency extends ModuleDependency {
@@ -427,3 +456,4 @@ HarmonyImportDependency.Template = class HarmonyImportDependencyTemplate extends
};
module.exports.ExportPresenceModes = ExportPresenceModes;
module.exports.getNonOptionalPart = getNonOptionalPart;

View File

@@ -18,12 +18,16 @@ const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency");
const HarmonyExports = require("./HarmonyExports");
const { ExportPresenceModes } = require("./HarmonyImportDependency");
const {
ExportPresenceModes,
getNonOptionalPart
} = require("./HarmonyImportDependency");
const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency");
const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency");
const { ImportPhaseUtils, createGetImportPhase } = require("./ImportPhase");
/** @typedef {import("estree").Expression} Expression */
/** @typedef {import("estree").PrivateIdentifier} PrivateIdentifier */
/** @typedef {import("estree").Identifier} Identifier */
/** @typedef {import("estree").MemberExpression} MemberExpression */
/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
@@ -63,17 +67,44 @@ const harmonySpecifierGuardTag = Symbol("harmony import guard");
const PLUGIN_NAME = "HarmonyImportDependencyParserPlugin";
/** @type {(members: Members) => string} */
const getMembersKey = (members) => members.join(".");
/**
* Strip the root binding name if needed
* @param {HarmonySettings} settings settings
* @param {Ids} ids ids
* @returns {Ids} ids for presence check
* @param {JavascriptParser} parser the parser
* @param {PrivateIdentifier | Expression} left left expression
* @param {Expression} right right expression
* @returns {{ leftPart: string, members: Members, settings: HarmonySettings } | undefined} info
*/
const getIdsForPresence = (settings, ids) =>
settings.ids.length ? ids.slice(1) : ids;
const getInOperatorHarmonyImportInfo = (parser, left, right) => {
const leftPartEvaluated = parser.evaluateExpression(left);
if (leftPartEvaluated.couldHaveSideEffects()) return;
/** @type {string | undefined} */
const leftPart = leftPartEvaluated.asString();
if (!leftPart) return;
const rightPart = parser.evaluateExpression(right);
if (!rightPart.isIdentifier()) return;
const rootInfo = rightPart.rootInfo;
const root =
typeof rootInfo === "string"
? rootInfo
: rootInfo instanceof VariableInfo
? rootInfo.name
: undefined;
if (!root) return;
const settings = /** @type {HarmonySettings | undefined} */ (
parser.getTagData(root, harmonySpecifierTag)
);
if (!settings) {
return;
}
return {
leftPart,
members: /** @type {(() => Members)} */ (rightPart.getMembers)(),
settings
};
};
module.exports = class HarmonyImportDependencyParserPlugin {
/**
@@ -82,23 +113,23 @@ module.exports = class HarmonyImportDependencyParserPlugin {
constructor(options) {
this.options = options;
/** @type {ExportPresenceMode} */
this.exportPresenceMode =
options.importExportsPresence !== undefined
? ExportPresenceModes.fromUserOption(options.importExportsPresence)
: options.exportsPresence !== undefined
? ExportPresenceModes.fromUserOption(options.exportsPresence)
: options.strictExportPresence
? ExportPresenceModes.ERROR
: ExportPresenceModes.AUTO;
this.exportPresenceMode = ExportPresenceModes.resolveFromOptions(
options.importExportsPresence,
options
);
this.strictThisContextOnImports = options.strictThisContextOnImports;
}
/**
* @param {JavascriptParser} parser the parser
* @param {HarmonySettings} settings settings
* @param {Ids} ids ids
* @returns {ExportPresenceMode} exportPresenceMode
*/
getExportPresenceMode(parser, ids) {
getExportPresenceMode(parser, settings, ids) {
// Guards only apply to namespace imports
if (settings.ids.length) return this.exportPresenceMode;
const harmonySettings = /** @type {HarmonySettings=} */ (
parser.currentTagData
);
@@ -107,9 +138,12 @@ module.exports = class HarmonyImportDependencyParserPlugin {
const data = /** @type {HarmonySpecifierGuards=} */ (
parser.getTagData(harmonySettings.name, harmonySpecifierGuardTag)
);
return data && data.guards && data.guards.has(getMembersKey(ids))
? ExportPresenceModes.NONE
: this.exportPresenceMode;
if (data && data.guards && data.guards.has(ids[0])) {
return ExportPresenceModes.NONE;
}
return this.exportPresenceMode;
}
/**
@@ -119,17 +153,6 @@ module.exports = class HarmonyImportDependencyParserPlugin {
apply(parser) {
const getImportPhase = createGetImportPhase(this.options.deferImport);
/**
* @param {Members} members members
* @param {MembersOptionals} membersOptionals members Optionals
* @returns {Ids} a non optional part
*/
function getNonOptionalPart(members, membersOptionals) {
let i = 0;
while (i < members.length && membersOptionals[i] === false) i++;
return i !== members.length ? members.slice(0, i) : members;
}
/**
* @param {MemberExpression} node member expression
* @param {number} count count
@@ -207,31 +230,14 @@ module.exports = class HarmonyImportDependencyParserPlugin {
);
parser.hooks.binaryExpression.tap(PLUGIN_NAME, (expression) => {
if (expression.operator !== "in") return;
const info = getInOperatorHarmonyImportInfo(
parser,
expression.left,
expression.right
);
if (!info) return;
const leftPartEvaluated = parser.evaluateExpression(expression.left);
if (leftPartEvaluated.couldHaveSideEffects()) return;
/** @type {string | undefined} */
const leftPart = leftPartEvaluated.asString();
if (!leftPart) return;
const rightPart = parser.evaluateExpression(expression.right);
if (!rightPart.isIdentifier()) return;
const rootInfo = rightPart.rootInfo;
if (
typeof rootInfo === "string" ||
!rootInfo ||
!rootInfo.tagInfo ||
rootInfo.tagInfo.tag !== harmonySpecifierTag
) {
return;
}
const settings =
/** @type {HarmonySettings} */
(rootInfo.tagInfo.data);
const members =
/** @type {(() => Members)} */
(rightPart.getMembers)();
const { leftPart, members, settings } = info;
const dep = new HarmonyEvaluatedImportSpecifierDependency(
settings.source,
settings.sourceOrder,
@@ -276,10 +282,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
settings.name,
/** @type {Range} */
(expr.range),
this.getExportPresenceMode(
parser,
getIdsForPresence(settings, settings.ids)
),
this.exportPresenceMode,
settings.phase,
settings.attributes,
[]
@@ -329,10 +332,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
settings.name,
/** @type {Range} */
(expr.range),
this.getExportPresenceMode(
parser,
getIdsForPresence(settings, ids)
),
this.getExportPresenceMode(parser, settings, ids),
settings.phase,
settings.attributes,
ranges
@@ -382,10 +382,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
ids,
settings.name,
/** @type {Range} */ (expr.range),
this.getExportPresenceMode(
parser,
getIdsForPresence(settings, ids)
),
this.getExportPresenceMode(parser, settings, ids),
settings.phase,
settings.attributes,
ranges
@@ -453,160 +450,6 @@ module.exports = class HarmonyImportDependencyParserPlugin {
}
});
/**
* @param {Expression} expression expression
* @returns {{ root: string, members: Members } | undefined} info
*/
const getHarmonyImportInfo = (expression) => {
const nameInfo = parser.getNameForExpression(expression);
if (!nameInfo) return;
const rootInfo = nameInfo.rootInfo;
const root =
typeof rootInfo === "string"
? rootInfo
: rootInfo instanceof VariableInfo
? rootInfo.name
: undefined;
if (!root) return;
if (!parser.getTagData(root, harmonySpecifierTag)) return;
return { root, members: nameInfo.getMembers() };
};
/**
* @param {Guards} guards guards
* @param {string} root root name
* @param {Members} members members
*/
const addToGuards = (guards, root, members) => {
const membersKey = getMembersKey(members);
const guardedMembers = guards.get(root);
if (guardedMembers) {
guardedMembers.add(membersKey);
return;
}
guards.set(
root,
// Adding `foo.bar` implies guarding `foo` as well
membersKey === "" ? new Set([""]) : new Set([membersKey, ""])
);
};
/**
* @param {Expression} expression expression
* @param {Guards} guards guards
* @param {boolean} needTruthy need to be truthy
*/
const collect = (expression, guards, needTruthy) => {
// !foo
if (
expression.type === "UnaryExpression" &&
expression.operator === "!"
) {
collect(expression.argument, guards, !needTruthy);
return;
} else if (expression.type === "LogicalExpression" && needTruthy) {
// foo && bar
if (expression.operator === "&&") {
collect(expression.left, guards, true);
collect(expression.right, guards, true);
}
// falsy || foo
else if (expression.operator === "||") {
const leftEvaluation = parser.evaluateExpression(expression.left);
const leftBool = leftEvaluation.asBool();
if (leftBool === false) {
collect(expression.right, guards, true);
}
}
// nullish ?? foo
else if (expression.operator === "??") {
const leftEvaluation = parser.evaluateExpression(expression.left);
const leftNullish = leftEvaluation.asNullish();
if (leftNullish === true) {
collect(expression.right, guards, true);
}
}
return;
}
if (!needTruthy) return;
/**
* @param {Expression} targetExpression expression
* @returns {boolean} is added
*/
const addGuardForExpression = (targetExpression) => {
const info = getHarmonyImportInfo(targetExpression);
if (!info) return false;
addToGuards(guards, info.root, info.members);
return true;
};
/**
* @param {Expression} left left expression
* @param {Expression} right right expression
* @param {(evaluation: ReturnType<JavascriptParser["evaluateExpression"]>) => boolean} matcher matcher
* @returns {boolean} is added
*/
const addGuardForNullishCompare = (left, right, matcher) => {
const leftEval = parser.evaluateExpression(left);
if (leftEval && matcher(leftEval)) {
return addGuardForExpression(right);
}
const rightEval = parser.evaluateExpression(right);
if (rightEval && matcher(rightEval)) {
return addGuardForExpression(/** @type {Expression} */ (left));
}
return false;
};
if (expression.type === "BinaryExpression") {
// "bar" in foo
if (expression.operator === "in") {
const leftEvaluation = parser.evaluateExpression(expression.left);
if (leftEvaluation.couldHaveSideEffects()) return;
const propertyName = leftEvaluation.asString();
if (!propertyName) return;
parser.evaluateExpression(expression.right);
const info = getHarmonyImportInfo(expression.right);
if (!info) return;
if (info.members.length) {
for (const member of info.members) {
addToGuards(guards, info.root, [member]);
}
}
addToGuards(guards, info.root, [...info.members, propertyName]);
return;
}
// foo !== undefined
else if (
expression.operator === "!==" &&
addGuardForNullishCompare(
/** @type {Expression} */ (expression.left),
expression.right,
(evaluation) => evaluation.isUndefined()
)
) {
return;
}
// foo != undefined
// foo != null
else if (
expression.operator === "!=" &&
addGuardForNullishCompare(
/** @type {Expression} */ (expression.left),
expression.right,
(evaluation) => Boolean(evaluation.asNullish())
)
) {
return;
}
}
addGuardForExpression(expression);
};
/**
* @param {Guards} guards guards
* @param {() => void} walk walk callback
@@ -657,7 +500,68 @@ module.exports = class HarmonyImportDependencyParserPlugin {
if (parser.scope.isAsmJs) return;
/** @type {Guards} */
const guards = new Map();
collect(expression, guards, true);
/**
* @param {Expression} expression expression
* @param {boolean} needTruthy need to be truthy
*/
const collect = (expression, needTruthy) => {
if (
expression.type === "UnaryExpression" &&
expression.operator === "!"
) {
collect(expression.argument, !needTruthy);
return;
} else if (expression.type === "LogicalExpression" && needTruthy) {
if (expression.operator === "&&") {
collect(expression.left, true);
collect(expression.right, true);
} else if (expression.operator === "||") {
const leftEvaluation = parser.evaluateExpression(expression.left);
const leftBool = leftEvaluation.asBool();
if (leftBool === false) {
collect(expression.right, true);
}
} else if (expression.operator === "??") {
const leftEvaluation = parser.evaluateExpression(expression.left);
const leftNullish = leftEvaluation.asNullish();
if (leftNullish === true) {
collect(expression.right, true);
}
}
return;
}
if (!needTruthy) return;
// Direct `"x" in ns` guards
if (
expression.type === "BinaryExpression" &&
expression.operator === "in"
) {
if (expression.right.type !== "Identifier") {
return;
}
const info = getInOperatorHarmonyImportInfo(
parser,
expression.left,
expression.right
);
if (!info) return;
const { settings, leftPart, members } = info;
// Only direct namespace guards
if (members.length > 0) return;
const guarded = guards.get(settings.name);
if (guarded) {
guarded.add(leftPart);
return;
}
guards.set(settings.name, new Set([leftPart]));
}
};
collect(expression, true);
if (guards.size === 0) return;
return (walk) => {

View File

@@ -5,6 +5,7 @@
"use strict";
const { JAVASCRIPT_TYPE } = require("../ModuleSourceTypeConstants");
const makeSerializable = require("../util/makeSerializable");
const HarmonyImportDependency = require("./HarmonyImportDependency");
@@ -72,11 +73,16 @@ HarmonyImportSideEffectDependency.Template = class HarmonyImportSideEffectDepend
*/
apply(dependency, source, templateContext) {
const { moduleGraph, concatenationScope } = templateContext;
if (concatenationScope) {
const module = /** @type {Module} */ (moduleGraph.getModule(dependency));
if (concatenationScope.isModuleInScope(module)) {
return;
}
const module = /** @type {Module} */ (moduleGraph.getModule(dependency));
if (module && !module.getSourceBasicTypes().has(JAVASCRIPT_TYPE)) {
// no need to render import
return;
}
if (concatenationScope && concatenationScope.isModuleInScope(module)) {
return;
}
super.apply(dependency, source, templateContext);
}

View File

@@ -9,6 +9,7 @@ const {
JAVASCRIPT_MODULE_TYPE_AUTO,
JAVASCRIPT_MODULE_TYPE_ESM
} = require("../ModuleTypeConstants");
const CreateRequireParserPlugin = require("./CreateRequireParserPlugin");
const HarmonyAcceptDependency = require("./HarmonyAcceptDependency");
const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency");
const HarmonyCompatibilityDependency = require("./HarmonyCompatibilityDependency");
@@ -138,6 +139,9 @@ class HarmonyModulesPlugin {
new HarmonyImportDependencyParserPlugin(parserOptions).apply(parser);
new HarmonyExportDependencyParserPlugin(parserOptions).apply(parser);
new HarmonyTopLevelThisParserPlugin().apply(parser);
if (parserOptions.createRequire) {
new CreateRequireParserPlugin(parserOptions).apply(parser);
}
};
normalModuleFactory.hooks.parser

View File

@@ -14,6 +14,7 @@ const {
} = require("../javascript/JavascriptParser");
const traverseDestructuringAssignmentProperties = require("../util/traverseDestructuringAssignmentProperties");
const ContextDependencyHelpers = require("./ContextDependencyHelpers");
const { getNonOptionalPart } = require("./HarmonyImportDependency");
const ImportContextDependency = require("./ImportContextDependency");
const ImportDependency = require("./ImportDependency");
const ImportEagerDependency = require("./ImportEagerDependency");
@@ -178,17 +179,6 @@ class ImportParserPlugin {
* @returns {void}
*/
apply(parser) {
/**
* @param {Members} members members
* @param {MembersOptionals} membersOptionals members Optionals
* @returns {string[]} a non optional part
*/
function getNonOptionalPart(members, membersOptionals) {
let i = 0;
while (i < members.length && membersOptionals[i] === false) i++;
return i !== members.length ? members.slice(0, i) : members;
}
parser.hooks.collectDestructuringAssignmentProperties.tap(
PLUGIN_NAME,
(expr) => {

View File

@@ -27,12 +27,16 @@ const ImportPhase = Object.freeze({
/**
* @typedef {object} ImportPhaseUtils
* @property {(phase: ImportPhaseType) => boolean} isEvaluation true if phase is evaluation
* @property {(phase: ImportPhaseType) => boolean} isDefer true if phase is defer
* @property {(phase: ImportPhaseType) => boolean} isSource true if phase is source
*/
/** @type {ImportPhaseUtils} */
const ImportPhaseUtils = {
isEvaluation(phase) {
return phase === ImportPhase.Evaluation;
},
isDefer(phase) {
return phase === ImportPhase.Defer;
},