/
githubmirror
/
webpack
Обзор
Документация
Войти
/
githubmirror
/
webpack
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
lib/library/ModuleLibraryPlugin.js
656 строк
21 KB
Alexander Akait
fix(library): don't crash on namespace object reexports in module output (#21589)
03 авг 2026, 14:57
Не верифицирован
03 авг 2026, 14:57
f7d3815
Код
Авторство
О чём код?
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const { ConcatSource } = require("webpack-sources"); const { UsageState } = require("../ExportsInfo"); const ExternalModule = require("../ExternalModule"); const RuntimeGlobals = require("../RuntimeGlobals"); const Template = require("../Template"); const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency"); const HarmonyExportSpecifierDependency = require("../dependencies/HarmonyExportSpecifierDependency"); const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin"); const ConcatenatedModule = require("../optimize/ConcatenatedModule"); const { InlinedUsedName } = require("../optimize/InlineExports"); const { RESERVED_IDENTIFIER, SAFE_IDENTIFIER, propertyAccess } = require("../util/property"); const { getEntryRuntime, getRuntimeKey } = require("../util/runtime"); const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); /** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ /** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ /** @typedef {import("../../declarations/WebpackOptions").LibraryExport} LibraryExport */ /** @typedef {import("../Chunk")} Chunk */ /** @typedef {import("../Compiler")} Compiler */ /** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../Module")} Module */ /** @typedef {import("../Module").BuildMeta} BuildMeta */ /** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */ /** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ /** @typedef {import("../javascript/JavascriptModulesPlugin").ModuleRenderContext} ModuleRenderContext */ /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ /** @typedef {import("../javascript/JavascriptModule").JavascriptModuleBuildMeta} JavascriptModuleBuildMeta */ /** * Defines the shared type used by this module. * @template T * @typedef {import("./AbstractLibraryPlugin").LibraryContext<T>} LibraryContext<T> */ /** * Defines the module library plugin options type used by this module. * @typedef {object} ModuleLibraryPluginOptions * @property {LibraryType} type */ /** * Defines the module library plugin parsed type used by this module. * @typedef {object} ModuleLibraryPluginParsed * @property {string} name * @property {LibraryExport=} export */ const PLUGIN_NAME = "ModuleLibraryPlugin"; /** * An export name is either an identifier name (reserved words included, e.g. * `default`) or a string literal (arbitrary module namespace names). * @param {string} name export name * @returns {string} name as it can be written after `as` */ const toExportName = (name) => SAFE_IDENTIFIER.test(name) ? name : JSON.stringify(name); /** * Whether the export name can also be used as the declared binding name. * @param {string} name export name * @returns {boolean} true, when the name can be declared */ const isDeclarableExportName = (name) => SAFE_IDENTIFIER.test(name) && !RESERVED_IDENTIFIER.has(name); /** * Represents the module library plugin runtime component. * @typedef {ModuleLibraryPluginParsed} T * @extends {AbstractLibraryPlugin<ModuleLibraryPluginParsed>} */ class ModuleLibraryPlugin extends AbstractLibraryPlugin { /** * Creates an instance of ModuleLibraryPlugin. * @param {ModuleLibraryPluginOptions} options the plugin options */ constructor(options) { super({ pluginName: "ModuleLibraryPlugin", type: options.type }); } /** * Applies the plugin by registering its hooks on the compiler. * @param {Compiler} compiler the compiler instance * @returns {void} */ apply(compiler) { super.apply(compiler); compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { const { onDemandExportsGeneration } = ConcatenatedModule.getCompilationHooks(compilation); const javascriptHooks = JavascriptModulesPlugin.getCompilationHooks(compilation); onDemandExportsGeneration.tap( PLUGIN_NAME, (module, runtimes, source, finalName) => { /** @type {BuildMeta} */ const buildMeta = module.buildMeta || (module.buildMeta = {}); /** @type {BuildMeta["exportsSourceByRuntime"]} */ const exportsSourceByRuntime = buildMeta.exportsSourceByRuntime || (buildMeta.exportsSourceByRuntime = new Map()); /** @type {BuildMeta["exportsFinalNameByRuntime"]} */ const exportsFinalNameByRuntime = buildMeta.exportsFinalNameByRuntime || (buildMeta.exportsFinalNameByRuntime = new Map()); for (const runtime of runtimes) { const key = getRuntimeKey(runtime); exportsSourceByRuntime.set(key, source); exportsFinalNameByRuntime.set(key, finalName); } return true; } ); // `ModuleLibraryPlugin` stashes the on-demand exports source via // `onDemandExportsGeneration` and only re-emits it when the // module is wrapped in an IIFE/factory. When the entry is // inlined directly, the stashed source — and the // `definePropertyGetters` / `requireScope` runtime helpers it // pulled in — never make it into the output. Drop those helpers // from the chunk's set so the bundle stays clean. compilation.hooks.additionalChunkRuntimeRequirements.tap( PLUGIN_NAME, (chunk, set, { chunkGraph, codeGenerationResults }) => { if (!set.has(RuntimeGlobals.definePropertyGetters)) return; if (chunkGraph.getNumberOfEntryModules(chunk) !== 1) return; if (chunkGraph.hasChunkEntryDependentChunks(chunk)) return; if ( set.has(RuntimeGlobals.moduleFactories) || set.has(RuntimeGlobals.moduleCache) || set.has(RuntimeGlobals.interceptModuleExecution) || set.has(RuntimeGlobals.module) || set.has(RuntimeGlobals.thisAsExports) ) { return; } if (javascriptHooks.inlineInRuntimeBailout.isUsed()) return; const [module] = chunkGraph.getChunkEntryModulesIterable(chunk); const exportsSourceByRuntime = module.buildMeta && module.buildMeta.exportsSourceByRuntime; if ( !exportsSourceByRuntime || !exportsSourceByRuntime.has(getRuntimeKey(chunk.runtime)) ) { return; } const codeGenResult = codeGenerationResults.get( module, chunk.runtime ); const jsSource = codeGenResult && codeGenResult.sources.get("javascript"); if ( jsSource && String(jsSource.source()).includes(`${RuntimeGlobals.require}.`) ) { return; } // Check whether any other module in the chunk still needs // the helpers we want to remove. let otherNeedsDefine = false; let otherNeedsExports = false; let otherNeedsRequireScope = false; for (const m of chunkGraph.getChunkModulesIterable(chunk)) { if (m === module) continue; const requirements = chunkGraph.getModuleRuntimeRequirements( m, chunk.runtime ); if (!requirements) continue; if (requirements.has(RuntimeGlobals.definePropertyGetters)) { otherNeedsDefine = true; } if (requirements.has(RuntimeGlobals.exports)) { otherNeedsExports = true; } if (requirements.has(RuntimeGlobals.requireScope)) { otherNeedsRequireScope = true; } if (otherNeedsDefine) break; } if (otherNeedsDefine) return; set.delete(RuntimeGlobals.definePropertyGetters); if (!otherNeedsExports) set.delete(RuntimeGlobals.exports); if (!otherNeedsRequireScope) set.delete(RuntimeGlobals.requireScope); } ); }); } /** * Finish entry module. * @param {Module} module the exporting entry module * @param {string} entryName the name of the entrypoint * @param {LibraryContext<T>} libraryContext context * @returns {void} */ finishEntryModule( module, entryName, { options, compilation, compilation: { moduleGraph } } ) { const runtime = getEntryRuntime(compilation, entryName); if (options.export) { const exportsInfo = moduleGraph.getExportInfo( module, Array.isArray(options.export) ? options.export[0] : options.export ); exportsInfo.setUsed(UsageState.Used, runtime); exportsInfo.canMangleUse = false; exportsInfo.canInlineUse = false; } else { const exportsInfo = moduleGraph.getExportsInfo(module); if ( // If the entry module is commonjs, its exports cannot be mangled (module.buildMeta && /** @type {JavascriptModuleBuildMeta} */ (module.buildMeta) .treatAsCommonJs) || // The entry module provides unknown exports exportsInfo._otherExportsInfo.provided === null ) { exportsInfo.setUsedInUnknownWay(runtime); } else { exportsInfo.setAllKnownExportsUsed(runtime); for (const exportInfo of exportsInfo.ownedExports) { exportInfo.canInlineUse = false; } } } moduleGraph.addExtraReason(module, "used as library export"); } /** * Returns preprocess as needed by overriding. * @param {LibraryOptions} library normalized library option * @returns {T} preprocess as needed by overriding */ parseOptions(library) { const { name } = library; if (name) { throw new Error( `Library name must be unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` ); } const _name = /** @type {string} */ (name); return { name: _name, export: library.export }; } /** * Analyze unknown provided exports. * @param {Source} source source * @param {Module} module module * @param {ModuleGraph} moduleGraph moduleGraph * @param {RuntimeSpec} runtime chunk runtime * @param {[string, string][]} exports exports * @param {Set<string>} alreadyRenderedExports already rendered exports * @returns {ConcatSource} source with null provided exports */ _analyzeUnknownProvidedExports( source, module, moduleGraph, runtime, exports, alreadyRenderedExports ) { const result = new ConcatSource(source); /** @type {Set<string>} */ const moduleRequests = new Set(); /** @type {Map<string, string | InlinedUsedName>} */ const unknownProvidedExports = new Map(); /** * Resolves dynamic star reexport. * @param {Module} module the module * @param {boolean} isDynamicReexport if module is dynamic reexported */ const resolveDynamicStarReexport = (module, isDynamicReexport) => { for (const connection of moduleGraph.getOutgoingConnections(module)) { const dep = connection.dependency; // Only handle star-reexport statement if ( dep instanceof HarmonyExportImportedSpecifierDependency && dep.name === null ) { const importedModule = connection.resolvedModule; const importedModuleExportsInfo = moduleGraph.getExportsInfo(importedModule); // The imported module provides unknown exports // So keep the reexports rendered in the bundle if ( dep.getMode(moduleGraph, runtime).type === "dynamic-reexport" && importedModuleExportsInfo._otherExportsInfo.provided === null ) { // Handle export * from 'external' if (importedModule instanceof ExternalModule) { moduleRequests.add(importedModule.userRequest); } else { resolveDynamicStarReexport(importedModule, true); } } // If importer modules existing `dynamic-reexport` dependency // We should keep export statement rendered in the bundle else if (isDynamicReexport) { for (const exportInfo of importedModuleExportsInfo.orderedExports) { if (!exportInfo.provided || exportInfo.name === "default") { continue; } const originalName = exportInfo.name; const usedName = /** @type {string | InlinedUsedName | false} */ (exportInfo.getUsedName(originalName, runtime)); if (!alreadyRenderedExports.has(originalName) && usedName) { unknownProvidedExports.set(originalName, usedName); } } } } } }; resolveDynamicStarReexport(module, false); for (const request of moduleRequests) { result.add(`export * from "${request}";\n`); } for (const [origin, used] of unknownProvidedExports) { exports.push([ origin, used instanceof InlinedUsedName ? used.render( Template.toNormalComment( `inlined export ${propertyAccess([origin])}` ) ) : `${RuntimeGlobals.exports}${propertyAccess([used])}` ]); } return result; } /** * Renders source with library export. * @param {Source} source source * @param {Module} module module * @param {StartupRenderContext} renderContext render context * @param {LibraryContext<T>} libraryContext context * @returns {Source} source with library export */ renderStartup(source, module, renderContext, { options, compilation }) { const { moduleGraph, chunk, codeGenerationResults, inlined, inlinedInIIFE, runtimeTemplate } = renderContext; let result = new ConcatSource(source); const exportInfos = options.export ? [ moduleGraph.getExportInfo( module, Array.isArray(options.export) ? options.export[0] : options.export ) ] : moduleGraph.getExportsInfo(module).orderedExports; const exportsFinalNameByRuntime = (module.buildMeta && module.buildMeta.exportsFinalNameByRuntime && module.buildMeta.exportsFinalNameByRuntime.get( getRuntimeKey(chunk.runtime) )) || {}; const isInlinedEntryWithoutIIFE = inlined && !inlinedInIIFE; // Direct export bindings from on-demand concatenation const definitions = isInlinedEntryWithoutIIFE ? exportsFinalNameByRuntime : {}; /** @type {string[]} */ const shortHandedExports = []; /** @type {[string, string][]} */ const exports = []; /** @type {Set<string>} */ const alreadyRenderedExports = new Set(); /** @type {Set<string>} */ const usedLocalNames = new Set(); /** * `toIdentifier` is lossy, so distinct export names (`"a b"` and `"a-b"`) * would otherwise be bound to the same generated name twice. * @param {string} exportName export name * @returns {string} unique local name for the export */ const toLocalName = (exportName) => { const base = `${RuntimeGlobals.exports}${Template.toIdentifier(exportName)}`; let name = base; let i = 0; while (usedLocalNames.has(name)) name = `${base}_${i++}`; usedLocalNames.add(name); return name; }; const isAsync = moduleGraph.isAsync(module); const treatAsCommonJs = module.buildMeta && /** @type {JavascriptModuleBuildMeta} */ (module.buildMeta) .treatAsCommonJs; const skipRenderDefaultExport = Boolean(treatAsCommonJs); // A single (non-concatenated) inlined entry is rendered at the top // level, so its own declarations are top-level bindings we can export // directly — keeping ESM live bindings instead of snapshotting // `__webpack_exports__.x` into a `const`. Concatenated entries already // get direct bindings via `definitions` above. /** @type {Map<string, string> | undefined} */ let directBindings; if (isInlinedEntryWithoutIIFE && !treatAsCommonJs) { const { data } = codeGenerationResults.get(module, chunk.runtime); const topLevelDeclarations = (data && data.get("topLevelDeclarations")) || (module.buildInfo && module.buildInfo.topLevelDeclarations); if (topLevelDeclarations) { for (const dep of module.dependencies) { if ( dep instanceof HarmonyExportSpecifierDependency && topLevelDeclarations.has(dep.id) ) { (directBindings || (directBindings = new Map())).set( dep.name, dep.id ); } } } } const moduleExportsInfo = moduleGraph.getExportsInfo(module); // Define ESM compatibility flag will rely on `__webpack_exports__` const needHarmonyCompatibilityFlag = moduleExportsInfo.otherExportsInfo.getUsed(chunk.runtime) !== UsageState.Unused || moduleExportsInfo .getReadOnlyExportInfo("__esModule") .getUsed(chunk.runtime) !== UsageState.Unused; let needExportsDeclaration = !isInlinedEntryWithoutIIFE || isAsync || needHarmonyCompatibilityFlag; if (isAsync) { result.add( `${RuntimeGlobals.exports} = await ${RuntimeGlobals.exports};\n` ); } // Try to find all known exports of the entry module outer: for (const exportInfo of exportInfos) { if (!exportInfo.provided) continue; const originalName = exportInfo.name; // Skip rendering the default export in some cases if (skipRenderDefaultExport && originalName === "default") continue; // Try to find all exports from the reexported modules const target = exportInfo.findTarget(moduleGraph, (_m) => true); // A namespace object target (`export * as ns from "./module"`) has no // export name, so there is nothing that could be missing if (target && target.export) { const targetExport = target.export[0]; const reexportsInfo = moduleGraph.getExportsInfo(target.module); for (const reexportInfo of reexportsInfo.orderedExports) { if ( reexportInfo.provided === false && reexportInfo.name !== "default" && reexportInfo.name === targetExport ) { continue outer; } } } // The exports of entry module are never inlined const usedName = /** @type {string} */ (exportInfo.getUsedName(originalName, chunk.runtime)); /** @type {string | undefined} */ const definition = definitions[usedName]; /** @type {string | undefined} */ const directLocal = definition === undefined && directBindings ? directBindings.get(originalName) : undefined; /** @type {string | undefined} */ let finalName; if (definition) { finalName = definition; } else if (directLocal !== undefined) { // Live binding to the inlined entry's own top-level declaration finalName = directLocal; needExportsDeclaration = true; } else { // Fallback to `__webpack_exports__` property access // when no direct export binding was found finalName = toLocalName(originalName); needExportsDeclaration = true; result.add( `${runtimeTemplate.renderConst()} ${finalName} = ${RuntimeGlobals.exports}${propertyAccess( [usedName] )};\n` ); } if ( // If the name includes `property access` and `call expressions` finalName && (finalName.includes(".") || finalName.includes("[") || finalName.includes("(")) ) { if (exportInfo.isReexport()) { const { data } = codeGenerationResults.get(module, chunk.runtime); const topLevelDeclarations = (data && data.get("topLevelDeclarations")) || (module.buildInfo && module.buildInfo.topLevelDeclarations); if (topLevelDeclarations && topLevelDeclarations.has(originalName)) { const name = toLocalName(originalName); result.add( `${runtimeTemplate.renderConst()} ${name} = ${finalName};\n` ); shortHandedExports.push(`${name} as ${toExportName(originalName)}`); } else { exports.push([originalName, finalName]); } } else { exports.push([originalName, finalName]); } } else { shortHandedExports.push( finalName === originalName ? finalName : `${finalName} as ${toExportName(originalName)}` ); } alreadyRenderedExports.add(originalName); } // Add default export `__webpack_exports__` statement to keep better compatibility if (treatAsCommonJs) { needExportsDeclaration = true; shortHandedExports.push(`${RuntimeGlobals.exports} as default`); } if (shortHandedExports.length > 0) { result.add(`export { ${shortHandedExports.join(", ")} };\n`); } result = this._analyzeUnknownProvidedExports( result, module, moduleGraph, chunk.runtime, exports, alreadyRenderedExports ); for (const [exportName, final] of exports) { if (isDeclarableExportName(exportName)) { result.add( `export ${runtimeTemplate.renderConst()} ${exportName} = ${final};\n` ); continue; } // Bind to a generated name, the export name can't be declared const name = toLocalName(exportName); result.add( `${runtimeTemplate.renderConst()} ${name} = ${final};\nexport { ${name} as ${toExportName(exportName)} };\n` ); } if (!needExportsDeclaration) { renderContext.needExportsDeclaration = false; } return result; } /** * Renders module content. * @param {Source} source source * @param {Module} module module * @param {ModuleRenderContext} renderContext render context * @param {Omit<LibraryContext<T>, "options">} libraryContext context * @returns {Source} source with library export */ renderModuleContent( source, module, { factory, inlinedInIIFE, chunk }, libraryContext ) { const exportsSource = module.buildMeta && module.buildMeta.exportsSourceByRuntime && module.buildMeta.exportsSourceByRuntime.get(getRuntimeKey(chunk.runtime)); // Re-add the module's exports source when rendered in factory // or as an inlined startup module wrapped in an IIFE if ((inlinedInIIFE || factory) && exportsSource) { return new ConcatSource(exportsSource, source); } return source; } } module.exports = ModuleLibraryPlugin;