Files
rspade_system/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js
root 9ebcc359ae Fix code quality violations and enhance ROUTE-EXISTS-01 rule
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>
2025-11-19 17:48:15 +00:00

426 lines
14 KiB
JavaScript

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const RuntimeGlobals = require("../RuntimeGlobals");
const formatLocation = require("../formatLocation");
const { evaluateToString } = require("../javascript/JavascriptParserHelpers");
const propertyAccess = require("../util/propertyAccess");
const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency");
const CommonJsExportsDependency = require("./CommonJsExportsDependency");
const CommonJsSelfReferenceDependency = require("./CommonJsSelfReferenceDependency");
const DynamicExports = require("./DynamicExports");
const HarmonyExports = require("./HarmonyExports");
const ModuleDecoratorDependency = require("./ModuleDecoratorDependency");
/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */
/** @typedef {import("estree").CallExpression} CallExpression */
/** @typedef {import("estree").Expression} Expression */
/** @typedef {import("estree").Super} Super */
/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../ExportsInfo").ExportInfoName} ExportInfoName */
/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
/** @typedef {import("../javascript/JavascriptParser").Members} Members */
/** @typedef {import("../javascript/JavascriptParser").StatementPath} StatementPath */
/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */
/** @typedef {import("../Module").BuildMeta} BuildMeta */
/**
* This function takes a generic expression and detects whether it is an ObjectExpression.
* This is used in the context of parsing CommonJS exports to get the value of the property descriptor
* when the `exports` object is assigned to `Object.defineProperty`.
*
* In CommonJS modules, the `exports` object can be assigned to `Object.defineProperty` and therefore
* webpack has to detect this case and get the value key of the property descriptor. See the following example
* for more information: https://astexplorer.net/#/gist/83ce51a4e96e59d777df315a6d111da6/8058ead48a1bb53c097738225db0967ef7f70e57
*
* This would be an example of a CommonJS module that exports an object with a property descriptor:
* ```js
* Object.defineProperty(exports, "__esModule", { value: true });
* exports.foo = void 0;
* exports.foo = "bar";
* ```
* @param {Expression} expr expression
* @returns {Expression | undefined} returns the value of property descriptor
*/
const getValueOfPropertyDescription = (expr) => {
if (expr.type !== "ObjectExpression") return;
for (const property of expr.properties) {
if (property.type === "SpreadElement" || property.computed) continue;
const key = property.key;
if (key.type !== "Identifier" || key.name !== "value") continue;
return /** @type {Expression} */ (property.value);
}
};
/**
* The purpose of this function is to check whether an expression is a truthy literal or not. This is
* useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy
* values like `null` and `false`. However, exports should only be created if the exported value is truthy.
* @param {Expression} expr expression being checked
* @returns {boolean} true, when the expression is a truthy literal
*/
const isTruthyLiteral = (expr) => {
switch (expr.type) {
case "Literal":
return Boolean(expr.value);
case "UnaryExpression":
if (expr.operator === "!") return isFalsyLiteral(expr.argument);
}
return false;
};
/**
* The purpose of this function is to check whether an expression is a falsy literal or not. This is
* useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy
* values like `null` and `false`. However, exports should only be created if the exported value is truthy.
* @param {Expression} expr expression being checked
* @returns {boolean} true, when the expression is a falsy literal
*/
const isFalsyLiteral = (expr) => {
switch (expr.type) {
case "Literal":
return !expr.value;
case "UnaryExpression":
if (expr.operator === "!") return isTruthyLiteral(expr.argument);
}
return false;
};
/**
* @param {JavascriptParser} parser the parser
* @param {Expression} expr expression
* @returns {{ argument: BasicEvaluatedExpression, ids: ExportInfoName[] } | undefined} parsed call
*/
const parseRequireCall = (parser, expr) => {
/** @type {ExportInfoName[]} */
const ids = [];
while (expr.type === "MemberExpression") {
if (expr.object.type === "Super") return;
if (!expr.property) return;
const prop = expr.property;
if (expr.computed) {
if (prop.type !== "Literal") return;
ids.push(`${prop.value}`);
} else {
if (prop.type !== "Identifier") return;
ids.push(prop.name);
}
expr = expr.object;
}
if (expr.type !== "CallExpression" || expr.arguments.length !== 1) return;
const callee = expr.callee;
if (
callee.type !== "Identifier" ||
parser.getVariableInfo(callee.name) !== "require"
) {
return;
}
const arg = expr.arguments[0];
if (arg.type === "SpreadElement") return;
const argValue = parser.evaluateExpression(arg);
return { argument: argValue, ids: ids.reverse() };
};
const PLUGIN_NAME = "CommonJsExportsParserPlugin";
class CommonJsExportsParserPlugin {
/**
* @param {ModuleGraph} moduleGraph module graph
*/
constructor(moduleGraph) {
this.moduleGraph = moduleGraph;
}
/**
* @param {JavascriptParser} parser the parser
* @returns {void}
*/
apply(parser) {
const enableStructuredExports = () => {
DynamicExports.enable(parser.state);
};
/**
* @param {boolean} topLevel true, when the export is on top level
* @param {Members} members members of the export
* @param {Expression | undefined} valueExpr expression for the value
* @returns {void}
*/
const checkNamespace = (topLevel, members, valueExpr) => {
if (!DynamicExports.isEnabled(parser.state)) return;
if (members.length > 0 && members[0] === "__esModule") {
if (valueExpr && isTruthyLiteral(valueExpr) && topLevel) {
DynamicExports.setFlagged(parser.state);
} else {
DynamicExports.setDynamic(parser.state);
}
}
};
/**
* @param {string=} reason reason
*/
const bailout = (reason) => {
DynamicExports.bailout(parser.state);
if (reason) bailoutHint(reason);
};
/**
* @param {string} reason reason
*/
const bailoutHint = (reason) => {
this.moduleGraph
.getOptimizationBailout(parser.state.module)
.push(`CommonJS bailout: ${reason}`);
};
// metadata //
parser.hooks.evaluateTypeof
.for("module")
.tap(PLUGIN_NAME, evaluateToString("object"));
parser.hooks.evaluateTypeof
.for("exports")
.tap(PLUGIN_NAME, evaluateToString("object"));
// exporting //
/**
* @param {AssignmentExpression} expr expression
* @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
* @param {Members} members members of the export
* @returns {boolean | undefined} true, when the expression was handled
*/
const handleAssignExport = (expr, base, members) => {
if (HarmonyExports.isEnabled(parser.state)) return;
// Handle reexporting
const requireCall = parseRequireCall(parser, expr.right);
if (
requireCall &&
requireCall.argument.isString() &&
(members.length === 0 || members[0] !== "__esModule")
) {
enableStructuredExports();
// It's possible to reexport __esModule, so we must convert to a dynamic module
if (members.length === 0) DynamicExports.setDynamic(parser.state);
const dep = new CommonJsExportRequireDependency(
/** @type {Range} */ (expr.range),
null,
base,
members,
/** @type {string} */ (requireCall.argument.string),
requireCall.ids,
!parser.isStatementLevelExpression(expr)
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
dep.optional = Boolean(parser.scope.inTry);
parser.state.module.addDependency(dep);
/** @type {BuildMeta} */ (
parser.state.module.buildMeta
).treatAsCommonJs = true;
return true;
}
if (members.length === 0) return;
enableStructuredExports();
const remainingMembers = members;
checkNamespace(
/** @type {StatementPath} */
(parser.statementPath).length === 1 &&
parser.isStatementLevelExpression(expr),
remainingMembers,
expr.right
);
const dep = new CommonJsExportsDependency(
/** @type {Range} */ (expr.left.range),
null,
base,
remainingMembers
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addDependency(dep);
/** @type {BuildMeta} */ (parser.state.module.buildMeta).treatAsCommonJs =
true;
parser.walkExpression(expr.right);
return true;
};
parser.hooks.assignMemberChain
.for("exports")
.tap(PLUGIN_NAME, (expr, members) =>
handleAssignExport(expr, "exports", members)
);
parser.hooks.assignMemberChain
.for("this")
.tap(PLUGIN_NAME, (expr, members) => {
if (!parser.scope.topLevelScope) return;
return handleAssignExport(expr, "this", members);
});
parser.hooks.assignMemberChain
.for("module")
.tap(PLUGIN_NAME, (expr, members) => {
if (members[0] !== "exports") return;
return handleAssignExport(expr, "module.exports", members.slice(1));
});
parser.hooks.call
.for("Object.defineProperty")
.tap(PLUGIN_NAME, (expression) => {
const expr = /** @type {CallExpression} */ (expression);
if (!parser.isStatementLevelExpression(expr)) return;
if (expr.arguments.length !== 3) return;
if (expr.arguments[0].type === "SpreadElement") return;
if (expr.arguments[1].type === "SpreadElement") return;
if (expr.arguments[2].type === "SpreadElement") return;
const exportsArg = parser.evaluateExpression(expr.arguments[0]);
if (!exportsArg.isIdentifier()) return;
if (
exportsArg.identifier !== "exports" &&
exportsArg.identifier !== "module.exports" &&
(exportsArg.identifier !== "this" || !parser.scope.topLevelScope)
) {
return;
}
const propertyArg = parser.evaluateExpression(expr.arguments[1]);
const property = propertyArg.asString();
if (typeof property !== "string") return;
enableStructuredExports();
const descArg = expr.arguments[2];
checkNamespace(
/** @type {StatementPath} */
(parser.statementPath).length === 1,
[property],
getValueOfPropertyDescription(descArg)
);
const dep = new CommonJsExportsDependency(
/** @type {Range} */ (expr.range),
/** @type {Range} */ (expr.arguments[2].range),
`Object.defineProperty(${exportsArg.identifier})`,
[property]
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addDependency(dep);
/** @type {BuildMeta} */ (
parser.state.module.buildMeta
).treatAsCommonJs = true;
parser.walkExpression(expr.arguments[2]);
return true;
});
// Self reference //
/**
* @param {Expression | Super} expr expression
* @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
* @param {Members} members members of the export
* @param {CallExpression=} call call expression
* @returns {boolean | void} true, when the expression was handled
*/
const handleAccessExport = (expr, base, members, call) => {
if (HarmonyExports.isEnabled(parser.state)) return;
if (members.length === 0) {
bailout(
`${base} is used directly at ${formatLocation(
/** @type {DependencyLocation} */ (expr.loc)
)}`
);
}
if (call && members.length === 1) {
bailoutHint(
`${base}${propertyAccess(
members
)}(...) prevents optimization as ${base} is passed as call context at ${formatLocation(
/** @type {DependencyLocation} */ (expr.loc)
)}`
);
}
const dep = new CommonJsSelfReferenceDependency(
/** @type {Range} */ (expr.range),
base,
members,
Boolean(call)
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addDependency(dep);
/** @type {BuildMeta} */ (parser.state.module.buildMeta).treatAsCommonJs =
true;
if (call) {
parser.walkExpressions(call.arguments);
}
return true;
};
parser.hooks.callMemberChain
.for("exports")
.tap(PLUGIN_NAME, (expr, members) =>
handleAccessExport(expr.callee, "exports", members, expr)
);
parser.hooks.expressionMemberChain
.for("exports")
.tap(PLUGIN_NAME, (expr, members) =>
handleAccessExport(expr, "exports", members)
);
parser.hooks.expression
.for("exports")
.tap(PLUGIN_NAME, (expr) => handleAccessExport(expr, "exports", []));
parser.hooks.callMemberChain
.for("module")
.tap(PLUGIN_NAME, (expr, members) => {
if (members[0] !== "exports") return;
return handleAccessExport(
expr.callee,
"module.exports",
members.slice(1),
expr
);
});
parser.hooks.expressionMemberChain
.for("module")
.tap(PLUGIN_NAME, (expr, members) => {
if (members[0] !== "exports") return;
return handleAccessExport(expr, "module.exports", members.slice(1));
});
parser.hooks.expression
.for("module.exports")
.tap(PLUGIN_NAME, (expr) =>
handleAccessExport(expr, "module.exports", [])
);
parser.hooks.callMemberChain
.for("this")
.tap(PLUGIN_NAME, (expr, members) => {
if (!parser.scope.topLevelScope) return;
return handleAccessExport(expr.callee, "this", members, expr);
});
parser.hooks.expressionMemberChain
.for("this")
.tap(PLUGIN_NAME, (expr, members) => {
if (!parser.scope.topLevelScope) return;
return handleAccessExport(expr, "this", members);
});
parser.hooks.expression.for("this").tap(PLUGIN_NAME, (expr) => {
if (!parser.scope.topLevelScope) return;
return handleAccessExport(expr, "this", []);
});
// Bailouts //
parser.hooks.expression.for("module").tap(PLUGIN_NAME, (expr) => {
bailout();
const isHarmony = HarmonyExports.isEnabled(parser.state);
const dep = new ModuleDecoratorDependency(
isHarmony
? RuntimeGlobals.harmonyModuleDecorator
: RuntimeGlobals.nodeModuleDecorator,
!isHarmony
);
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
parser.state.module.addDependency(dep);
return true;
});
}
}
module.exports = CommonJsExportsParserPlugin;