/
githubmirror
/
webpack
Обзор
Документация
Войти
/
githubmirror
/
webpack
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
lib/dependencies/CommonJsExportRequireDependency.js
605 строк
19 KB
Xiao
fix(cjs): disconnect unused side-effect-free reexport requires (#21651)
09 авг 2026, 21:22
Не верифицирован
09 авг 2026, 21:22
da0e809
Код
Авторство
О чём код?
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const Dependency = require("../Dependency"); const { UsageState } = require("../ExportsInfo"); const Template = require("../Template"); const { equals } = require("../util/ArrayHelpers"); const makeSerializable = require("../util/makeSerializable"); const { propertyAccess } = require("../util/property"); const { ESM_MODULE_EXPORTS_NAME, getRequireEsmModuleExportsAccess, handleDependencyBase, isRequireEsmModuleExportsModule } = require("./CommonJsDependencyHelpers"); const ModuleDependency = require("./ModuleDependency"); const processExportInfo = require("./processExportInfo"); /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ /** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ /** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ /** @typedef {import("../Dependency").RawReferencedExports} RawReferencedExports */ /** @typedef {import("../Dependency").ReferencedExports} ReferencedExports */ /** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../ExportsInfo")} ExportsInfo */ /** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ /** @typedef {import("../ExportsInfo").ExportInfoName} ExportInfoName */ /** @typedef {import("../Module")} Module */ /** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ /** @typedef {import("../javascript/JavascriptParser").Range} Range */ /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ /** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */ /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[undefined | boolean, Range, Range | null, CommonJSDependencyBaseKeywords, ExportInfoName[], ExportInfoName[], boolean, boolean]>} ObjectDeserializerContext */ /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[undefined | boolean, Range, Range | null, CommonJSDependencyBaseKeywords, ExportInfoName[], ExportInfoName[], boolean, boolean]>} ObjectSerializerContext */ const idsSymbol = /** @type {symbol} */ ( Symbol("CommonJsExportRequireDependency.ids") ); const EMPTY_OBJECT = {}; /** @typedef {Set<string>} Exports */ /** @typedef {Set<string>} Checked */ class CommonJsExportRequireDependency extends ModuleDependency { /** * Creates an instance of CommonJsExportRequireDependency. * @param {Range} range range * @param {Range | null} valueRange value range * @param {CommonJSDependencyBaseKeywords} base base * @param {ExportInfoName[]} names names * @param {string} request request * @param {ExportInfoName[]} ids ids * @param {boolean} resultUsed true, when the result is used */ constructor(range, valueRange, base, names, request, ids, resultUsed) { super(request); this.range = range; this.valueRange = valueRange; /** @type {CommonJSDependencyBaseKeywords} */ this.base = base; /** @type {string[]} */ this.names = names; /** @type {string[]} */ this.ids = ids; /** @type {boolean} */ this.resultUsed = resultUsed; /** @type {undefined | boolean} */ this.asiSafe = undefined; // true when the reexport is a lazy `{ get: () => require(...) }` accessor // (as produced by `Object.defineProperty`) rather than an eager value. /** @type {boolean} */ this.getter = false; } get type() { return "cjs export require"; } get category() { return "commonjs"; } /** * Could affect referencing module. * @returns {boolean | TRANSITIVE} true, when changes to the referenced module could affect the referencing module; TRANSITIVE, when changes to the referenced module could affect referencing modules of the referencing module */ couldAffectReferencingModule() { return Dependency.TRANSITIVE; } /** * Returns true if this dependency can be concatenated * @param {boolean} concatenateCommonJsModules whether optimization.concatenateModules.commonjs is enabled * @returns {boolean} true if this dependency can be concatenated */ canConcatenate(concatenateCommonJsModules) { return concatenateCommonJsModules; } /** * Returns function to determine if the connection is active. * @param {ModuleGraph} moduleGraph module graph * @returns {null | false | GetConditionFn} function to determine if the connection is active */ getCondition(moduleGraph) { // Conservative: keep `module.exports = require(...)` (names empty) active even // when the parent is evaluation-only and the target is side-effect-free. if (this.resultUsed || this.names.length === 0) return null; const names = this.names; const getter = this.getter; return (connection, runtime) => { const parentModule = moduleGraph.getParentModule(this); if (!parentModule) return true; const used = moduleGraph .getExportsInfo(parentModule) .getUsedName(names, runtime); if (used !== false) return true; if (getter) return false; const refModule = connection.resolvedModule; if (!refModule) return true; return refModule.getSideEffectsConnectionState(moduleGraph); }; } /** * Returns the imported id. * @param {ModuleGraph} moduleGraph the module graph * @returns {ExportInfoName[]} the imported id */ getIds(moduleGraph) { return moduleGraph.getMeta(this)[idsSymbol] || this.ids; } /** * Updates ids using the provided module graph. * @param {ModuleGraph} moduleGraph the module graph * @param {ExportInfoName[]} ids the imported ids * @returns {void} */ setIds(moduleGraph, ids) { moduleGraph.getMeta(this)[idsSymbol] = ids; } /** * Returns list of exports referenced by this dependency * @param {ModuleGraph} moduleGraph module graph * @param {RuntimeSpec} runtime the runtime for which the module is analysed * @returns {ReferencedExports} referenced exports */ getReferencedExports(moduleGraph, runtime) { const ids = this.getIds(moduleGraph); const importedModule = moduleGraph.getModule(this); if ( importedModule && isRequireEsmModuleExportsModule(importedModule, moduleGraph) ) { // `require(esm)` unwraps the "module.exports" named export; any // further property access lands on that value (which webpack does // not model), so only the "module.exports" export is observable. return [{ name: [ESM_MODULE_EXPORTS_NAME], canInline: false }]; } const getFullResult = () => { if (ids.length === 0) { return Dependency.EXPORTS_OBJECT_REFERENCED; } return [ { name: ids, canMangle: false, canInline: false } ]; }; if (this.resultUsed) return getFullResult(); /** @type {ExportsInfo | undefined} */ let exportsInfo = moduleGraph.getExportsInfo( /** @type {Module} */ (moduleGraph.getParentModule(this)) ); for (const name of this.names) { const exportInfo = /** @type {ExportInfo} */ (exportsInfo.getReadOnlyExportInfo(name)); const used = exportInfo.getUsed(runtime); if (used === UsageState.Unused) return Dependency.NO_EXPORTS_REFERENCED; if (used !== UsageState.OnlyPropertiesUsed) return getFullResult(); exportsInfo = exportInfo.exportsInfo; if (!exportsInfo) return getFullResult(); } if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) { return getFullResult(); } /** @type {RawReferencedExports} */ const referencedExports = []; for (const exportInfo of exportsInfo.orderedExports) { processExportInfo( runtime, referencedExports, [...ids, exportInfo.name], exportInfo, false ); } return referencedExports.map((name) => ({ name, canMangle: false, canInline: false })); } /** * Returns the exported names * @param {ModuleGraph} moduleGraph module graph * @returns {ExportsSpec | undefined} export names */ getExports(moduleGraph) { const importedModule = moduleGraph.getModule(this); const esmUnwrap = importedModule && isRequireEsmModuleExportsModule(importedModule, moduleGraph); if (this.names.length === 1) { const ids = this.getIds(moduleGraph); const name = this.names[0]; const from = moduleGraph.getConnection(this); if (!from) return; const exportChain = esmUnwrap ? [ESM_MODULE_EXPORTS_NAME, ...ids] : ids.length === 0 ? null : ids; return { exports: [ { name, from, export: exportChain, // we can't mangle names that are in an empty object // because one could access the prototype property // when export isn't set yet canMangle: !(name in EMPTY_OBJECT) && false } ], dependencies: [from.module] }; } else if (this.names.length > 0) { const name = this.names[0]; return { exports: [ { name, // we can't mangle names that are in an empty object // because one could access the prototype property // when export isn't set yet canMangle: !(name in EMPTY_OBJECT) && false } ], dependencies: undefined }; } const from = moduleGraph.getConnection(this); if (!from) return; if (esmUnwrap) { // Full re-export `module.exports = require("./esm")` of a module // with a `"module.exports"` named export: the wrapping module's // `module.exports` becomes the unwrapped value, whose own // properties webpack cannot enumerate statically. return { exports: true, canMangle: false, dependencies: [from.module] }; } // The imported namespace ESM hasn't been flagged by // FlagDependencyExportsPlugin yet (no exports determined), so its // `"module.exports"` unwrap eligibility is still unknown. Star-reexporting // now would add `__esModule` (and names) the monotonic merge can't retract // once the module turns out to unwrap, making the result order-dependent // across runtimes; defer — the `from.module` dependency re-queues us once // its exports (owned names, or dynamic `other`) become known. if ( importedModule && importedModule.getExportsType(moduleGraph, false) === "namespace" ) { const importedExportsInfo = moduleGraph.getExportsInfo(importedModule); if ( importedExportsInfo.otherExportsInfo.provided === false && importedExportsInfo.ownedExports[Symbol.iterator]().next().done ) { return { exports: [], dependencies: [from.module] }; } } const reexportInfo = this.getStarReexports( moduleGraph, undefined, from.module ); const ids = this.getIds(moduleGraph); if (reexportInfo) { return { exports: Array.from( /** @type {Exports} */ (reexportInfo.exports), (name) => ({ name, from, export: [...ids, name], canMangle: !(name in EMPTY_OBJECT) && false }) ), dependencies: [from.module] }; } return { exports: true, from: ids.length === 0 ? from : undefined, canMangle: false, dependencies: [from.module] }; } /** * Gets star reexports. * @param {ModuleGraph} moduleGraph the module graph * @param {RuntimeSpec} runtime the runtime * @param {Module} importedModule the imported module (optional) * @returns {{ exports?: Exports, checked?: Checked } | undefined} information */ getStarReexports( moduleGraph, runtime, importedModule = /** @type {Module} */ (moduleGraph.getModule(this)) ) { /** @type {ExportsInfo | undefined} */ let importedExportsInfo = moduleGraph.getExportsInfo(importedModule); const ids = this.getIds(moduleGraph); if (ids.length > 0) { importedExportsInfo = importedExportsInfo.getNestedExportsInfo(ids); } /** @type {ExportsInfo | undefined} */ let exportsInfo = moduleGraph.getExportsInfo( /** @type {Module} */ (moduleGraph.getParentModule(this)) ); if (this.names.length > 0) { exportsInfo = exportsInfo.getNestedExportsInfo(this.names); } const noExtraExports = importedExportsInfo && importedExportsInfo.otherExportsInfo.provided === false; const noExtraImports = exportsInfo && exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused; if (!noExtraExports && !noExtraImports) { return; } const isNamespaceImport = importedModule.getExportsType(moduleGraph, false) === "namespace"; /** @type {Exports} */ const exports = new Set(); /** @type {Checked} */ const checked = new Set(); if (noExtraImports) { for (const exportInfo of /** @type {ExportsInfo} */ (exportsInfo) .orderedExports) { const name = exportInfo.name; if (exportInfo.getUsed(runtime) === UsageState.Unused) continue; if (name === "__esModule" && isNamespaceImport) { exports.add(name); } else if (importedExportsInfo) { const importedExportInfo = importedExportsInfo.getReadOnlyExportInfo(name); if (importedExportInfo.provided === false) continue; exports.add(name); if (importedExportInfo.provided === true) continue; checked.add(name); } else { exports.add(name); checked.add(name); } } } else if (noExtraExports) { for (const importedExportInfo of /** @type {ExportsInfo} */ ( importedExportsInfo ).orderedExports) { const name = importedExportInfo.name; if (importedExportInfo.provided === false) continue; if (exportsInfo) { const exportInfo = exportsInfo.getReadOnlyExportInfo(name); if (exportInfo.getUsed(runtime) === UsageState.Unused) continue; } exports.add(name); if (importedExportInfo.provided === true) continue; checked.add(name); } if (isNamespaceImport) { exports.add("__esModule"); checked.delete("__esModule"); } } return { exports, checked }; } /** * Serializes this instance into the provided serializer context. * @param {ObjectSerializerContext} context context */ serialize(context) { context .write(this.asiSafe) .write(this.range) .write(this.valueRange) .write(this.base) .write(this.names) .write(this.ids) .write(this.resultUsed) .write(this.getter); super.serialize(context); } /** * Restores this instance from the provided deserializer context. * @param {ObjectDeserializerContext} context context */ deserialize(context) { this.asiSafe = context.read(); const c1 = context.rest; this.range = c1.read(); const c2 = c1.rest; this.valueRange = c2.read(); const c3 = c2.rest; this.base = c3.read(); const c4 = c3.rest; this.names = c4.read(); const c5 = c4.rest; this.ids = c5.read(); const c6 = c5.rest; this.resultUsed = c6.read(); const c7 = c6.rest; this.getter = c7.read(); super.deserialize(c7.rest); } } makeSerializable( CommonJsExportRequireDependency, "webpack/lib/dependencies/CommonJsExportRequireDependency" ); CommonJsExportRequireDependency.Template = class CommonJsExportRequireDependencyTemplate extends ( ModuleDependency.Template ) { /** * Applies the plugin by registering its hooks on the compiler. * @param {Dependency} dependency the dependency for which the template should be applied * @param {ReplaceSource} source the current replace source which can be modified * @param {DependencyTemplateContext} templateContext the context object * @returns {void} */ apply( dependency, source, { module, runtimeTemplate, chunkGraph, moduleGraph, runtimeRequirements, runtime, concatenationScope } ) { const dep = /** @type {CommonJsExportRequireDependency} */ (dependency); // CJS exports are never inlined const used = /** @type {string | string[] | false} */ ( moduleGraph.getExportsInfo(module).getUsedName(dep.names, runtime) ); const connection = /** @type {ModuleGraphConnection | undefined} */ ( moduleGraph.getConnection(dep) ); // Inactive unused reexport: no module id; drop to a no-op. // Missing connection (e.g. IgnorePlugin) must keep the require so it throws. if (!used && connection && !connection.isTargetActive(runtime)) { source.replace(dep.range[0], dep.range[1] - 1, "/* unused reexport */ 0"); return; } const [type, base] = handleDependencyBase( dep.base, module, runtimeRequirements ); const importedModule = moduleGraph.getModule(dep); /** @type {string} */ let requireExpr; if ( concatenationScope && importedModule && concatenationScope.isModuleInScope(importedModule) ) { // The alias reads the target's exports value, so it binds like `require()` // rather than an ESM import: no interop. const ids = dep.getIds(moduleGraph); const requestedIds = isRequireEsmModuleExportsModule( importedModule, moduleGraph ) ? [ESM_MODULE_EXPORTS_NAME, ...ids] : ids; requireExpr = concatenationScope.createModuleReference(importedModule, { ids: requestedIds.length > 0 ? requestedIds : undefined, asiSafe: true, moduleExportsAccess: true }); } else { requireExpr = runtimeTemplate.moduleExports({ module: importedModule, chunkGraph, request: dep.request, weak: dep.weak, runtimeRequirements }); if (importedModule) { const ids = dep.getIds(moduleGraph); const esmRequireAccess = getRequireEsmModuleExportsAccess( importedModule, moduleGraph, runtime ); if (esmRequireAccess !== null) { requireExpr += `${esmRequireAccess}${propertyAccess(ids)}`; } else { // CJS exports are never inlined const usedImported = /** @type {string | string[] | false} */ ( moduleGraph.getExportsInfo(importedModule).getUsedName(ids, runtime) ); if (usedImported) { const comment = equals(usedImported, ids) ? "" : `${Template.toNormalComment(propertyAccess(ids))} `; requireExpr += `${comment}${propertyAccess(/** @type {string[]} */ (usedImported))}`; } } } } switch (type) { case "expression": source.replace( dep.range[0], dep.range[1] - 1, used ? `${base}${propertyAccess(/** @type {string[]} */ (used))} = ${requireExpr}` : `/* unused reexport */ ${requireExpr}` ); return; case "Object.defineProperty": { // `Object.defineProperty(exports, "name", { value: require("...") })` // or the lazy getter form `{ get: () => require("...") }` used by // barrel files (e.g. webpack's own `lib/index.js`). const valueRange = /** @type {Range} */ (dep.valueRange); if (!used) { // Active unused eager reexport: keep side effects. Unused getters // are inactive and already replaced with `0` above. source.replace( dep.range[0], dep.range[1] - 1, `/* unused reexport */ ${requireExpr}` ); return; } const descriptor = dep.getter ? "enumerable: true, get: () => (" : "value: ("; source.replace( dep.range[0], valueRange[0] - 1, `Object.defineProperty(${base}${propertyAccess( /** @type {string[]} */ (used).slice(0, -1) )}, ${JSON.stringify(used[used.length - 1])}, { ${descriptor}` ); source.replace(valueRange[0], valueRange[1] - 1, requireExpr); source.replace(valueRange[1], dep.range[1] - 1, ") })"); return; } default: throw new Error("Unexpected type"); } } }; module.exports = CommonJsExportRequireDependency; module.exports.idsSymbol = idsSymbol;