webpack/lib/FlagDependencyExportsPlugin.js

374 lines
11 KiB
JavaScript
Raw Normal View History

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
2018-07-30 23:08:51 +08:00
"use strict";
const asyncLib = require("neo-async");
const { equals } = require("./util/ArrayHelpers");
const Queue = require("./util/Queue");
2018-07-28 03:12:09 +08:00
/** @typedef {import("./Compiler")} Compiler */
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
/** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./Dependency").ExportSpec} ExportSpec */
/** @typedef {import("./ExportsInfo")} ExportsInfo */
/** @typedef {import("./Module")} Module */
2018-07-28 03:12:09 +08:00
class FlagDependencyExportsPlugin {
2018-07-28 03:12:09 +08:00
/**
2020-04-23 16:48:36 +08:00
* Apply the plugin
2018-07-28 03:12:09 +08:00
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
2018-02-25 09:00:20 +08:00
compiler.hooks.compilation.tap(
"FlagDependencyExportsPlugin",
compilation => {
const moduleGraph = compilation.moduleGraph;
const cache = compilation.getCache("FlagDependencyExportsPlugin");
compilation.hooks.finishModules.tapAsync(
2018-02-25 09:00:20 +08:00
"FlagDependencyExportsPlugin",
(modules, callback) => {
2020-01-30 18:34:33 +08:00
const logger = compilation.getLogger(
"webpack.FlagDependencyExportsPlugin"
);
/** @type {Queue<Module>} */
2018-02-25 09:00:20 +08:00
const queue = new Queue();
// Step 1: Try to restore cached provided export info from cache
2020-01-30 18:34:33 +08:00
logger.time("restore cached provided exports");
asyncLib.each(
modules,
(module, callback) => {
if (
module.buildInfo.cacheable !== true ||
typeof module.buildInfo.hash !== "string"
) {
// Enqueue uncacheable module for determining the exports
queue.enqueue(module);
moduleGraph.getExportsInfo(module).setHasProvideInfo();
return callback();
2018-02-25 09:00:20 +08:00
}
cache.get(
module.identifier(),
module.buildInfo.hash,
(err, result) => {
if (err) return callback(err);
if (result !== undefined) {
moduleGraph
.getExportsInfo(module)
.restoreProvided(result);
} else {
// Without cached info enqueue module for determining the exports
queue.enqueue(module);
moduleGraph.getExportsInfo(module).setHasProvideInfo();
}
callback();
2018-02-25 09:00:20 +08:00
}
);
},
err => {
2020-01-30 18:34:33 +08:00
logger.timeEnd("restore cached provided exports");
if (err) return callback(err);
/** @type {Set<Module>} */
const modulesToStore = new Set();
/** @type {Map<Module, Set<Module>>} */
const dependencies = new Map();
/** @type {Module} */
let module;
/** @type {ExportsInfo} */
let exportsInfo;
let cacheable = true;
let changed = false;
/**
* @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);
}
};
/**
* @param {Dependency} dep the dependency
* @returns {void}
*/
const processDependency = dep => {
const exportDesc = dep.getExports(moduleGraph);
if (!exportDesc) return;
const exports = exportDesc.exports;
const canMangle = exportDesc.canMangle;
const terminalBinding = exportDesc.terminalBinding || false;
const exportDeps = exportDesc.dependencies;
if (exports === true) {
// unknown exports
if (
exportsInfo.setUnknownExportsProvided(
canMangle,
exportDesc.excludeExports
)
) {
changed = true;
}
} else if (Array.isArray(exports)) {
/**
* @param {Module} module from module
* @param {string[]=} exportName export name accessed
* @returns {{module: Module, export: string[]}=} resolved target
*/
const resolveTarget = (module, exportName) => {
const set = new Set();
for (;;) {
if (!exportName) break;
const exportsInfo = moduleGraph.getExportsInfo(module);
const exportInfo = exportsInfo.getReadOnlyExportInfoRecursive(
exportName
);
if (!exportInfo) break;
if (exportInfo.target) {
// check circular
if (set.has(exportInfo.target)) return undefined;
set.add(exportInfo.target);
module = exportInfo.target.module;
exportName = exportInfo.target.export;
} else {
break;
}
}
return { module, export: exportName };
};
/**
* 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) {
if (typeof exportNameOrSpec === "string") {
const exportInfo = exportsInfo.getExportInfo(
exportNameOrSpec
);
if (exportInfo.provided === false) {
exportInfo.provided = true;
2019-06-12 20:00:34 +08:00
changed = true;
}
if (
canMangle === false &&
exportInfo.canMangleProvide !== false
) {
exportInfo.canMangleProvide = false;
changed = true;
}
if (terminalBinding && !exportInfo.terminalBinding) {
exportInfo.terminalBinding = true;
changed = true;
}
} else {
const exportInfo = exportsInfo.getExportInfo(
exportNameOrSpec.name
);
if (exportInfo.provided === false) {
exportInfo.provided = true;
changed = true;
}
if (
exportInfo.canMangleProvide !== false &&
(exportNameOrSpec.canMangle === false ||
(canMangle === false &&
exportNameOrSpec.canMangle === undefined))
) {
exportInfo.canMangleProvide = false;
changed = true;
}
if (
(exportNameOrSpec.terminalBinding !== undefined
? exportNameOrSpec.terminalBinding
: terminalBinding) &&
!exportInfo.terminalBinding
) {
exportInfo.terminalBinding = true;
changed = true;
}
if (exportNameOrSpec.exports) {
const nestedExportsInfo = exportInfo.createNestedExportsInfo();
mergeExports(
nestedExportsInfo,
exportNameOrSpec.exports
);
}
const target =
exportNameOrSpec.from &&
resolveTarget(
exportNameOrSpec.from,
exportNameOrSpec.export
);
const oldTarget = exportInfo.target;
if (
target &&
(!oldTarget ||
oldTarget.module !== target.module ||
(target.export
? !oldTarget.export ||
!equals(oldTarget.export, target.export)
: oldTarget.export))
) {
exportInfo.target = target;
changed = true;
const targetModuleExportsInfo =
target &&
moduleGraph.getExportsInfo(target.module);
const targetExportsInfo =
targetModuleExportsInfo &&
targetModuleExportsInfo.getNestedExportsInfo(
target.export
);
if (targetExportsInfo) {
if (exportNameOrSpec.exports) {
const nestedExportsInfo =
exportInfo.exportsInfo;
if (
nestedExportsInfo.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);
}
}
};
2020-01-30 18:34:33 +08:00
logger.time("figure out provided exports");
while (queue.length > 0) {
module = queue.dequeue();
exportsInfo = moduleGraph.getExportsInfo(module);
if (exportsInfo.otherExportsInfo.provided !== null) {
if (!module.buildMeta || !module.buildMeta.exportsType) {
// It's a module without declared exports
exportsInfo.setUnknownExportsProvided();
modulesToStore.add(module);
notifyDependencies();
} else {
// It's a module with declared exports
cacheable = true;
changed = false;
processDependenciesBlock(module);
if (cacheable) {
modulesToStore.add(module);
}
if (changed) {
notifyDependencies();
}
}
}
2018-02-25 09:00:20 +08:00
}
2020-01-30 18:34:33 +08:00
logger.timeEnd("figure out provided exports");
2020-01-30 18:34:33 +08:00
logger.time("store provided exports into cache");
asyncLib.each(
modulesToStore,
(module, callback) => {
if (
module.buildInfo.cacheable !== true ||
typeof module.buildInfo.hash !== "string"
) {
// not cacheable
return callback();
}
cache.store(
module.identifier(),
module.buildInfo.hash,
moduleGraph
.getExportsInfo(module)
.getRestoreProvidedData(),
callback
);
},
2020-01-30 18:34:33 +08:00
err => {
logger.timeEnd("store provided exports into cache");
callback(err);
}
);
2018-02-25 09:00:20 +08:00
}
);
}
2018-02-25 09:00:20 +08:00
);
/** @type {WeakMap<Module, any>} */
2018-02-25 09:00:20 +08:00
const providedExportsCache = new WeakMap();
compilation.hooks.rebuildModule.tap(
"FlagDependencyExportsPlugin",
module => {
providedExportsCache.set(
module,
moduleGraph.getExportsInfo(module).getRestoreProvidedData()
);
2018-02-25 09:00:20 +08:00
}
);
compilation.hooks.finishRebuildingModule.tap(
"FlagDependencyExportsPlugin",
module => {
moduleGraph
.getExportsInfo(module)
.restoreProvided(providedExportsCache.get(module));
2018-02-25 09:00:20 +08:00
}
);
}
);
}
}
module.exports = FlagDependencyExportsPlugin;