aboutsummaryrefslogtreecommitdiff
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.js482
1 files changed, 287 insertions, 195 deletions
diff --git a/node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js b/node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js
index b6f566a1c..9aec3644b 100644
--- a/node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js
+++ b/node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js
@@ -1,195 +1,287 @@
-/*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
-*/
-"use strict";
-
-const identifierUtils = require("../util/identifier");
-
-function moveModuleBetween(oldChunk, newChunk) {
- return function(module) {
- oldChunk.moveModule(module, newChunk);
- };
-}
-
-function isNotAEntryModule(entryModule) {
- return function(module) {
- return entryModule !== module;
- };
-}
-
-function copyWithReason(obj) {
- const newObj = {};
- Object.keys(obj).forEach((key) => {
- newObj[key] = obj[key];
- });
- if(!newObj.reasons || newObj.reasons.indexOf("aggressive-splitted") < 0)
- newObj.reasons = (newObj.reasons || []).concat("aggressive-splitted");
- return newObj;
-}
-
-class AggressiveSplittingPlugin {
- constructor(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;
- }
- apply(compiler) {
- compiler.plugin("this-compilation", (compilation) => {
- compilation.plugin("optimize-chunks-advanced", (chunks) => {
- // Precompute stuff
- const nameToModuleMap = new Map();
- compilation.modules.forEach(m => {
- const name = identifierUtils.makePathsRelative(compiler.context, m.identifier(), compilation.cache);
- nameToModuleMap.set(name, m);
- });
-
- const savedSplits = compilation.records && compilation.records.aggressiveSplits || [];
- const usedSplits = compilation._aggressiveSplittingSplits ?
- savedSplits.concat(compilation._aggressiveSplittingSplits) : savedSplits;
-
- const minSize = this.options.minSize;
- const maxSize = this.options.maxSize;
- // 1. try to restore to recorded splitting
- for(let j = 0; j < usedSplits.length; j++) {
- const splitData = usedSplits[j];
- const selectedModules = splitData.modules.map(name => nameToModuleMap.get(name));
-
- // Does the modules exist at all?
- if(selectedModules.every(Boolean)) {
-
- // Find all chunks containing all modules in the split
- for(let i = 0; i < chunks.length; i++) {
- const chunk = chunks[i];
-
- // Cheap check if chunk is suitable at all
- if(chunk.getNumberOfModules() < splitData.modules.length)
- continue;
-
- // Check if all modules are in the chunk
- if(selectedModules.every(m => chunk.containsModule(m))) {
-
- // Is chunk identical to the split or do we need to split it?
- if(chunk.getNumberOfModules() > splitData.modules.length) {
- // split the chunk into two parts
- const newChunk = compilation.addChunk();
- selectedModules.forEach(moveModuleBetween(chunk, newChunk));
- chunk.split(newChunk);
- chunk.name = null;
- newChunk._fromAggressiveSplitting = true;
- if(j < savedSplits.length)
- newChunk._fromAggressiveSplittingIndex = j;
- if(splitData.id !== null && splitData.id !== undefined) {
- newChunk.id = splitData.id;
- }
- newChunk.origins = chunk.origins.map(copyWithReason);
- chunk.origins = chunk.origins.map(copyWithReason);
- return true;
- } else { // chunk is identical to the split
- if(j < savedSplits.length)
- chunk._fromAggressiveSplittingIndex = j;
- chunk.name = null;
- if(splitData.id !== null && splitData.id !== undefined) {
- chunk.id = splitData.id;
- }
- }
- }
- }
- }
- }
-
- // 2. for any other chunk which isn't splitted yet, split it
- for(let i = 0; i < chunks.length; i++) {
- const chunk = chunks[i];
- const size = chunk.size(this.options);
- if(size > maxSize && chunk.getNumberOfModules() > 1) {
- const newChunk = compilation.addChunk();
- const modules = chunk.getModules()
- .filter(isNotAEntryModule(chunk.entryModule))
- .sort((a, b) => {
- a = a.identifier();
- b = b.identifier();
- if(a > b) return 1;
- if(a < b) return -1;
- return 0;
- });
- for(let k = 0; k < modules.length; k++) {
- chunk.moveModule(modules[k], newChunk);
- const newSize = newChunk.size(this.options);
- const chunkSize = chunk.size(this.options);
- // break early if it's fine
- if(chunkSize < maxSize && newSize < maxSize && newSize >= minSize && chunkSize >= minSize)
- break;
- if(newSize > maxSize && k === 0) {
- // break if there is a single module which is bigger than maxSize
- break;
- }
- if(newSize > maxSize || chunkSize < minSize) {
- // move it back
- newChunk.moveModule(modules[k], chunk);
- // check if it's fine now
- if(newSize < maxSize && newSize >= minSize && chunkSize >= minSize)
- break;
- }
- }
- if(newChunk.getNumberOfModules() > 0) {
- chunk.split(newChunk);
- chunk.name = null;
- newChunk.origins = chunk.origins.map(copyWithReason);
- chunk.origins = chunk.origins.map(copyWithReason);
- compilation._aggressiveSplittingSplits = (compilation._aggressiveSplittingSplits || []).concat({
- modules: newChunk.mapModules(m => identifierUtils.makePathsRelative(compiler.context, m.identifier(), compilation.cache))
- });
- return true;
- } else {
- chunks.splice(chunks.indexOf(newChunk), 1);
- }
- }
- }
- });
- compilation.plugin("record-hash", (records) => {
- // 3. save to made splittings to records
- const minSize = this.options.minSize;
- if(!records.aggressiveSplits) records.aggressiveSplits = [];
- compilation.chunks.forEach((chunk) => {
- if(chunk.hasEntryModule()) return;
- const size = chunk.size(this.options);
- const incorrectSize = size < minSize;
- const modules = chunk.mapModules(m => identifierUtils.makePathsRelative(compiler.context, m.identifier(), compilation.cache));
- if(typeof chunk._fromAggressiveSplittingIndex === "undefined") {
- if(incorrectSize) return;
- chunk.recorded = true;
- records.aggressiveSplits.push({
- modules: modules,
- hash: chunk.hash,
- id: chunk.id
- });
- } else {
- const splitData = records.aggressiveSplits[chunk._fromAggressiveSplittingIndex];
- if(splitData.hash !== chunk.hash || incorrectSize) {
- if(chunk._fromAggressiveSplitting) {
- chunk._aggressiveSplittingInvalid = true;
- splitData.invalid = true;
- } else {
- splitData.hash = chunk.hash;
- }
- }
- }
- });
- records.aggressiveSplits = records.aggressiveSplits.filter((splitData) => {
- return !splitData.invalid;
- });
- });
- compilation.plugin("need-additional-seal", (callback) => {
- const invalid = compilation.chunks.some((chunk) => {
- return chunk._aggressiveSplittingInvalid;
- });
- if(invalid)
- return true;
- });
- });
- }
-}
-module.exports = AggressiveSplittingPlugin;
+/*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+*/
+"use strict";
+
+const identifierUtils = require("../util/identifier");
+const { intersect } = require("../util/SetHelpers");
+const validateOptions = require("schema-utils");
+const schema = require("../../schemas/plugins/optimize/AggressiveSplittingPlugin.json");
+
+const moveModuleBetween = (oldChunk, newChunk) => {
+ return module => {
+ oldChunk.moveModule(module, newChunk);
+ };
+};
+
+const isNotAEntryModule = entryModule => {
+ return module => {
+ return entryModule !== module;
+ };
+};
+
+class AggressiveSplittingPlugin {
+ constructor(options) {
+ validateOptions(schema, options || {}, "Aggressive Splitting Plugin");
+
+ 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;
+ }
+ }
+ apply(compiler) {
+ compiler.hooks.thisCompilation.tap(
+ "AggressiveSplittingPlugin",
+ compilation => {
+ let needAdditionalSeal = false;
+ let newSplits;
+ let fromAggressiveSplittingSet;
+ let chunkSplitDataMap;
+ compilation.hooks.optimize.tap("AggressiveSplittingPlugin", () => {
+ newSplits = [];
+ fromAggressiveSplittingSet = new Set();
+ chunkSplitDataMap = new Map();
+ });
+ compilation.hooks.optimizeChunksAdvanced.tap(
+ "AggressiveSplittingPlugin",
+ chunks => {
+ // Precompute stuff
+ const nameToModuleMap = new Map();
+ const moduleToNameMap = new Map();
+ for (const m of compilation.modules) {
+ const name = identifierUtils.makePathsRelative(
+ compiler.context,
+ m.identifier(),
+ compilation.cache
+ );
+ 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 = this.options.minSize;
+ const maxSize = 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)
+ const size = selectedModules.reduce(
+ (sum, m) => sum + m.size(),
+ 0
+ );
+ if (size !== splitData.size) return false;
+
+ // get chunks with all modules
+ const selectedChunks = intersect(
+ selectedModules.map(m => new Set(m.chunksIterable))
+ );
+
+ // No relevant chunks found
+ if (selectedChunks.size === 0) return false;
+
+ // The found chunk is already the split or similar
+ if (
+ selectedChunks.size === 1 &&
+ Array.from(selectedChunks)[0].getNumberOfModules() ===
+ 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) {
+ selectedModules.forEach(moveModuleBetween(chunk, newChunk));
+ chunk.split(newChunk);
+ chunk.name = null;
+ }
+ fromAggressiveSplittingSet.add(newChunk);
+ chunkSplitDataMap.set(newChunk, splitData);
+
+ if (splitData.id !== null && splitData.id !== undefined) {
+ newChunk.id = 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 sortedChunks = chunks.slice().sort((a, b) => {
+ const diff1 = b.modulesSize() - a.modulesSize();
+ if (diff1) return diff1;
+ const diff2 = a.getNumberOfModules() - b.getNumberOfModules();
+ if (diff2) return diff2;
+ const modulesA = Array.from(a.modulesIterable);
+ const modulesB = Array.from(b.modulesIterable);
+ modulesA.sort();
+ modulesB.sort();
+ const aI = modulesA[Symbol.iterator]();
+ const bI = modulesB[Symbol.iterator]();
+ // eslint-disable-next-line no-constant-condition
+ while (true) {
+ const aItem = aI.next();
+ const bItem = bI.next();
+ if (aItem.done) return 0;
+ const aModuleIdentifier = aItem.value.identifier();
+ const bModuleIdentifier = bItem.value.identifier();
+ if (aModuleIdentifier > bModuleIdentifier) return -1;
+ if (aModuleIdentifier < bModuleIdentifier) return 1;
+ }
+ });
+ for (const chunk of sortedChunks) {
+ if (fromAggressiveSplittingSet.has(chunk)) continue;
+ const size = chunk.modulesSize();
+ if (size > maxSize && chunk.getNumberOfModules() > 1) {
+ const modules = chunk
+ .getModules()
+ .filter(isNotAEntryModule(chunk.entryModule))
+ .sort((a, b) => {
+ a = a.identifier();
+ b = b.identifier();
+ if (a > b) return 1;
+ if (a < b) return -1;
+ return 0;
+ });
+ 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) {
+ if (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
+ chunk.recorded = true;
+ }
+ }
+
+ // Also add all unused historial 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;