Implement JQHTML function cache ID system and fix bundle compilation Implement underscore prefix for system tables Fix JS syntax linter to support decorators and grant exception to Task system SPA: Update planning docs and wishlists with remaining features SPA: Document Navigation API abandonment and future enhancements Implement SPA browser integration with History API (Phase 1) Convert contacts view page to SPA action Convert clients pages to SPA actions and document conversion procedure SPA: Merge GET parameters and update documentation Implement SPA route URL generation in JavaScript and PHP Implement SPA bootstrap controller architecture Add SPA routing manual page (rsx:man spa) Add SPA routing documentation to CLAUDE.md Phase 4 Complete: Client-side SPA routing implementation Update get_routes() consumers for unified route structure Complete SPA Phase 3: PHP-side route type detection and is_spa flag Restore unified routes structure and Manifest_Query class Refactor route indexing and add SPA infrastructure Phase 3 Complete: SPA route registration in manifest Implement SPA Phase 2: Extract router code and test decorators Rename Jqhtml_Component to Component and complete SPA foundation setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
803 lines
25 KiB
JavaScript
803 lines
25 KiB
JavaScript
/*
|
|
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 { VariableInfo } = require("../javascript/JavascriptParser");
|
|
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 */
|
|
/** @typedef {import("../javascript/JavascriptParser").Members} Members */
|
|
/** @typedef {import("../javascript/JavascriptParser").CalleeMembers} CalleeMembers */
|
|
|
|
/**
|
|
* @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
|
|
*/
|
|
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 } =
|
|
/** @type {CommonJsImportSettings} */
|
|
(parser.currentTagData);
|
|
return context;
|
|
}
|
|
};
|
|
|
|
// #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,
|
|
[
|
|
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
|
|
*/
|
|
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;
|
|
};
|
|
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;
|
|
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(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
|
|
/**
|
|
* @param {CallExpression} expr call expression
|
|
* @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;
|
|
};
|
|
|
|
parser.hooks.call
|
|
.for("require.resolve")
|
|
.tap(PLUGIN_NAME, (expr) => processResolve(expr, false));
|
|
parser.hooks.call
|
|
.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;
|