| | |
| | | |
| | | "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, |
| | | expressionIsUnsupported, |
| | | toConstantDependency |
| | | } = require("../javascript/JavascriptParserHelpers"); |
| | | const traverseDestructuringAssignmentProperties = require("../util/traverseDestructuringAssignmentProperties"); |
| | | const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency"); |
| | | const CommonJsRequireContextDependency = require("./CommonJsRequireContextDependency"); |
| | | const CommonJsRequireDependency = require("./CommonJsRequireDependency"); |
| | |
| | | /** @typedef {import("estree").NewExpression} NewExpression */ |
| | | /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ |
| | | /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ |
| | | /** @typedef {import("../Dependency").RawReferencedExports} RawReferencedExports */ |
| | | /** @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 */ |
| | | /** @typedef {import("../javascript/JavascriptParser").CalleeMembers} CalleeMembers */ |
| | | /** @typedef {import("./LocalModule")} LocalModule */ |
| | | |
| | | /** |
| | | * Defines the common js import settings type used by this module. |
| | | * @typedef {object} CommonJsImportSettings |
| | | * @property {string=} name |
| | | * @property {string} context |
| | | */ |
| | | |
| | | const createRequireSpecifierTag = Symbol("createRequire"); |
| | | const createdRequireIdentifierTag = Symbol("createRequire()"); |
| | | |
| | | const PLUGIN_NAME = "CommonJsImportsParserPlugin"; |
| | | |
| | | class CommonJsImportsParserPlugin { |
| | | /** |
| | | * @param {JavascriptParserOptions} options parser options |
| | | * Checks whether this object is require call expression. |
| | | * @param {Expression} expression expression |
| | | * @returns {boolean} true, when expression is `require(...)` or `module.require(...)` |
| | | */ |
| | | constructor(options) { |
| | | this.options = options; |
| | | const isRequireCallExpression = (expression) => { |
| | | if (expression.type !== "CallExpression") return false; |
| | | const { callee } = expression; |
| | | if (callee.type === "Identifier") { |
| | | return callee.name === "require"; |
| | | } |
| | | |
| | | /** |
| | | * @param {JavascriptParser} parser the parser |
| | | * @returns {void} |
| | | */ |
| | | apply(parser) { |
| | | const options = this.options; |
| | | const getContext = () => { |
| | | if (parser.currentTagData) { |
| | | const { context } = |
| | | /** @type {CommonJsImportSettings} */ |
| | | (parser.currentTagData); |
| | | return context; |
| | | if (callee.type === "MemberExpression" && !callee.computed) { |
| | | const object = callee.object; |
| | | const property = callee.property; |
| | | return ( |
| | | object.type === "Identifier" && |
| | | object.name === "module" && |
| | | property.type === "Identifier" && |
| | | property.name === "require" |
| | | ); |
| | | } |
| | | }; |
| | | |
| | | // #region metadata |
| | | /** |
| | | * @param {string} expression expression |
| | | * @param {() => Members} getMembers get members |
| | | */ |
| | | const tapRequireExpression = (expression, getMembers) => { |
| | | parser.hooks.typeof |
| | | .for(expression) |
| | | .tap( |
| | | PLUGIN_NAME, |
| | | toConstantDependency(parser, JSON.stringify("function")) |
| | | ); |
| | | parser.hooks.evaluateTypeof |
| | | .for(expression) |
| | | .tap(PLUGIN_NAME, evaluateToString("function")); |
| | | parser.hooks.evaluateIdentifier |
| | | .for(expression) |
| | | .tap( |
| | | PLUGIN_NAME, |
| | | 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"]); |
| | | // #endregion |
| | | |
| | | // Weird stuff // |
| | | parser.hooks.assign.for("require").tap(PLUGIN_NAME, (expr) => { |
| | | // to not leak to global "require", we need to define a local require here. |
| | | const dep = new ConstDependency("var require;", 0); |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.loc); |
| | | parser.state.module.addPresentationalDependency(dep); |
| | | return true; |
| | | }); |
| | | |
| | | // #region Unsupported |
| | | parser.hooks.call |
| | | .for("require.main.require") |
| | | .tap( |
| | | PLUGIN_NAME, |
| | | expressionIsUnsupported( |
| | | parser, |
| | | "require.main.require is not supported by webpack." |
| | | ) |
| | | ); |
| | | parser.hooks.expression |
| | | .for("module.parent.require") |
| | | .tap( |
| | | PLUGIN_NAME, |
| | | expressionIsUnsupported( |
| | | parser, |
| | | "module.parent.require is not supported by webpack." |
| | | ) |
| | | ); |
| | | parser.hooks.call |
| | | .for("module.parent.require") |
| | | .tap( |
| | | PLUGIN_NAME, |
| | | expressionIsUnsupported( |
| | | parser, |
| | | "module.parent.require is not supported by webpack." |
| | | ) |
| | | ); |
| | | // #endregion |
| | | |
| | | // #region Renaming |
| | | /** |
| | | * @param {Expression} expr expression |
| | | * @returns {boolean} true when set undefined |
| | | */ |
| | | const defineUndefined = (expr) => { |
| | | // To avoid "not defined" error, replace the value with undefined |
| | | const dep = new ConstDependency( |
| | | "undefined", |
| | | /** @type {Range} */ (expr.range) |
| | | ); |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.loc); |
| | | parser.state.module.addPresentationalDependency(dep); |
| | | return false; |
| | | }; |
| | | parser.hooks.canRename.for("require").tap(PLUGIN_NAME, () => true); |
| | | parser.hooks.rename.for("require").tap(PLUGIN_NAME, defineUndefined); |
| | | // #endregion |
| | | |
| | | // #region Inspection |
| | | const requireCache = toConstantDependency( |
| | | parser, |
| | | RuntimeGlobals.moduleCache, |
| | | [ |
| | | /** |
| | | * Gets require referenced exports from destructuring. |
| | | * @param {JavascriptParser} parser parser |
| | | * @param {CallExpression | NewExpression} expr expression |
| | | * @returns {RawReferencedExports | null} referenced exports from destructuring |
| | | */ |
| | | const getRequireReferencedExportsFromDestructuring = (parser, expr) => { |
| | | const referencedPropertiesInDestructuring = |
| | | parser.destructuringAssignmentPropertiesFor(expr); |
| | | if (!referencedPropertiesInDestructuring) return null; |
| | | |
| | | /** @type {RawReferencedExports} */ |
| | | const referencedExports = []; |
| | | traverseDestructuringAssignmentProperties( |
| | | referencedPropertiesInDestructuring, |
| | | (stack) => referencedExports.push(stack.map((p) => p.id)) |
| | | ); |
| | | return referencedExports; |
| | | }; |
| | | |
| | | /** |
| | | * Creates a require cache dependency. |
| | | * @param {JavascriptParser} parser parser |
| | | * @returns {(expr: Expression) => boolean} handler |
| | | */ |
| | | const createRequireCacheDependency = (parser) => |
| | | toConstantDependency(parser, RuntimeGlobals.moduleCache, [ |
| | | RuntimeGlobals.moduleCache, |
| | | RuntimeGlobals.moduleId, |
| | | RuntimeGlobals.moduleLoaded |
| | | ] |
| | | ); |
| | | ]); |
| | | |
| | | parser.hooks.expression.for("require.cache").tap(PLUGIN_NAME, requireCache); |
| | | // #endregion |
| | | |
| | | // #region Require as expression |
| | | /** |
| | | * @param {Expression} expr expression |
| | | * @returns {boolean} true when handled |
| | | * Creates a require as expression handler. |
| | | * @param {JavascriptParser} parser parser |
| | | * @param {JavascriptParserOptions} options options |
| | | * @param {() => undefined | string} getContext context accessor |
| | | * @returns {(expr: Expression) => boolean} handler |
| | | */ |
| | | const requireAsExpressionHandler = (expr) => { |
| | | const createRequireAsExpressionHandler = |
| | | (parser, options, getContext) => (expr) => { |
| | | const dep = new CommonJsRequireContextDependency( |
| | | { |
| | | request: /** @type {string} */ (options.unknownContextRequest), |
| | |
| | | parser.state.current.addDependency(dep); |
| | | return true; |
| | | }; |
| | | parser.hooks.expression |
| | | .for("require") |
| | | .tap(PLUGIN_NAME, requireAsExpressionHandler); |
| | | // #endregion |
| | | |
| | | // #region Require |
| | | /** |
| | | * Creates a require call handler. |
| | | * @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) => { |
| | | /** |
| | | * Process require item. |
| | | * @param {CallExpression | NewExpression} expr expression |
| | | * @param {BasicEvaluatedExpression} param param |
| | | * @returns {boolean | void} true when handled |
| | | */ |
| | | const processRequireItem = (expr, param) => { |
| | | if (param.isString()) { |
| | | const referencedExports = getRequireReferencedExportsFromDestructuring( |
| | | parser, |
| | | expr |
| | | ); |
| | | const dep = new CommonJsRequireDependency( |
| | | /** @type {string} */ (param.string), |
| | | /** @type {Range} */ (param.range), |
| | | getContext() |
| | | getContext(), |
| | | referencedExports |
| | | ); |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.loc); |
| | | dep.optional = Boolean(parser.scope.inTry); |
| | |
| | | } |
| | | }; |
| | | /** |
| | | * Process require context. |
| | | * @param {CallExpression | NewExpression} expr expression |
| | | * @param {BasicEvaluatedExpression} param param |
| | | * @returns {boolean | void} true when handled |
| | | */ |
| | | const processRequireContext = (expr, param) => { |
| | | const referencedExports = getRequireReferencedExportsFromDestructuring( |
| | | parser, |
| | | expr |
| | | ); |
| | | const dep = ContextDependencyHelpers.create( |
| | | CommonJsRequireContextDependency, |
| | | /** @type {Range} */ (expr.range), |
| | |
| | | expr, |
| | | options, |
| | | { |
| | | category: "commonjs" |
| | | category: "commonjs", |
| | | referencedExports |
| | | }, |
| | | parser, |
| | | undefined, |
| | |
| | | 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) => { |
| | | |
| | | return (callNew) => (expr) => { |
| | | if (options.commonjsMagicComments) { |
| | | const { options: requireOptions, errors: commentErrors } = |
| | | parser.parseCommentOptions(/** @type {Range} */ (expr.range)); |
| | |
| | | } |
| | | |
| | | if (expr.arguments.length !== 1) return; |
| | | /** @type {null | LocalModule} */ |
| | | let localModule; |
| | | const param = parser.evaluateExpression(expr.arguments[0]); |
| | | if (param.isConditional()) { |
| | |
| | | } |
| | | return true; |
| | | }; |
| | | parser.hooks.call |
| | | .for("require") |
| | | .tap(PLUGIN_NAME, createRequireHandler(false)); |
| | | parser.hooks.new |
| | | .for("require") |
| | | .tap(PLUGIN_NAME, createRequireHandler(true)); |
| | | parser.hooks.call |
| | | .for("module.require") |
| | | .tap(PLUGIN_NAME, createRequireHandler(false)); |
| | | parser.hooks.new |
| | | .for("module.require") |
| | | .tap(PLUGIN_NAME, createRequireHandler(true)); |
| | | // #endregion |
| | | |
| | | // #region Require with property access |
| | | /** |
| | | * @param {Expression} expr expression |
| | | * @param {CalleeMembers} calleeMembers callee members |
| | | * @param {CallExpression} callExpr call expression |
| | | * @param {Members} members members |
| | | * @param {Range[]} memberRanges member ranges |
| | | * @returns {boolean | void} true when handled |
| | | */ |
| | | const chainHandler = ( |
| | | expr, |
| | | calleeMembers, |
| | | callExpr, |
| | | members, |
| | | memberRanges |
| | | ) => { |
| | | if (callExpr.arguments.length !== 1) return; |
| | | const param = parser.evaluateExpression(callExpr.arguments[0]); |
| | | if ( |
| | | param.isString() && |
| | | !getLocalModule(parser.state, /** @type {string} */ (param.string)) |
| | | ) { |
| | | const dep = new CommonJsFullRequireDependency( |
| | | /** @type {string} */ (param.string), |
| | | /** @type {Range} */ (expr.range), |
| | | members, |
| | | /** @type {Range[]} */ memberRanges |
| | | ); |
| | | dep.asiSafe = !parser.isAsiPosition( |
| | | /** @type {Range} */ (expr.range)[0] |
| | | ); |
| | | dep.optional = Boolean(parser.scope.inTry); |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.loc); |
| | | parser.state.current.addDependency(dep); |
| | | return true; |
| | | } |
| | | }; |
| | | /** |
| | | * @param {CallExpression} expr expression |
| | | * @param {CalleeMembers} calleeMembers callee members |
| | | * @param {CallExpression} callExpr call expression |
| | | * @param {Members} members members |
| | | * @param {Range[]} memberRanges member ranges |
| | | * @returns {boolean | void} true when handled |
| | | */ |
| | | const callChainHandler = ( |
| | | expr, |
| | | calleeMembers, |
| | | callExpr, |
| | | members, |
| | | memberRanges |
| | | ) => { |
| | | if (callExpr.arguments.length !== 1) return; |
| | | const param = parser.evaluateExpression(callExpr.arguments[0]); |
| | | if ( |
| | | param.isString() && |
| | | !getLocalModule(parser.state, /** @type {string} */ (param.string)) |
| | | ) { |
| | | const dep = new CommonJsFullRequireDependency( |
| | | /** @type {string} */ (param.string), |
| | | /** @type {Range} */ (expr.callee.range), |
| | | members, |
| | | /** @type {Range[]} */ memberRanges |
| | | ); |
| | | dep.call = true; |
| | | dep.asiSafe = !parser.isAsiPosition( |
| | | /** @type {Range} */ (expr.range)[0] |
| | | ); |
| | | dep.optional = Boolean(parser.scope.inTry); |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.callee.loc); |
| | | parser.state.current.addDependency(dep); |
| | | parser.walkExpressions(expr.arguments); |
| | | return true; |
| | | } |
| | | }; |
| | | parser.hooks.memberChainOfCallMemberChain |
| | | .for("require") |
| | | .tap(PLUGIN_NAME, chainHandler); |
| | | parser.hooks.memberChainOfCallMemberChain |
| | | .for("module.require") |
| | | .tap(PLUGIN_NAME, chainHandler); |
| | | parser.hooks.callMemberChainOfCallMemberChain |
| | | .for("require") |
| | | .tap(PLUGIN_NAME, callChainHandler); |
| | | parser.hooks.callMemberChainOfCallMemberChain |
| | | .for("module.require") |
| | | .tap(PLUGIN_NAME, callChainHandler); |
| | | // #endregion |
| | | |
| | | // #region Require.resolve |
| | | /** |
| | | * Creates a process resolve handler. |
| | | * @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) => { |
| | | /** |
| | | * Process resolve item. |
| | | * @param {CallExpression} expr call expression |
| | | * @param {BasicEvaluatedExpression} param param |
| | | * @param {boolean} weak weak |
| | | * @returns {boolean | void} true when handled |
| | | */ |
| | | const processResolve = (expr, weak) => { |
| | | 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; |
| | | } |
| | | }; |
| | | /** |
| | | * Process resolve context. |
| | | * @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)); |
| | |
| | | parser.state.module.addPresentationalDependency(dep); |
| | | return true; |
| | | }; |
| | | }; |
| | | |
| | | class CommonJsImportsParserPlugin { |
| | | /** |
| | | * @param {CallExpression} expr call expression |
| | | * @param {BasicEvaluatedExpression} param param |
| | | * @param {boolean} weak weak |
| | | * @returns {boolean | void} true when handled |
| | | * Creates an instance of CommonJsImportsParserPlugin. |
| | | * @param {JavascriptParserOptions} options parser options |
| | | */ |
| | | const processResolveItem = (expr, param, weak) => { |
| | | if (param.isString()) { |
| | | const dep = new RequireResolveDependency( |
| | | /** @type {string} */ (param.string), |
| | | /** @type {Range} */ (param.range), |
| | | getContext() |
| | | constructor(options) { |
| | | this.options = options; |
| | | } |
| | | |
| | | /** |
| | | * Applies the plugin by registering its hooks on the compiler. |
| | | * @param {JavascriptParser} parser the parser |
| | | * @returns {void} |
| | | */ |
| | | apply(parser) { |
| | | const options = this.options; |
| | | parser.hooks.collectDestructuringAssignmentProperties.tap( |
| | | PLUGIN_NAME, |
| | | (expr) => { |
| | | if (isRequireCallExpression(expr)) return true; |
| | | } |
| | | ); |
| | | |
| | | const getContext = () => { |
| | | if (parser.currentTagData) { |
| | | const { context } = |
| | | /** @type {CommonJsImportSettings} */ |
| | | (parser.currentTagData); |
| | | return context; |
| | | } |
| | | }; |
| | | |
| | | // #region metadata |
| | | /** |
| | | * Tap require expression. |
| | | * @param {string} expression expression |
| | | * @param {() => Members} getMembers get members |
| | | */ |
| | | const tapRequireExpression = (expression, getMembers) => { |
| | | parser.hooks.typeof |
| | | .for(expression) |
| | | .tap( |
| | | PLUGIN_NAME, |
| | | toConstantDependency(parser, JSON.stringify("function")) |
| | | ); |
| | | parser.hooks.evaluateTypeof |
| | | .for(expression) |
| | | .tap(PLUGIN_NAME, evaluateToString("function")); |
| | | parser.hooks.evaluateIdentifier |
| | | .for(expression) |
| | | .tap( |
| | | PLUGIN_NAME, |
| | | evaluateToIdentifier(expression, "require", getMembers, true) |
| | | ); |
| | | }; |
| | | tapRequireExpression("require", () => []); |
| | | tapRequireExpression("require.resolve", () => ["resolve"]); |
| | | tapRequireExpression("require.resolveWeak", () => ["resolveWeak"]); |
| | | // #endregion |
| | | |
| | | // Weird stuff // |
| | | parser.hooks.assign.for("require").tap(PLUGIN_NAME, (expr) => { |
| | | // to not leak to global "require", we need to define a local require here. |
| | | const dep = new ConstDependency("var require;", 0); |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.loc); |
| | | parser.state.module.addPresentationalDependency(dep); |
| | | return true; |
| | | }); |
| | | |
| | | // #region Unsupported |
| | | parser.hooks.call |
| | | .for("require.main.require") |
| | | .tap( |
| | | PLUGIN_NAME, |
| | | expressionIsUnsupported( |
| | | parser, |
| | | "require.main.require is not supported by webpack." |
| | | ) |
| | | ); |
| | | parser.hooks.expression |
| | | .for("module.parent.require") |
| | | .tap( |
| | | PLUGIN_NAME, |
| | | expressionIsUnsupported( |
| | | parser, |
| | | "module.parent.require is not supported by webpack." |
| | | ) |
| | | ); |
| | | parser.hooks.call |
| | | .for("module.parent.require") |
| | | .tap( |
| | | PLUGIN_NAME, |
| | | expressionIsUnsupported( |
| | | parser, |
| | | "module.parent.require is not supported by webpack." |
| | | ) |
| | | ); |
| | | // #endregion |
| | | |
| | | // #region Renaming |
| | | /** |
| | | * Returns true when set undefined. |
| | | * @param {Expression} expr expression |
| | | * @returns {boolean} true when set undefined |
| | | */ |
| | | const defineUndefined = (expr) => { |
| | | // To avoid "not defined" error, replace the value with undefined |
| | | const dep = new ConstDependency( |
| | | "undefined", |
| | | /** @type {Range} */ (expr.range) |
| | | ); |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.loc); |
| | | parser.state.module.addPresentationalDependency(dep); |
| | | return false; |
| | | }; |
| | | parser.hooks.canRename.for("require").tap(PLUGIN_NAME, () => true); |
| | | parser.hooks.rename.for("require").tap(PLUGIN_NAME, defineUndefined); |
| | | // #endregion |
| | | |
| | | // #region Inspection |
| | | const requireCache = createRequireCacheDependency(parser); |
| | | |
| | | parser.hooks.expression.for("require.cache").tap(PLUGIN_NAME, requireCache); |
| | | // #endregion |
| | | |
| | | // #region Require as expression |
| | | /** |
| | | * Require as expression handler. |
| | | * @param {Expression} expr expression |
| | | * @returns {boolean} true when handled |
| | | */ |
| | | const requireAsExpressionHandler = createRequireAsExpressionHandler( |
| | | parser, |
| | | options, |
| | | getContext |
| | | ); |
| | | parser.hooks.expression |
| | | .for("require") |
| | | .tap(PLUGIN_NAME, requireAsExpressionHandler); |
| | | // #endregion |
| | | |
| | | // #region Require |
| | | /** |
| | | * Creates a require handler. |
| | | * @param {boolean} callNew true, when require is called with new |
| | | * @returns {(expr: CallExpression | NewExpression) => (boolean | void)} handler |
| | | */ |
| | | const createRequireHandler = createRequireCallHandler( |
| | | parser, |
| | | options, |
| | | getContext |
| | | ); |
| | | parser.hooks.call |
| | | .for("require") |
| | | .tap(PLUGIN_NAME, createRequireHandler(false)); |
| | | parser.hooks.new |
| | | .for("require") |
| | | .tap(PLUGIN_NAME, createRequireHandler(true)); |
| | | parser.hooks.call |
| | | .for("module.require") |
| | | .tap(PLUGIN_NAME, createRequireHandler(false)); |
| | | parser.hooks.new |
| | | .for("module.require") |
| | | .tap(PLUGIN_NAME, createRequireHandler(true)); |
| | | // #endregion |
| | | |
| | | // #region Require with property access |
| | | /** |
| | | * Returns true when handled. |
| | | * @param {Expression} expr expression |
| | | * @param {CalleeMembers} calleeMembers callee members |
| | | * @param {CallExpression} callExpr call expression |
| | | * @param {Members} members members |
| | | * @param {Range[]} memberRanges member ranges |
| | | * @returns {boolean | void} true when handled |
| | | */ |
| | | const chainHandler = ( |
| | | expr, |
| | | calleeMembers, |
| | | callExpr, |
| | | members, |
| | | memberRanges |
| | | ) => { |
| | | if (callExpr.arguments.length !== 1) return; |
| | | const param = parser.evaluateExpression(callExpr.arguments[0]); |
| | | if ( |
| | | param.isString() && |
| | | !getLocalModule(parser.state, /** @type {string} */ (param.string)) |
| | | ) { |
| | | const dep = new CommonJsFullRequireDependency( |
| | | /** @type {string} */ (param.string), |
| | | /** @type {Range} */ (expr.range), |
| | | members, |
| | | /** @type {Range[]} */ memberRanges |
| | | ); |
| | | dep.asiSafe = !parser.isAsiPosition( |
| | | /** @type {Range} */ (expr.range)[0] |
| | | ); |
| | | dep.optional = Boolean(parser.scope.inTry); |
| | | dep.weak = weak; |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.loc); |
| | | parser.state.current.addDependency(dep); |
| | | return true; |
| | | } |
| | | }; |
| | | /** |
| | | * Call chain handler. |
| | | * @param {CallExpression} expr expression |
| | | * @param {CalleeMembers} calleeMembers callee members |
| | | * @param {CallExpression} callExpr call expression |
| | | * @param {Members} members members |
| | | * @param {Range[]} memberRanges member ranges |
| | | * @returns {boolean | void} true when handled |
| | | */ |
| | | const callChainHandler = ( |
| | | expr, |
| | | calleeMembers, |
| | | callExpr, |
| | | members, |
| | | memberRanges |
| | | ) => { |
| | | if (callExpr.arguments.length !== 1) return; |
| | | const param = parser.evaluateExpression(callExpr.arguments[0]); |
| | | if ( |
| | | param.isString() && |
| | | !getLocalModule(parser.state, /** @type {string} */ (param.string)) |
| | | ) { |
| | | const dep = new CommonJsFullRequireDependency( |
| | | /** @type {string} */ (param.string), |
| | | /** @type {Range} */ (expr.callee.range), |
| | | members, |
| | | /** @type {Range[]} */ memberRanges |
| | | ); |
| | | dep.call = true; |
| | | dep.asiSafe = !parser.isAsiPosition( |
| | | /** @type {Range} */ (expr.range)[0] |
| | | ); |
| | | dep.optional = Boolean(parser.scope.inTry); |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.callee.loc); |
| | | parser.state.current.addDependency(dep); |
| | | parser.walkExpressions(expr.arguments); |
| | | return true; |
| | | } |
| | | }; |
| | | parser.hooks.memberChainOfCallMemberChain |
| | | .for("require") |
| | | .tap(PLUGIN_NAME, chainHandler); |
| | | parser.hooks.memberChainOfCallMemberChain |
| | | .for("module.require") |
| | | .tap(PLUGIN_NAME, chainHandler); |
| | | parser.hooks.callMemberChainOfCallMemberChain |
| | | .for("require") |
| | | .tap(PLUGIN_NAME, callChainHandler); |
| | | parser.hooks.callMemberChainOfCallMemberChain |
| | | .for("module.require") |
| | | .tap(PLUGIN_NAME, callChainHandler); |
| | | // #endregion |
| | | |
| | | // #region Require.resolve |
| | | /** |
| | | * Processes the provided expr. |
| | | * @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" |
| | | }, |
| | | const processResolve = createProcessResolveHandler( |
| | | parser, |
| | | getContext() |
| | | options, |
| | | getContext |
| | | ); |
| | | if (!dep) return; |
| | | dep.loc = /** @type {DependencyLocation} */ (expr.loc); |
| | | dep.optional = Boolean(parser.scope.inTry); |
| | | parser.state.current.addDependency(dep); |
| | | return true; |
| | | }; |
| | | |
| | | parser.hooks.call |
| | | .for("require.resolve") |
| | |
| | | .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 { |
| | | 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; |