Use a Set to store modules in a chunk

This commit is contained in:
Tobias Koppers 2017-04-21 10:05:56 +02:00
parent d5d04280c0
commit e1bac32c19
19 changed files with 156 additions and 116 deletions

View File

@ -37,17 +37,7 @@ module.exports = class AsyncDependenciesBlock extends DependenciesBlock {
sortItems() {
super.sortItems();
if(this.chunks) {
this.chunks.sort((a, b) => {
let i = 0;
while(true) { // eslint-disable-line no-constant-condition
if(!a.modules[i] && !b.modules[i]) return 0;
if(!a.modules[i]) return -1;
if(!b.modules[i]) return 1;
if(a.modules[i].id > b.modules[i].id) return 1;
if(a.modules[i].id < b.modules[i].id) return -1;
i++;
}
});
this.chunks.sort((a, b) => a.compareTo(b));
}
}
};

View File

@ -4,6 +4,7 @@
*/
"use strict";
const util = require("util");
const compareLocations = require("./compareLocations");
let debugId = 1000;
@ -20,7 +21,8 @@ class Chunk {
this.ids = null;
this.debugId = debugId++;
this.name = name;
this.modules = [];
this._modules = new Set();
this._modulesIsSorted = true;
this.entrypoints = [];
this.chunks = [];
this.parents = [];
@ -88,7 +90,12 @@ class Chunk {
}
addModule(module) {
return this.addToCollection(this.modules, module);
if(!this._modules.has(module)) {
this._modules.add(module);
this._modulesIsSorted = false;
return true;
}
return false;
}
addBlock(block) {
@ -96,9 +103,7 @@ class Chunk {
}
removeModule(module) {
const idx = this.modules.indexOf(module);
if(idx >= 0) {
this.modules.splice(idx, 1);
if(this._modules.delete(module)) {
module.removeChunk(this);
return true;
}
@ -133,9 +138,72 @@ class Chunk {
});
}
setModules(modules) {
this._modules = new Set(modules);
this._modulesIsSorted = false;
}
getNumberOfModules() {
return this._modules.size;
}
get modulesIterable() {
return this._modules;
}
forEachModule(fn) {
this._modules.forEach(fn);
}
mapModules(fn) {
const modules = this._modules;
const array = new Array(modules.size);
let idx = 0;
for(let module of modules) {
array[idx++] = fn(module, idx, modules);
}
return array;
}
_ensureModulesSorted() {
if(this._modulesIsSorted) return;
this._modules = new Set(Array.from(this._modules).sort((a, b) => {
if(a.identifier() > b.identifier()) return 1;
if(a.identifier() < b.identifier()) return -1;
return 0;
}));
this._modulesIsSorted = true;
}
compareTo(otherChunk) {
this._ensureModulesSorted();
otherChunk._ensureModulesSorted();
if(this._modules.size > otherChunk._modules.size) return -1;
if(this._modules.size < otherChunk._modules.size) return 1;
const a = this._modules[Symbol.iterator]();
const b = otherChunk._modules[Symbol.iterator]();
while(true) { // eslint-disable-line
const aItem = a.next();
const bItem = b.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;
}
}
containsModule(module) {
return this._modules.has(module);
}
getModules() {
return Array.from(this._modules);
}
remove(reason) {
// cleanup modules
this.modules.slice().forEach(module => {
Array.from(this._modules).forEach(module => {
module.removeChunk(this);
});
@ -219,9 +287,9 @@ class Chunk {
return false;
}
const otherChunkModules = otherChunk.modules.slice();
const otherChunkModules = Array.from(otherChunk._modules);
otherChunkModules.forEach(module => otherChunk.moveModule(module, this));
otherChunk.modules.length = 0;
otherChunk._modules.clear();
otherChunk.parents.forEach(parentChunk => parentChunk.replaceChunk(otherChunk, this));
otherChunk.parents.length = 0;
@ -276,14 +344,14 @@ class Chunk {
}
isEmpty() {
return this.modules.length === 0;
return this._modules.size === 0;
}
updateHash(hash) {
hash.update(`${this.id} `);
hash.update(this.ids ? this.ids.join(",") : "");
hash.update(`${this.name || ""} `);
this.modules.forEach(m => m.updateHash(hash));
this._modules.forEach(m => m.updateHash(hash));
}
canBeIntegrated(otherChunk) {
@ -307,8 +375,8 @@ class Chunk {
modulesSize() {
let count = 0;
for(let i = 0; i < this.modules.length; i++) {
count += this.modules[i].size();
for(let module of this._modules) {
count += module.size();
}
return count;
}
@ -325,9 +393,8 @@ class Chunk {
let integratedModulesSize = this.modulesSize();
// only count modules that do not exist in this chunk!
for(let i = 0; i < otherChunk.modules.length; i++) {
const otherModule = otherChunk.modules[i];
if(this.modules.indexOf(otherModule) === -1) {
for(let otherModule of otherChunk._modules) {
if(!this._modules.has(otherModule)) {
integratedModulesSize += otherModule.size();
}
}
@ -356,7 +423,7 @@ class Chunk {
}
sortItems() {
this.modules.sort(byId);
this._modules = new Set(Array.from(this._modules).sort(byId));
this.origins.sort((a, b) => {
const aIdent = a.module.identifier();
const bIdent = b.module.identifier();
@ -373,7 +440,7 @@ class Chunk {
}
toString() {
return `Chunk[${this.modules.join()}]`;
return `Chunk[${Array.from(this._modules).join()}]`;
}
checkConstraints() {
@ -393,4 +460,14 @@ class Chunk {
}
}
Object.defineProperty(Chunk.prototype, "modules", {
configurable: false,
get: util.deprecate(function() {
return Array.from(this._modules);
}, "Chunk.modules is deprecated. Use Chunk.getNumberOfModules/mapModules/forEachModule/containsModule instead."),
set: util.deprecate(function(value) {
this.setModules(value);
}, "Chunk.modules is deprecated. Use Chunk.addModule/removeModule instead.")
});
module.exports = Chunk;

View File

@ -12,7 +12,7 @@ class FlagInitialModulesAsUsedPlugin {
if(!chunk.isInitial()) {
return;
}
chunk.modules.forEach((module) => {
chunk.forEachModule((module) => {
module.usedExports = true;
});
});

View File

@ -54,7 +54,7 @@ HotModuleReplacementPlugin.prototype.apply = function(compiler) {
});
records.chunkModuleIds = {};
this.chunks.forEach(function(chunk) {
records.chunkModuleIds[chunk.id] = chunk.modules.map(function(m) {
records.chunkModuleIds[chunk.id] = chunk.mapModules(function(m) {
return m.id;
});
});
@ -112,11 +112,11 @@ HotModuleReplacementPlugin.prototype.apply = function(compiler) {
chunkId = isNaN(+chunkId) ? chunkId : +chunkId;
var currentChunk = this.chunks.find(chunk => chunk.id === chunkId);
if(currentChunk) {
var newModules = currentChunk.modules.filter(function(module) {
var newModules = currentChunk.mapModules(m => m).filter(function(module) {
return module.hotUpdate;
});
var allModules = {};
currentChunk.modules.forEach(function(module) {
currentChunk.forEachModule(function(module) {
allModules[module.id] = true;
});
var removedModules = records.chunkModuleIds[chunkId].filter(function(id) {

View File

@ -5,6 +5,7 @@
"use strict";
const Template = require("./Template");
const Chunk = require("./Chunk");
module.exports = class HotUpdateChunkTemplate extends Template {
constructor(outputOptions) {
@ -12,11 +13,11 @@ module.exports = class HotUpdateChunkTemplate extends Template {
}
render(id, modules, removedModules, hash, moduleTemplate, dependencyTemplates) {
const modulesSource = this.renderChunkModules({
id: id,
modules: modules,
removedModules: removedModules
}, moduleTemplate, dependencyTemplates);
const hotUpdateChunk = new Chunk();
hotUpdateChunk.id = id;
hotUpdateChunk.setModules(modules);
hotUpdateChunk.removedModules = removedModules;
const modulesSource = this.renderChunkModules(hotUpdateChunk, moduleTemplate, dependencyTemplates);
const core = this.applyPluginsWaterfall("modules", modulesSource, modules, removedModules, moduleTemplate, dependencyTemplates);
const source = this.applyPluginsWaterfall("render", core, modules, removedModules, hash, id, moduleTemplate, dependencyTemplates);
return source;

View File

@ -30,19 +30,24 @@ class LibManifestPlugin {
const manifest = {
name,
type: this.options.type,
content: chunk.modules.reduce((obj, module) => {
content: chunk.mapModules(module => {
if(module.libIdent) {
const ident = module.libIdent({
context: this.options.context || compiler.options.context
});
if(ident) {
obj[ident] = {
id: module.id,
meta: module.meta,
exports: Array.isArray(module.providedExports) ? module.providedExports : undefined
return {
ident,
data: {
id: module.id,
meta: module.meta,
exports: Array.isArray(module.providedExports) ? module.providedExports : undefined
}
};
}
}
}).filter(Boolean).reduce((obj, item) => {
obj[item.ident] = item.data;
return obj;
}, {})
};

View File

@ -328,19 +328,19 @@ class Stats {
entry: chunk.hasRuntime(),
recorded: chunk.recorded,
extraAsync: !!chunk.extraAsync,
size: chunk.modules.reduce((size, module) => size + module.size(), 0),
size: chunk.mapModules(m => m.size()).reduce((size, moduleSize) => size + moduleSize, 0),
names: chunk.name ? [chunk.name] : [],
files: chunk.files.slice(),
hash: chunk.renderedHash,
parents: chunk.parents.map(c => c.id)
};
if(showChunkModules) {
obj.modules = chunk.modules
.slice()
obj.modules = chunk
.mapModules(m => m)
.sort(sortByField("depth"))
.filter(createModuleFilter())
.map(fnModule);
obj.filteredModules = chunk.modules.length - obj.modules.length;
obj.filteredModules = chunk.getNumberOfModules() - obj.modules.length;
obj.modules.sort(sortByField(sortModules));
}
if(showChunkOrigins) {

View File

@ -99,12 +99,12 @@ module.exports = class Template extends Tapable {
renderChunkModules(chunk, moduleTemplate, dependencyTemplates, prefix) {
if(!prefix) prefix = "";
var source = new ConcatSource();
if(chunk.modules.length === 0) {
if(chunk.getNumberOfModules() === 0) {
source.add("[]");
return source;
}
var removedModules = chunk.removedModules;
var allModules = chunk.modules.map(function(module) {
var allModules = chunk.mapModules(function(module) {
return {
id: module.id,
source: moduleTemplate.render(module, dependencyTemplates, chunk)
@ -118,7 +118,7 @@ module.exports = class Template extends Tapable {
});
});
}
var bounds = this.getModulesArrayBounds(chunk.modules);
var bounds = this.getModulesArrayBounds(allModules);
if(bounds) {
// Render a spare array

View File

@ -32,7 +32,7 @@ class UmdMainTemplatePlugin {
apply(compilation) {
const mainTemplate = compilation.mainTemplate;
compilation.templatesPlugin("render-with-entry", function(source, chunk, hash) {
let externals = chunk.modules.filter(m => m.external);
let externals = chunk.mapModules(m => m).filter(m => m.external);
const optionalExternals = [];
let requiredExternals = [];
if(this.optionalAmdExternalAsGlobal) {

View File

@ -6,18 +6,6 @@
const identifierUtils = require("../util/identifier");
function toIndexOf(list) {
return function(item) {
return list.indexOf(item);
};
}
function toChunkModuleIndices(modules) {
return function(idx) {
return modules[idx];
};
}
function moveModuleBetween(oldChunk, newChunk) {
return function(module) {
oldChunk.moveModule(module, newChunk);
@ -62,17 +50,19 @@ class AggressiveSplittingPlugin {
const splitData = usedSplits[j];
for(let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const chunkModuleNames = chunk.modules.map(m => identifierUtils.makePathsRelative(compiler.context, m.identifier()));
if(chunkModuleNames.length < splitData.modules.length)
if(chunk.getNumberOfModules() < splitData.modules.length)
continue;
const moduleIndicies = splitData.modules.map(toIndexOf(chunkModuleNames));
const hasAllModules = moduleIndicies.every((idx) => {
return idx >= 0;
const nameToModuleMap = new Map();
chunk.forEachModule(m => {
const name = identifierUtils.makePathsRelative(compiler.context, m.identifier());
nameToModuleMap.set(name, m);
});
if(hasAllModules) {
if(chunkModuleNames.length > splitData.modules.length) {
const selectedModules = moduleIndicies.map(toChunkModuleIndices(chunk.modules));
const selectedModules = splitData.modules.map(name => nameToModuleMap.get(name));
if(selectedModules.every(Boolean)) {
if(chunk.getNumberOfModules() > splitData.modules.length) {
const newChunk = compilation.addChunk();
selectedModules.forEach(moveModuleBetween(chunk, newChunk));
chunk.split(newChunk);
@ -101,9 +91,9 @@ class AggressiveSplittingPlugin {
for(let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const size = chunk.size(this.options);
if(size > maxSize && chunk.modules.length > 1) {
if(size > maxSize && chunk.getNumberOfModules() > 1) {
const newChunk = compilation.addChunk();
const modules = chunk.modules
const modules = chunk.mapModules(m => m)
.filter(isNotAEntryModule(chunk.entryModule))
.sort((a, b) => {
a = a.identifier();
@ -131,13 +121,13 @@ class AggressiveSplittingPlugin {
break;
}
}
if(newChunk.modules.length > 0) {
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.modules.map(m => identifierUtils.makePathsRelative(compiler.context, m.identifier()))
modules: newChunk.mapModules(m => identifierUtils.makePathsRelative(compiler.context, m.identifier()))
});
return true;
} else {
@ -154,7 +144,7 @@ class AggressiveSplittingPlugin {
if(chunk.hasEntryModule()) return;
const size = chunk.size(this.options);
const incorrectSize = size < minSize;
const modules = chunk.modules.map(m => identifierUtils.makePathsRelative(compiler.context, m.identifier()));
const modules = chunk.mapModules(m => identifierUtils.makePathsRelative(compiler.context, m.identifier()));
if(typeof chunk._fromAggressiveSplittingIndex === "undefined") {
if(incorrectSize) return;
chunk.recorded = true;

View File

@ -275,7 +275,7 @@ Take a look at the "name"/"names" or async/children option.`);
// count how many chunks contain a module
const commonModulesToCountMap = usedChunks.reduce((map, chunk) => {
for(let module of chunk.modules) {
for(let module of chunk.modulesIterable) {
const count = map.has(module) ? map.get(module) : 0;
map.set(module, count + 1);
}

View File

@ -11,7 +11,7 @@ class EnsureChunkConditionsPlugin {
compilation.plugin(["optimize-chunks-basic", "optimize-extracted-chunks-basic"], (chunks) => {
let changed = false;
chunks.forEach((chunk) => {
chunk.modules.slice().forEach((module) => {
chunk.forEachModule((module) => {
if(!module.chunkCondition) return;
if(!module.chunkCondition(chunk)) {
const usedChunks = module._EnsureChunkConditionsPlugin_usedChunks = (module._EnsureChunkConditionsPlugin_usedChunks || []).concat(chunk);

View File

@ -17,13 +17,13 @@ class FlagIncludedChunksPlugin {
// instead of swapping A and B just bail
// as we loop twice the current A will be B and B then A
if(chunkA.modules.length < chunkB.modules.length) return;
if(chunkA.getNumberOfModules() < chunkB.getNumberOfModules()) return;
if(chunkB.modules.length === 0) return;
if(chunkB.getNumberOfModules() === 0) return;
// is chunkB in chunkA?
for(let i = 0; i < chunkB.modules.length; i++) {
if(chunkA.modules.indexOf(chunkB.modules[i]) < 0) return;
for(let m of chunkB.modulesIterable) {
if(!chunkA.containsModule(m)) return;
}
chunkA.ids.push(chunkB.id);
});

View File

@ -5,7 +5,7 @@
"use strict";
function getChunkIdentifier(chunk) {
return chunk.modules.map((m) => {
return chunk.mapModules((m) => {
return m.identifier();
}).sort().join(", ");
}

View File

@ -80,13 +80,6 @@ class OccurrenceOrderPlugin {
function occurs(c) {
return c.blocks.length;
}
chunks.forEach((c) => {
c.modules.sort((a, b) => {
if(a.identifier() > b.identifier()) return 1;
if(a.identifier() < b.identifier()) return -1;
return 0;
});
});
chunks.sort((a, b) => {
const aEntryOccurs = occursInEntry(a);
const bEntryOccurs = occursInEntry(b);
@ -96,13 +89,7 @@ class OccurrenceOrderPlugin {
const bOccurs = occurs(b);
if(aOccurs > bOccurs) return -1;
if(aOccurs < bOccurs) return 1;
if(a.modules.length > b.modules.length) return -1;
if(a.modules.length < b.modules.length) return 1;
for(let i = 0; i < a.modules.length; i++) {
if(a.modules[i].identifier() > b.modules[i].identifier()) return -1;
if(a.modules[i].identifier() < b.modules[i].identifier()) return 1;
}
return 0;
return a.compareTo(b);
});
// TODO refactor to Map
chunks.forEach((c) => {

View File

@ -4,18 +4,8 @@
*/
"use strict";
function chunkContainsModule(chunk, module) {
const chunks = module.chunks;
const modules = chunk.modules;
if(chunks.length < modules.length) {
return chunks.indexOf(chunk) >= 0;
} else {
return modules.indexOf(module) >= 0;
}
}
function hasModule(chunk, module, checkedChunks) {
if(chunkContainsModule(chunk, module)) return [chunk];
if(chunk.containsModule(module)) return [chunk];
if(chunk.parents.length === 0) return false;
return allHaveModule(chunk.parents.filter((c) => {
return checkedChunks.indexOf(c) < 0;
@ -67,7 +57,7 @@ class RemoveParentModulesPlugin {
// TODO consider Map when performance has improved https://gist.github.com/sokra/b36098368da7b8f6792fd7c85fca6311
var cache = Object.create(null);
var modules = chunk.modules.slice();
var modules = chunk.getModules();
for(var i = 0; i < modules.length; i++) {
var module = modules[i];

View File

@ -102,7 +102,7 @@ describe("Chunk", () => {
});
describe("and the chunk does contain this module", function() {
beforeEach(function() {
ChunkInstance.modules = [module];
ChunkInstance._modules = new Set([module]);
});
it("calls module.removeChunk with itself and returns true", function() {
ChunkInstance.removeModule(module).should.eql(true);

View File

@ -1,4 +1,4 @@
Hash: 57bbddba5221b9ac4a33
Hash: 3605a628ea012f7d12ca
Time: Xms
Asset Size Chunks Chunk Names
fc930a2adf8206ea2dc5.js 1.94 kB 0 [emitted]
@ -6,10 +6,10 @@ cd45585186d59208602b.js 1.96 kB 1 [emitted]
6b94c231e016c5aaccdb.js 1.94 kB 2 [emitted]
fd0985cee894c4f3f1a6.js 1.94 kB 3 [emitted]
d9fc46873c8ea924b895.js 979 bytes 4 [emitted]
beecea47f9a8ded3c298.js 7.63 kB 6 [emitted] main
a773fee259e5a284dea9.js 7.63 kB 6 [emitted] main
b08c507d4e1e05cbab45.js 985 bytes 9 [emitted]
5d50e858fe6e559aa47c.js 977 bytes 11 [emitted]
Entrypoint main = beecea47f9a8ded3c298.js
Entrypoint main = a773fee259e5a284dea9.js
chunk {0} fc930a2adf8206ea2dc5.js 1.8 kB {6}
> aggressive-splitted duplicate [9] (webpack)/test/statsCases/aggressive-splitting-on-demand/index.js 4:0-51
> aggressive-splitted duplicate [9] (webpack)/test/statsCases/aggressive-splitting-on-demand/index.js 5:0-44
@ -36,7 +36,7 @@ chunk {4} d9fc46873c8ea924b895.js 899 bytes {6}
> aggressive-splitted duplicate [9] (webpack)/test/statsCases/aggressive-splitting-on-demand/index.js 2:0-23
> aggressive-splitted duplicate [9] (webpack)/test/statsCases/aggressive-splitting-on-demand/index.js 3:0-30
[2] (webpack)/test/statsCases/aggressive-splitting-on-demand/c.js 899 bytes {4} [built]
chunk {6} beecea47f9a8ded3c298.js (main) 248 bytes [entry]
chunk {6} a773fee259e5a284dea9.js (main) 248 bytes [entry]
> main [9] (webpack)/test/statsCases/aggressive-splitting-on-demand/index.js
[9] (webpack)/test/statsCases/aggressive-splitting-on-demand/index.js 248 bytes {6} [built]
chunk {9} b08c507d4e1e05cbab45.js 899 bytes {6}

View File

@ -12,13 +12,13 @@ module.exports = {
if(chunk.name) {
return chunk.name;
}
const modulesToName = (mods) => mods.map((mod) => {
const chunkModulesToName = (chunk) => chunk.mapModules((mod) => {
const rs = new RequestShortener(mod.context);
return rs.shorten(mod.request).replace(/[.\/\\]/g, "_");
}).join("-");
if(chunk.modules.length > 0) {
return `chunk-containing-${modulesToName(chunk.modules)}`;
if(chunk.getNumberOfModules() > 0) {
return `chunk-containing-${chunkModulesToName(chunk)}`;
}
return null;