package.lib.dependencies.CommonJsImportsParserPlugin.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of webpack Show documentation
Show all versions of webpack Show documentation
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
The newest version!
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"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 {
evaluateToIdentifier,
evaluateToString,
expressionIsUnsupported,
toConstantDependency
} = require("../javascript/JavascriptParserHelpers");
const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency");
const CommonJsRequireContextDependency = require("./CommonJsRequireContextDependency");
const CommonJsRequireDependency = require("./CommonJsRequireDependency");
const ConstDependency = require("./ConstDependency");
const ContextDependencyHelpers = require("./ContextDependencyHelpers");
const LocalModuleDependency = require("./LocalModuleDependency");
const { getLocalModule } = require("./LocalModulesHelpers");
const RequireHeaderDependency = require("./RequireHeaderDependency");
const RequireResolveContextDependency = require("./RequireResolveContextDependency");
const RequireResolveDependency = require("./RequireResolveDependency");
const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency");
/** @typedef {import("estree").CallExpression} CallExpression */
/** @typedef {import("estree").Expression} Expression */
/** @typedef {import("estree").NewExpression} NewExpression */
/** @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 */
const createRequireSpecifierTag = Symbol("createRequire");
const createdRequireIdentifierTag = Symbol("createRequire()");
class CommonJsImportsParserPlugin {
/**
* @param {JavascriptParserOptions} options parser options
*/
constructor(options) {
this.options = options;
}
/**
* @param {JavascriptParser} parser the parser
* @returns {void}
*/
apply(parser) {
const options = this.options;
const getContext = () => {
if (parser.currentTagData) {
const { context } = parser.currentTagData;
return context;
}
};
//#region metadata
/**
* @param {string} expression expression
* @param {() => string[]} getMembers get members
*/
const tapRequireExpression = (expression, getMembers) => {
parser.hooks.typeof
.for(expression)
.tap(
"CommonJsImportsParserPlugin",
toConstantDependency(parser, JSON.stringify("function"))
);
parser.hooks.evaluateTypeof
.for(expression)
.tap("CommonJsImportsParserPlugin", evaluateToString("function"));
parser.hooks.evaluateIdentifier
.for(expression)
.tap(
"CommonJsImportsParserPlugin",
evaluateToIdentifier(expression, "require", getMembers, true)
);
};
/**
* @param {string | symbol} tag tag
*/
const tapRequireExpressionTag = tag => {
parser.hooks.typeof
.for(tag)
.tap(
"CommonJsImportsParserPlugin",
toConstantDependency(parser, JSON.stringify("function"))
);
parser.hooks.evaluateTypeof
.for(tag)
.tap("CommonJsImportsParserPlugin", evaluateToString("function"));
};
tapRequireExpression("require", () => []);
tapRequireExpression("require.resolve", () => ["resolve"]);
tapRequireExpression("require.resolveWeak", () => ["resolveWeak"]);
//#endregion
// Weird stuff //
parser.hooks.assign
.for("require")
.tap("CommonJsImportsParserPlugin", 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.expression
.for("require.main")
.tap(
"CommonJsImportsParserPlugin",
expressionIsUnsupported(
parser,
"require.main is not supported by webpack."
)
);
parser.hooks.call
.for("require.main.require")
.tap(
"CommonJsImportsParserPlugin",
expressionIsUnsupported(
parser,
"require.main.require is not supported by webpack."
)
);
parser.hooks.expression
.for("module.parent.require")
.tap(
"CommonJsImportsParserPlugin",
expressionIsUnsupported(
parser,
"module.parent.require is not supported by webpack."
)
);
parser.hooks.call
.for("module.parent.require")
.tap(
"CommonJsImportsParserPlugin",
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("CommonJsImportsParserPlugin", () => true);
parser.hooks.rename
.for("require")
.tap("CommonJsImportsParserPlugin", defineUndefined);
//#endregion
//#region Inspection
const requireCache = toConstantDependency(
parser,
RuntimeGlobals.moduleCache,
[
RuntimeGlobals.moduleCache,
RuntimeGlobals.moduleId,
RuntimeGlobals.moduleLoaded
]
);
parser.hooks.expression
.for("require.cache")
.tap("CommonJsImportsParserPlugin", requireCache);
//#endregion
//#region Require as expression
/**
* @param {Expression} expr expression
* @returns {boolean} true when handled
*/
const requireAsExpressionHandler = expr => {
const dep = new CommonJsRequireContextDependency(
{
request: options.unknownContextRequest,
recursive: options.unknownContextRecursive,
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 = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
return true;
};
parser.hooks.expression
.for("require")
.tap("CommonJsImportsParserPlugin", 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 = !!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 = !!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}`,
comment.loc
)
);
}
}
if (requireOptions) {
if (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 {
// Do not instrument `require()` if `webpackIgnore` is `true`
if (requireOptions.webpackIgnore) {
return true;
}
}
}
}
}
if (expr.arguments.length !== 1) return;
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;
};
parser.hooks.call
.for("require")
.tap("CommonJsImportsParserPlugin", createRequireHandler(false));
parser.hooks.new
.for("require")
.tap("CommonJsImportsParserPlugin", createRequireHandler(true));
parser.hooks.call
.for("module.require")
.tap("CommonJsImportsParserPlugin", createRequireHandler(false));
parser.hooks.new
.for("module.require")
.tap("CommonJsImportsParserPlugin", createRequireHandler(true));
//#endregion
//#region Require with property access
/**
* @param {Expression} expr expression
* @param {string[]} calleeMembers callee members
* @param {CallExpression} callExpr call expression
* @param {string[]} 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 = !!parser.scope.inTry;
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.current.addDependency(dep);
return true;
}
};
/**
* @param {CallExpression} expr expression
* @param {string[]} calleeMembers callee members
* @param {CallExpression} callExpr call expression
* @param {string[]} 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 = !!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("CommonJsImportsParserPlugin", chainHandler);
parser.hooks.memberChainOfCallMemberChain
.for("module.require")
.tap("CommonJsImportsParserPlugin", chainHandler);
parser.hooks.callMemberChainOfCallMemberChain
.for("require")
.tap("CommonJsImportsParserPlugin", callChainHandler);
parser.hooks.callMemberChainOfCallMemberChain
.for("module.require")
.tap("CommonJsImportsParserPlugin", callChainHandler);
//#endregion
//#region Require.resolve
/**
* @param {CallExpression} expr call expression
* @param {boolean} weak weak
* @returns {boolean | void} true when handled
*/
const processResolve = (expr, weak) => {
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;
} else {
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 = !!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 = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
return true;
};
parser.hooks.call
.for("require.resolve")
.tap("CommonJsImportsParserPlugin", expr => {
return processResolve(expr, false);
});
parser.hooks.call
.for("require.resolveWeak")
.tap("CommonJsImportsParserPlugin", expr => {
return 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("CommonJsImportsParserPlugin", expr => {
const context = parseCreateRequireArguments(expr);
if (context === undefined) return;
const ident = parser.evaluatedVariable({
tag: createdRequireIdentifierTag,
data: { context },
next: undefined
});
return new BasicEvaluatedExpression()
.setIdentifier(
/** @type {TODO} */ (ident),
/** @type {TODO} */ (ident),
() => []
)
.setSideEffects(false)
.setRange(/** @type {Range} */ (expr.range));
});
parser.hooks.unhandledExpressionMemberChain
.for(createdRequireIdentifierTag)
.tap("CommonJsImportsParserPlugin", (expr, members) => {
return expressionIsUnsupported(
parser,
`createRequire().${members.join(".")} is not supported by webpack.`
)(expr);
});
parser.hooks.canRename
.for(createdRequireIdentifierTag)
.tap("CommonJsImportsParserPlugin", () => true);
parser.hooks.canRename
.for(createRequireSpecifierTag)
.tap("CommonJsImportsParserPlugin", () => true);
parser.hooks.rename
.for(createRequireSpecifierTag)
.tap("CommonJsImportsParserPlugin", defineUndefined);
parser.hooks.expression
.for(createdRequireIdentifierTag)
.tap("CommonJsImportsParserPlugin", requireAsExpressionHandler);
parser.hooks.call
.for(createdRequireIdentifierTag)
.tap("CommonJsImportsParserPlugin", 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: "CommonJsImportsParserPlugin",
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: "CommonJsImportsParserPlugin",
stage: -10
},
(statement, source, id, name) => {
if (!moduleName.includes(source) || id !== specifierName) return;
parser.tagVariable(name, createRequireSpecifierTag);
return true;
}
);
parser.hooks.preDeclarator.tap(
"CommonJsImportsParserPlugin",
declarator => {
if (
declarator.id.type !== "Identifier" ||
!declarator.init ||
declarator.init.type !== "CallExpression" ||
declarator.init.callee.type !== "Identifier"
)
return;
const variableInfo =
/** @type {TODO} */
(parser.getVariableInfo(declarator.init.callee.name));
if (
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(
"CommonJsImportsParserPlugin",
(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(
"CommonJsImportsParserPlugin",
(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("CommonJsImportsParserPlugin", (expr, members) => {
// require.cache
if (members.length === 1 && members[0] === "cache") {
return requireCache(expr);
}
});
parser.hooks.callMemberChain
.for(createdRequireIdentifierTag)
.tap("CommonJsImportsParserPlugin", (expr, members) => {
// require.resolve()
if (members.length === 1 && members[0] === "resolve") {
return processResolve(expr, false);
}
});
parser.hooks.call
.for(createRequireSpecifierTag)
.tap("CommonJsImportsParserPlugin", 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;