diff options
Diffstat (limited to 'node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js')
-rw-r--r-- | node_modules/webpack/lib/optimize/AggressiveSplittingPlugin.js | 482 |
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; |