summaryrefslogtreecommitdiff
path: root/node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js')
-rw-r--r--node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js334
1 files changed, 334 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js b/node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js
new file mode 100644
index 00000000..457f61d2
--- /dev/null
+++ b/node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js
@@ -0,0 +1,334 @@
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+
+"use strict";
+
+const { STAGE_ADVANCED } = require("../OptimizationStages");
+const { intersect } = require("../util/SetHelpers");
+const {
+ compareModulesByIdentifier,
+ compareChunks
+} = require("../util/comparators");
+const createSchemaValidation = require("../util/create-schema-validation");
+const identifierUtils = require("../util/identifier");
+
+/** @typedef {import("../../declarations/plugins/optimize/AggressiveSplittingPlugin").AggressiveSplittingPluginOptions} AggressiveSplittingPluginOptions */
+/** @typedef {import("../Chunk")} Chunk */
+/** @typedef {import("../ChunkGraph")} ChunkGraph */
+/** @typedef {import("../Compiler")} Compiler */
+/** @typedef {import("../Module")} Module */
+
+const validate = createSchemaValidation(
+ require("../../schemas/plugins/optimize/AggressiveSplittingPlugin.check.js"),
+ () =>
+ require("../../schemas/plugins/optimize/AggressiveSplittingPlugin.json"),
+ {
+ name: "Aggressive Splitting Plugin",
+ baseDataPath: "options"
+ }
+);
+
+/**
+ * @param {ChunkGraph} chunkGraph the chunk graph
+ * @param {Chunk} oldChunk the old chunk
+ * @param {Chunk} newChunk the new chunk
+ * @returns {(module: Module) => void} function to move module between chunks
+ */
+const moveModuleBetween = (chunkGraph, oldChunk, newChunk) => module => {
+ chunkGraph.disconnectChunkAndModule(oldChunk, module);
+ chunkGraph.connectChunkAndModule(newChunk, module);
+};
+
+/**
+ * @param {ChunkGraph} chunkGraph the chunk graph
+ * @param {Chunk} chunk the chunk
+ * @returns {function(Module): boolean} filter for entry module
+ */
+const isNotAEntryModule = (chunkGraph, chunk) => module =>
+ !chunkGraph.isEntryModuleInChunk(module, chunk);
+
+/** @type {WeakSet<Chunk>} */
+const recordedChunks = new WeakSet();
+
+class AggressiveSplittingPlugin {
+ /**
+ * @param {AggressiveSplittingPluginOptions=} options options object
+ */
+ constructor(options = {}) {
+ validate(options);
+
+ this.options = options;
+ if (typeof this.options.minSize !== "number") {
+ this.options.minSize = 30 * 1024;
+ }
+ if (typeof this.options.maxSize !== "number") {
+ this.options.maxSize = 50 * 1024;
+ }
+ if (typeof this.options.chunkOverhead !== "number") {
+ this.options.chunkOverhead = 0;
+ }
+ if (typeof this.options.entryChunkMultiplicator !== "number") {
+ this.options.entryChunkMultiplicator = 1;
+ }
+ }
+
+ /**
+ * @param {Chunk} chunk the chunk to test
+ * @returns {boolean} true if the chunk was recorded
+ */
+ static wasChunkRecorded(chunk) {
+ return recordedChunks.has(chunk);
+ }
+
+ /**
+ * Apply the plugin
+ * @param {Compiler} compiler the compiler instance
+ * @returns {void}
+ */
+ apply(compiler) {
+ compiler.hooks.thisCompilation.tap(
+ "AggressiveSplittingPlugin",
+ compilation => {
+ let needAdditionalSeal = false;
+ let newSplits;
+ /** @type {Set<Chunk>} */
+ let fromAggressiveSplittingSet;
+ /** @type {Map<Chunk, TODO>} */
+ let chunkSplitDataMap;
+ compilation.hooks.optimize.tap("AggressiveSplittingPlugin", () => {
+ newSplits = [];
+ fromAggressiveSplittingSet = new Set();
+ chunkSplitDataMap = new Map();
+ });
+ compilation.hooks.optimizeChunks.tap(
+ {
+ name: "AggressiveSplittingPlugin",
+ stage: STAGE_ADVANCED
+ },
+ chunks => {
+ const chunkGraph = compilation.chunkGraph;
+ // Precompute stuff
+ const nameToModuleMap = new Map();
+ const moduleToNameMap = new Map();
+ const makePathsRelative =
+ identifierUtils.makePathsRelative.bindContextCache(
+ compiler.context,
+ compiler.root
+ );
+ for (const m of compilation.modules) {
+ const name = makePathsRelative(m.identifier());
+ nameToModuleMap.set(name, m);
+ moduleToNameMap.set(m, name);
+ }
+
+ // Check used chunk ids
+ const usedIds = new Set();
+ for (const chunk of chunks) {
+ usedIds.add(chunk.id);
+ }
+
+ const recordedSplits =
+ (compilation.records && compilation.records.aggressiveSplits) ||
+ [];
+ const usedSplits = newSplits
+ ? recordedSplits.concat(newSplits)
+ : recordedSplits;
+
+ const minSize = /** @type {number} */ (this.options.minSize);
+ const maxSize = /** @type {number} */ (this.options.maxSize);
+
+ const applySplit = splitData => {
+ // Cannot split if id is already taken
+ if (splitData.id !== undefined && usedIds.has(splitData.id)) {
+ return false;
+ }
+
+ // Get module objects from names
+ const selectedModules = splitData.modules.map(name =>
+ nameToModuleMap.get(name)
+ );
+
+ // Does the modules exist at all?
+ if (!selectedModules.every(Boolean)) return false;
+
+ // Check if size matches (faster than waiting for hash)
+ let size = 0;
+ for (const m of selectedModules) size += m.size();
+ if (size !== splitData.size) return false;
+
+ // get chunks with all modules
+ const selectedChunks = intersect(
+ selectedModules.map(
+ m => new Set(chunkGraph.getModuleChunksIterable(m))
+ )
+ );
+
+ // No relevant chunks found
+ if (selectedChunks.size === 0) return false;
+
+ // The found chunk is already the split or similar
+ if (
+ selectedChunks.size === 1 &&
+ chunkGraph.getNumberOfChunkModules(
+ Array.from(selectedChunks)[0]
+ ) === selectedModules.length
+ ) {
+ const chunk = Array.from(selectedChunks)[0];
+ if (fromAggressiveSplittingSet.has(chunk)) return false;
+ fromAggressiveSplittingSet.add(chunk);
+ chunkSplitDataMap.set(chunk, splitData);
+ return true;
+ }
+
+ // split the chunk into two parts
+ const newChunk = compilation.addChunk();
+ newChunk.chunkReason = "aggressive splitted";
+ for (const chunk of selectedChunks) {
+ for (const module of selectedModules) {
+ moveModuleBetween(chunkGraph, chunk, newChunk)(module);
+ }
+ chunk.split(newChunk);
+ chunk.name = null;
+ }
+ fromAggressiveSplittingSet.add(newChunk);
+ chunkSplitDataMap.set(newChunk, splitData);
+
+ if (splitData.id !== null && splitData.id !== undefined) {
+ newChunk.id = splitData.id;
+ newChunk.ids = [splitData.id];
+ }
+ return true;
+ };
+
+ // try to restore to recorded splitting
+ let changed = false;
+ for (let j = 0; j < usedSplits.length; j++) {
+ const splitData = usedSplits[j];
+ if (applySplit(splitData)) changed = true;
+ }
+
+ // for any chunk which isn't splitted yet, split it and create a new entry
+ // start with the biggest chunk
+ const cmpFn = compareChunks(chunkGraph);
+ const sortedChunks = Array.from(chunks).sort((a, b) => {
+ const diff1 =
+ chunkGraph.getChunkModulesSize(b) -
+ chunkGraph.getChunkModulesSize(a);
+ if (diff1) return diff1;
+ const diff2 =
+ chunkGraph.getNumberOfChunkModules(a) -
+ chunkGraph.getNumberOfChunkModules(b);
+ if (diff2) return diff2;
+ return cmpFn(a, b);
+ });
+ for (const chunk of sortedChunks) {
+ if (fromAggressiveSplittingSet.has(chunk)) continue;
+ const size = chunkGraph.getChunkModulesSize(chunk);
+ if (
+ size > maxSize &&
+ chunkGraph.getNumberOfChunkModules(chunk) > 1
+ ) {
+ const modules = chunkGraph
+ .getOrderedChunkModules(chunk, compareModulesByIdentifier)
+ .filter(isNotAEntryModule(chunkGraph, chunk));
+ const selectedModules = [];
+ let selectedModulesSize = 0;
+ for (let k = 0; k < modules.length; k++) {
+ const module = modules[k];
+ const newSize = selectedModulesSize + module.size();
+ if (newSize > maxSize && selectedModulesSize >= minSize) {
+ break;
+ }
+ selectedModulesSize = newSize;
+ selectedModules.push(module);
+ }
+ if (selectedModules.length === 0) continue;
+ const splitData = {
+ modules: selectedModules
+ .map(m => moduleToNameMap.get(m))
+ .sort(),
+ size: selectedModulesSize
+ };
+
+ if (applySplit(splitData)) {
+ newSplits = (newSplits || []).concat(splitData);
+ changed = true;
+ }
+ }
+ }
+ if (changed) return true;
+ }
+ );
+ compilation.hooks.recordHash.tap(
+ "AggressiveSplittingPlugin",
+ records => {
+ // 4. save made splittings to records
+ const allSplits = new Set();
+ const invalidSplits = new Set();
+
+ // Check if some splittings are invalid
+ // We remove invalid splittings and try again
+ for (const chunk of compilation.chunks) {
+ const splitData = chunkSplitDataMap.get(chunk);
+ if (
+ splitData !== undefined &&
+ splitData.hash &&
+ chunk.hash !== splitData.hash
+ ) {
+ // Split was successful, but hash doesn't equal
+ // We can throw away the split since it's useless now
+ invalidSplits.add(splitData);
+ }
+ }
+
+ if (invalidSplits.size > 0) {
+ records.aggressiveSplits = records.aggressiveSplits.filter(
+ splitData => !invalidSplits.has(splitData)
+ );
+ needAdditionalSeal = true;
+ } else {
+ // set hash and id values on all (new) splittings
+ for (const chunk of compilation.chunks) {
+ const splitData = chunkSplitDataMap.get(chunk);
+ if (splitData !== undefined) {
+ splitData.hash = chunk.hash;
+ splitData.id = chunk.id;
+ allSplits.add(splitData);
+ // set flag for stats
+ recordedChunks.add(chunk);
+ }
+ }
+
+ // Also add all unused historical splits (after the used ones)
+ // They can still be used in some future compilation
+ const recordedSplits =
+ compilation.records && compilation.records.aggressiveSplits;
+ if (recordedSplits) {
+ for (const splitData of recordedSplits) {
+ if (!invalidSplits.has(splitData)) allSplits.add(splitData);
+ }
+ }
+
+ // record all splits
+ records.aggressiveSplits = Array.from(allSplits);
+
+ needAdditionalSeal = false;
+ }
+ }
+ );
+ compilation.hooks.needAdditionalSeal.tap(
+ "AggressiveSplittingPlugin",
+ () => {
+ if (needAdditionalSeal) {
+ needAdditionalSeal = false;
+ return true;
+ }
+ }
+ );
+ }
+ );
+ }
+}
+module.exports = AggressiveSplittingPlugin;