| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- const asyncLib = require("neo-async");
- const Queue = require("./util/Queue");
- const { compareModulesByIdentifier } = require("./util/comparators");
- const createHash = require("./util/createHash");
- /** @import Compiler from "./Compiler" */
- /** @import DependenciesBlock from "./DependenciesBlock" */
- /**
- * @import Dependency, {
- * ExportSpec,
- * ExportsSpec,
- * ExportInfoName
- * } from "./Dependency"
- */
- /** @import ExportsInfo, { RestoreProvidedData } from "./ExportsInfo" */
- /** @import { HashFunction } from "../declarations/WebpackOptions" */
- /** @import LazyBarrelController from "./LazyBarrel" */
- /** @import Module, { BuildInfo } from "./Module" */
- const PLUGIN_NAME = "FlagDependencyExportsPlugin";
- const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`;
- /** @typedef {{ etag: string, data: InstanceType<RestoreProvidedData> }} MemCacheEntry */
- /**
- * @param {Module} module the module
- * @param {LazyBarrelController} lazyBarrelController the compilation's lazy barrel controller
- * @param {HashFunction} hashFunction hash function for long keys
- * @returns {string} the hash
- */
- const getLazyRequestsHash = (module, lazyBarrelController, hashFunction) => {
- const lazyKeys = lazyBarrelController.getLazyRequests(module);
- if (!lazyKeys) return "";
- const joined = [...lazyKeys].join("|");
- if (joined.length < 100) return joined;
- const hash = createHash(hashFunction);
- hash.update(joined);
- return /** @type {string} */ (hash.digest("hex"));
- };
- class FlagDependencyExportsPlugin {
- /**
- * Applies the plugin by registering its hooks on the compiler.
- * @param {Compiler} compiler the compiler instance
- * @returns {void}
- */
- apply(compiler) {
- compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
- const moduleGraph = compilation.moduleGraph;
- const cache = compilation.getCache(PLUGIN_NAME);
- const lazyBarrelController = compilation._lazyBarrelController;
- const hashFunction = compilation.outputOptions.hashFunction;
- compilation.hooks.finishModules.tapAsync(
- PLUGIN_NAME,
- (modules, callback) => {
- const logger = compilation.getLogger(PLUGIN_LOGGER_NAME);
- let statRestoredFromMemCache = 0;
- let statRestoredFromCache = 0;
- let statNoExports = 0;
- let statFlaggedUncached = 0;
- let statNotCached = 0;
- let statQueueItemsProcessed = 0;
- const { moduleMemCaches } = compilation;
- /** @type {Queue<Module>} */
- const queue = new Queue();
- /** @type {Set<Module>} */
- const modulesToAnalyse = new Set();
- // Step 1: Try to restore cached provided export info from cache
- logger.time("restore cached provided exports");
- asyncLib.each(
- /** @type {import("neo-async").IterableCollection<Module>} */ (
- /** @type {unknown} */ (modules)
- ),
- (module, callback) => {
- const exportsInfo = moduleGraph.getExportsInfo(module);
- // If the module doesn't have an exportsType, it's a module
- // without declared exports.
- if (
- (!module.buildMeta || !module.buildMeta.exportsType) &&
- exportsInfo.otherExportsInfo.provided !== null
- ) {
- // It's a module without declared exports
- statNoExports++;
- exportsInfo.setHasProvideInfo();
- exportsInfo.setUnknownExportsProvided();
- return callback();
- }
- // If the module has no hash, it's uncacheable
- if (
- typeof (/** @type {BuildInfo} */ (module.buildInfo).hash) !==
- "string"
- ) {
- statFlaggedUncached++;
- // Determined below, in a stable order
- modulesToAnalyse.add(module);
- exportsInfo.setHasProvideInfo();
- return callback();
- }
- const memCache = moduleMemCaches && moduleMemCaches.get(module);
- /** @type {MemCacheEntry | undefined} */
- const memCacheEntry = memCache && memCache.get(this);
- const hash = getLazyRequestsHash(
- module,
- lazyBarrelController,
- hashFunction
- );
- // validated by the still-lazy request keys: reference-change
- // detection cannot invalidate the mem cache when the un-lazied
- // connections are unsafe-cached (they are skipped when comparing
- // references)
- if (memCacheEntry !== undefined && memCacheEntry.etag === hash) {
- statRestoredFromMemCache++;
- exportsInfo.restoreProvided(memCacheEntry.data);
- return callback();
- }
- const buildHash = /** @type {string} */ (
- /** @type {BuildInfo} */ (module.buildInfo).hash
- );
- cache.get(
- module.identifier(),
- hash ? `${buildHash}|${hash}` : buildHash,
- (err, result) => {
- if (err) return callback(err);
- if (result !== undefined) {
- statRestoredFromCache++;
- exportsInfo.restoreProvided(result);
- } else {
- statNotCached++;
- // Without cached info determine the exports below
- modulesToAnalyse.add(module);
- exportsInfo.setHasProvideInfo();
- }
- callback();
- }
- );
- },
- (err) => {
- logger.timeEnd("restore cached provided exports");
- if (err) return callback(err);
- // Seed the queue with targets before the modules that read
- // them. Enqueueing at the two branches above ordered the work by
- // how fast each cache lookup answered — one is synchronous, the
- // other a `cache.get` callback — so the persistent cache decided
- // it. A module analysed before the module it re-exports from
- // reads exports that are not determined yet and over-estimates,
- // and this loop can add exports info but never retract it, so
- // the over-estimate survives the pass that corrects it. The
- // roots are sorted so the walk is stable, and post-order visits
- // each module after everything it points at.
- /** @type {Set<Module>} */
- const visited = new Set();
- /** @type {Module[]} */
- const stack = [];
- /** @type {Set<Module>} */
- const expanded = new Set();
- for (const root of [...modulesToAnalyse].sort(
- compareModulesByIdentifier
- )) {
- if (visited.has(root)) continue;
- stack.push(root);
- while (stack.length > 0) {
- const current = stack[stack.length - 1];
- if (expanded.has(current)) {
- stack.pop();
- if (!visited.has(current)) {
- visited.add(current);
- if (modulesToAnalyse.has(current)) {
- queue.enqueue(current);
- }
- }
- continue;
- }
- expanded.add(current);
- for (const connection of moduleGraph.getOutgoingConnections(
- current
- )) {
- const target = connection.module;
- // A module restored from cache is already final, so
- // nothing has to be ordered before it and the walk stops
- // there — on an incremental build that is most of them.
- if (
- target !== undefined &&
- target !== null &&
- modulesToAnalyse.has(target) &&
- !expanded.has(target)
- ) {
- stack.push(target);
- }
- }
- }
- }
- /** @type {Set<Module>} */
- const modulesToStore = new Set();
- /** @type {Map<Module, Set<Module>>} */
- const dependencies = new Map();
- /** @type {Module} */
- let module;
- /** @type {ExportsInfo} */
- let exportsInfo;
- /** @type {Map<Dependency, ExportsSpec>} */
- const exportsSpecsFromDependencies = new Map();
- let cacheable = true;
- let changed = false;
- /**
- * Process dependencies block.
- * @param {DependenciesBlock} depBlock the dependencies block
- * @returns {void}
- */
- const processDependenciesBlock = (depBlock) => {
- for (const dep of depBlock.dependencies) {
- processDependency(dep);
- }
- for (const block of depBlock.blocks) {
- processDependenciesBlock(block);
- }
- };
- /**
- * Process dependency.
- * @param {Dependency} dep the dependency
- * @returns {void}
- */
- const processDependency = (dep) => {
- const exportDesc = dep.getExports(moduleGraph);
- if (!exportDesc) return;
- exportsSpecsFromDependencies.set(dep, exportDesc);
- };
- /**
- * Process exports spec.
- * @param {Dependency} dep dependency
- * @param {ExportsSpec} exportDesc info
- * @returns {void}
- */
- const processExportsSpec = (dep, exportDesc) => {
- const exports = exportDesc.exports;
- const globalCanMangle = exportDesc.canMangle;
- const globalFrom = exportDesc.from;
- const globalPriority = exportDesc.priority;
- const globalTerminalBinding =
- exportDesc.terminalBinding || false;
- const globalPure = exportDesc.isPure || false;
- const exportDeps = exportDesc.dependencies;
- if (exportDesc.hideExports) {
- for (const name of exportDesc.hideExports) {
- const exportInfo = exportsInfo.getExportInfo(name);
- exportInfo.unsetTarget(dep);
- }
- }
- if (exports === true) {
- // unknown exports
- if (
- exportsInfo.setUnknownExportsProvided(
- globalCanMangle,
- exportDesc.excludeExports,
- globalFrom && dep,
- globalFrom,
- globalPriority
- )
- ) {
- changed = true;
- }
- } else if (Array.isArray(exports)) {
- /**
- * merge in new exports
- * @param {ExportsInfo} exportsInfo own exports info
- * @param {(ExportSpec | string)[]} exports list of exports
- */
- const mergeExports = (exportsInfo, exports) => {
- for (const exportNameOrSpec of exports) {
- /** @type {ExportInfoName} */
- let name;
- let canMangle = globalCanMangle;
- let terminalBinding = globalTerminalBinding;
- let pure = globalPure;
- /** @type {ExportSpec["exports"]} */
- let exports;
- let from = globalFrom;
- /** @type {ExportSpec["export"]} */
- let fromExport;
- let priority = globalPriority;
- let hidden = false;
- /** @type {ExportSpec["inlined"]} */
- let inlined;
- if (typeof exportNameOrSpec === "string") {
- name = exportNameOrSpec;
- } else {
- name = exportNameOrSpec.name;
- if (exportNameOrSpec.canMangle !== undefined) {
- canMangle = exportNameOrSpec.canMangle;
- }
- if (exportNameOrSpec.export !== undefined) {
- fromExport = exportNameOrSpec.export;
- }
- if (exportNameOrSpec.exports !== undefined) {
- exports = exportNameOrSpec.exports;
- }
- if (exportNameOrSpec.from !== undefined) {
- from = exportNameOrSpec.from;
- }
- if (exportNameOrSpec.priority !== undefined) {
- priority = exportNameOrSpec.priority;
- }
- if (exportNameOrSpec.terminalBinding !== undefined) {
- terminalBinding = exportNameOrSpec.terminalBinding;
- }
- if (exportNameOrSpec.isPure !== undefined) {
- pure = exportNameOrSpec.isPure;
- }
- if (exportNameOrSpec.hidden !== undefined) {
- hidden = exportNameOrSpec.hidden;
- }
- if (exportNameOrSpec.inlined !== undefined) {
- inlined = exportNameOrSpec.inlined;
- }
- }
- const exportInfo = exportsInfo.getExportInfo(name);
- if (
- exportInfo.provided === false ||
- exportInfo.provided === null
- ) {
- exportInfo.provided = true;
- changed = true;
- }
- if (
- exportInfo.canMangleProvide !== false &&
- canMangle === false
- ) {
- exportInfo.canMangleProvide = false;
- changed = true;
- }
- if (
- inlined !== undefined &&
- exportInfo.canInlineProvide === undefined
- ) {
- exportInfo.canInlineProvide = inlined;
- changed = true;
- }
- if (terminalBinding && !exportInfo.terminalBinding) {
- exportInfo.terminalBinding = true;
- changed = true;
- }
- if (pure && exportInfo.pureProvide !== true) {
- exportInfo.pureProvide = true;
- changed = true;
- }
- if (exports) {
- const nestedExportsInfo =
- exportInfo.createNestedExportsInfo();
- mergeExports(
- /** @type {ExportsInfo} */ (nestedExportsInfo),
- exports
- );
- }
- if (
- from &&
- (hidden
- ? exportInfo.unsetTarget(dep)
- : exportInfo.setTarget(
- dep,
- from,
- fromExport === undefined ? [name] : fromExport,
- priority
- ))
- ) {
- changed = true;
- }
- // Recalculate target exportsInfo
- const target = exportInfo.getTarget(moduleGraph);
- /** @type {undefined | ExportsInfo} */
- let targetExportsInfo;
- if (target) {
- const targetModuleExportsInfo =
- moduleGraph.getExportsInfo(target.module);
- targetExportsInfo =
- targetModuleExportsInfo.getNestedExportsInfo(
- target.export
- );
- // add dependency for this module
- const set = dependencies.get(target.module);
- if (set === undefined) {
- dependencies.set(target.module, new Set([module]));
- } else {
- set.add(module);
- }
- }
- if (exportInfo.exportsInfoOwned) {
- if (
- /** @type {ExportsInfo} */
- (exportInfo.exportsInfo).setRedirectNamedTo(
- targetExportsInfo
- )
- ) {
- changed = true;
- }
- } else if (exportInfo.exportsInfo !== targetExportsInfo) {
- exportInfo.exportsInfo = targetExportsInfo;
- changed = true;
- }
- }
- };
- mergeExports(exportsInfo, exports);
- }
- // store dependencies
- if (exportDeps) {
- cacheable = false;
- for (const exportDependency of exportDeps) {
- // add dependency for this module
- const set = dependencies.get(exportDependency);
- if (set === undefined) {
- dependencies.set(exportDependency, new Set([module]));
- } else {
- set.add(module);
- }
- }
- }
- };
- const notifyDependencies = () => {
- const deps = dependencies.get(module);
- if (deps !== undefined) {
- for (const dep of deps) {
- queue.enqueue(dep);
- }
- }
- };
- logger.time("figure out provided exports");
- while (queue.length > 0) {
- module = /** @type {Module} */ (queue.dequeue());
- statQueueItemsProcessed++;
- exportsInfo = moduleGraph.getExportsInfo(module);
- cacheable = true;
- changed = false;
- exportsSpecsFromDependencies.clear();
- moduleGraph.freeze();
- processDependenciesBlock(module);
- moduleGraph.unfreeze();
- for (const [dep, exportsSpec] of exportsSpecsFromDependencies) {
- processExportsSpec(dep, exportsSpec);
- }
- if (cacheable) {
- modulesToStore.add(module);
- }
- if (changed) {
- notifyDependencies();
- }
- }
- logger.timeEnd("figure out provided exports");
- logger.log(
- `${Math.round(
- (100 * (statFlaggedUncached + statNotCached)) /
- (statRestoredFromMemCache +
- statRestoredFromCache +
- statNotCached +
- statFlaggedUncached +
- statNoExports)
- )}% of exports of modules have been determined (${statNoExports} no declared exports, ${statNotCached} not cached, ${statFlaggedUncached} flagged uncacheable, ${statRestoredFromCache} from cache, ${statRestoredFromMemCache} from mem cache, ${
- statQueueItemsProcessed - statNotCached - statFlaggedUncached
- } additional calculations due to dependencies)`
- );
- logger.time("store provided exports into cache");
- asyncLib.each(
- modulesToStore,
- (module, callback) => {
- if (
- typeof (
- /** @type {BuildInfo} */
- (module.buildInfo).hash
- ) !== "string"
- ) {
- // not cacheable
- return callback();
- }
- const cachedData = moduleGraph
- .getExportsInfo(module)
- .getRestoreProvidedData();
- const memCache =
- moduleMemCaches && moduleMemCaches.get(module);
- const hash = getLazyRequestsHash(
- module,
- lazyBarrelController,
- hashFunction
- );
- if (memCache) {
- /** @type {MemCacheEntry} */
- const entry = {
- etag: hash,
- data: cachedData
- };
- memCache.set(this, entry);
- }
- const buildHash = /** @type {string} */ (
- /** @type {BuildInfo} */ (module.buildInfo).hash
- );
- cache.store(
- module.identifier(),
- hash ? `${buildHash}|${hash}` : buildHash,
- cachedData,
- callback
- );
- },
- (err) => {
- logger.timeEnd("store provided exports into cache");
- callback(err);
- }
- );
- }
- );
- }
- );
- /** @type {WeakMap<Module, InstanceType<RestoreProvidedData>>} */
- const providedExportsCache = new WeakMap();
- compilation.hooks.rebuildModule.tap(PLUGIN_NAME, (module) => {
- providedExportsCache.set(
- module,
- moduleGraph.getExportsInfo(module).getRestoreProvidedData()
- );
- });
- compilation.hooks.finishRebuildingModule.tap(PLUGIN_NAME, (module) => {
- moduleGraph.getExportsInfo(module).restoreProvided(
- /** @type {InstanceType<RestoreProvidedData>} */
- (providedExportsCache.get(module))
- );
- });
- });
- }
- }
- module.exports = FlagDependencyExportsPlugin;
|