Skip to content

Commit 803664e

Browse files
authored
Merge pull request webpack#6049 from webpack/performance/improvements
Performance improvements
2 parents caa20ea + 2acd0d4 commit 803664e

18 files changed

Lines changed: 207 additions & 107 deletions

lib/Chunk.js

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -536,43 +536,64 @@ class Chunk {
536536
}
537537

538538
getChunkMaps(includeEntries, realHash) {
539-
const chunksProcessed = new Set();
540539
const chunkHashMap = Object.create(null);
541540
const chunkNameMap = Object.create(null);
542-
const addChunk = chunk => {
543-
if(chunksProcessed.has(chunk)) return;
544-
chunksProcessed.add(chunk);
541+
542+
const queue = [this];
543+
const chunksEnqueued = new Set([this]);
544+
545+
while(queue.length > 0) {
546+
const chunk = queue.pop();
545547
if(!chunk.hasRuntime() || includeEntries) {
546548
chunkHashMap[chunk.id] = realHash ? chunk.hash : chunk.renderedHash;
547549
if(chunk.name)
548550
chunkNameMap[chunk.id] = chunk.name;
549551
}
550-
chunk._chunks.forEach(addChunk);
551-
};
552-
addChunk(this);
552+
for(const child of chunk.chunksIterable) {
553+
if(chunksEnqueued.has(child)) continue;
554+
chunksEnqueued.add(child);
555+
queue.push(child);
556+
}
557+
}
558+
553559
return {
554560
hash: chunkHashMap,
555561
name: chunkNameMap
556562
};
557563
}
558564

559565
getChunkModuleMaps(includeEntries, filterFn) {
560-
const chunksProcessed = new Set();
561566
const chunkModuleIdMap = Object.create(null);
562567
const chunkModuleHashMap = Object.create(null);
563-
(function addChunk(chunk) {
564-
if(chunksProcessed.has(chunk)) return;
565-
chunksProcessed.add(chunk);
568+
569+
const chunksEnqueued = new Set([this]);
570+
const queue = [this];
571+
572+
while(queue.length > 0) {
573+
const chunk = queue.pop();
566574
if(!chunk.hasRuntime() || includeEntries) {
567-
const array = chunk.getModules().filter(filterFn);
568-
if(array.length > 0)
569-
chunkModuleIdMap[chunk.id] = array.map(m => m.id).sort();
570-
for(const m of array) {
571-
chunkModuleHashMap[m.id] = m.renderedHash;
575+
let array = undefined;
576+
for(const module of chunk.modulesIterable) {
577+
if(filterFn(module)) {
578+
if(array === undefined) {
579+
array = [];
580+
chunkModuleIdMap[chunk.id] = array;
581+
}
582+
array.push(module.id);
583+
chunkModuleHashMap[module.id] = module.renderedHash;
584+
}
585+
}
586+
if(array !== undefined) {
587+
array.sort();
572588
}
573589
}
574-
chunk._chunks.forEach(addChunk);
575-
}(this));
590+
for(const child of chunk.chunksIterable) {
591+
if(chunksEnqueued.has(child)) continue;
592+
chunksEnqueued.add(child);
593+
queue.push(child);
594+
}
595+
}
596+
576597
return {
577598
id: chunkModuleIdMap,
578599
hash: chunkModuleHashMap
@@ -585,8 +606,9 @@ class Chunk {
585606

586607
while(queue.length > 0) {
587608
const chunk = queue.pop();
588-
if(chunk.getModules().some(filterFn))
589-
return true;
609+
for(const module of chunk.modulesIterable)
610+
if(filterFn(module))
611+
return true;
590612
for(const next of chunk.chunksIterable) {
591613
if(!chunksProcessed.has(next)) {
592614
chunksProcessed.add(next);

lib/Compilation.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ class Compilation extends Tapable {
170170
this.compiler = compiler;
171171
this.resolverFactory = compiler.resolverFactory;
172172
this.inputFileSystem = compiler.inputFileSystem;
173+
this.requestShortener = compiler.requestShortener;
173174

174175
const options = this.options = compiler.options;
175176
this.outputOptions = options && options.output;
@@ -181,8 +182,8 @@ class Compilation extends Tapable {
181182
this.chunkTemplate = new ChunkTemplate(this.outputOptions);
182183
this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(this.outputOptions);
183184
this.moduleTemplates = {
184-
javascript: new ModuleTemplate(this.outputOptions),
185-
webassembly: new ModuleTemplate(this.outputOptions)
185+
javascript: new ModuleTemplate(this.outputOptions, this.requestShortener),
186+
webassembly: new ModuleTemplate(this.outputOptions, this.requestShortener)
186187
};
187188

188189
this.semaphore = new Semaphore(options.parallelism || 100);
@@ -1544,6 +1545,7 @@ class Compilation extends Tapable {
15441545

15451546
createChunkAssets() {
15461547
const outputOptions = this.outputOptions;
1548+
const cachedSourceMap = new Map();
15471549
for(let i = 0; i < this.chunks.length; i++) {
15481550
const chunk = this.chunks[i];
15491551
chunk.files = [];
@@ -1568,10 +1570,21 @@ class Compilation extends Tapable {
15681570
source = this.cache[cacheName].source;
15691571
} else {
15701572
source = fileManifest.render();
1573+
// Ensure that source is a cached source to avoid additional cost because of repeated access
1574+
if(!(source instanceof CachedSource)) {
1575+
const cacheEntry = cachedSourceMap.get(source);
1576+
if(cacheEntry) {
1577+
source = cacheEntry;
1578+
} else {
1579+
const cachedSource = new CachedSource(source);
1580+
cachedSourceMap.set(source, cachedSource);
1581+
source = cachedSource;
1582+
}
1583+
}
15711584
if(this.cache) {
15721585
this.cache[cacheName] = {
15731586
hash: usedHash,
1574-
source: source = (source instanceof CachedSource ? source : new CachedSource(source))
1587+
source
15751588
};
15761589
}
15771590
}

lib/Compiler.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const NormalModuleFactory = require("./NormalModuleFactory");
1818
const ContextModuleFactory = require("./ContextModuleFactory");
1919
const ResolverFactory = require("./ResolverFactory");
2020

21+
const RequestShortener = require("./RequestShortener");
2122
const makePathsRelative = require("./util/identifier").makePathsRelative;
2223

2324
class Watching {
@@ -174,7 +175,7 @@ class Watching {
174175
}
175176

176177
class Compiler extends Tapable {
177-
constructor() {
178+
constructor(context) {
178179
super();
179180
this.hooks = {
180181
shouldEmit: new SyncBailHook(["compilation"]),
@@ -321,6 +322,10 @@ class Compiler extends Tapable {
321322
};
322323

323324
this.options = {};
325+
326+
this.context = context;
327+
328+
this.requestShortener = new RequestShortener(context);
324329
}
325330

326331
watch(watchOptions, handler) {
@@ -515,7 +520,7 @@ class Compiler extends Tapable {
515520
}
516521

517522
createChildCompiler(compilation, compilerName, compilerIndex, outputOptions, plugins) {
518-
const childCompiler = new Compiler();
523+
const childCompiler = new Compiler(this.context);
519524
if(Array.isArray(plugins)) {
520525
plugins.forEach(plugin => childCompiler.apply(plugin));
521526
}

lib/EvalDevToolModuleTemplatePlugin.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
const RawSource = require("webpack-sources").RawSource;
88
const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
9+
const cache = new WeakMap();
910

1011
class EvalDevToolModuleTemplatePlugin {
1112
constructor(options) {
@@ -16,6 +17,8 @@ class EvalDevToolModuleTemplatePlugin {
1617

1718
apply(moduleTemplate) {
1819
moduleTemplate.plugin("module", (source, module) => {
20+
const cacheEntry = cache.get(source);
21+
if(cacheEntry !== undefined) return cacheEntry;
1922
const content = source.source();
2023
const str = ModuleFilenameHelpers.createFilename(module, {
2124
moduleFilenameTemplate: this.moduleFilenameTemplate,
@@ -25,7 +28,9 @@ class EvalDevToolModuleTemplatePlugin {
2528
ModuleFilenameHelpers.createFooter(module, moduleTemplate.requestShortener),
2629
this.sourceUrlComment.replace(/\[url\]/g, encodeURI(str).replace(/%2F/g, "/").replace(/%20/g, "_").replace(/%5E/g, "^").replace(/%5C/g, "\\").replace(/^\//, ""))
2730
].join("\n");
28-
return new RawSource(`eval(${JSON.stringify(content + footer)});`);
31+
const result = new RawSource(`eval(${JSON.stringify(content + footer)});`);
32+
cache.set(source, result);
33+
return result;
2934
});
3035
moduleTemplate.plugin("hash", hash => {
3136
hash.update("EvalDevToolModuleTemplatePlugin");

lib/FlagDependencyExportsPlugin.js

Lines changed: 60 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
*/
55
"use strict";
66

7+
const Queue = require("./util/Queue");
8+
79
const addToSet = (a, b) => {
810
let changed = false;
9-
b.forEach((item) => {
11+
for(const item of b) {
1012
if(!a.has(item)) {
1113
a.add(item);
1214
changed = true;
1315
}
14-
});
16+
}
1517
return changed;
1618
};
1719

@@ -20,69 +22,87 @@ class FlagDependencyExportsPlugin {
2022
apply(compiler) {
2123
compiler.plugin("compilation", (compilation) => {
2224
compilation.plugin("finish-modules", (modules) => {
23-
const dependencies = Object.create(null);
25+
const dependencies = new Map();
2426

25-
// TODO maybe replace with utils/Queue for better performance?
26-
const queue = modules.filter((m) => !m.providedExports);
27+
const queue = new Queue();
2728

2829
let module;
2930
let moduleWithExports;
3031
let moduleProvidedExports;
3132

3233
const processDependenciesBlock = depBlock => {
33-
depBlock.dependencies.forEach((dep) => processDependency(dep));
34-
depBlock.variables.forEach((variable) => {
35-
variable.dependencies.forEach((dep) => processDependency(dep));
36-
});
37-
depBlock.blocks.forEach(processDependenciesBlock);
34+
for(const dep of depBlock.dependencies) {
35+
if(processDependency(dep))
36+
return true;
37+
}
38+
for(const variable of depBlock.variables) {
39+
for(const dep of variable.dependencies) {
40+
if(processDependency(dep))
41+
return true;
42+
}
43+
}
44+
for(const block of depBlock.blocks) {
45+
if(processDependenciesBlock(block))
46+
return true;
47+
}
48+
return false;
3849
};
3950

4051
const processDependency = dep => {
4152
const exportDesc = dep.getExports && dep.getExports();
4253
if(!exportDesc) return;
4354
moduleWithExports = true;
4455
const exports = exportDesc.exports;
56+
// break early if it's only in the worst state
57+
if(module.providedExports === true) {
58+
return true;
59+
}
60+
// break if it should move to the worst state
61+
if(exports === true) {
62+
module.providedExports = true;
63+
notifyDependencies();
64+
return true;
65+
}
66+
// merge in new exports
67+
if(Array.isArray(exports)) {
68+
if(addToSet(moduleProvidedExports, exports)) {
69+
notifyDependencies();
70+
}
71+
}
72+
// store dependencies
4573
const exportDeps = exportDesc.dependencies;
4674
if(exportDeps) {
47-
exportDeps.forEach((dep) => {
48-
const depIdent = dep.identifier();
49-
// if this was not yet initialized
50-
// initialize it as an array containing the module and stop
51-
const array = dependencies[depIdent];
52-
if(!array) {
53-
dependencies[depIdent] = [module];
54-
return;
75+
for(const exportDependency of exportDeps) {
76+
// add dependency for this module
77+
const set = dependencies.get(exportDependency);
78+
if(set === undefined) {
79+
dependencies.set(exportDependency, new Set([module]));
80+
} else {
81+
set.add(module);
5582
}
56-
57-
// check if this module is known
58-
// if not, add it to the dependencies for this identifier
59-
if(array.indexOf(module) < 0)
60-
array.push(module);
61-
});
62-
}
63-
let changed = false;
64-
if(module.providedExports !== true) {
65-
if(exports === true) {
66-
module.providedExports = true;
67-
changed = true;
68-
} else if(Array.isArray(exports)) {
69-
changed = addToSet(moduleProvidedExports, exports);
7083
}
7184
}
72-
if(changed) {
73-
notifyDependencies();
74-
}
85+
return false;
7586
};
7687

7788
const notifyDependencies = () => {
78-
const deps = dependencies[module.identifier()];
79-
if(deps) {
80-
deps.forEach((dep) => queue.push(dep));
89+
const deps = dependencies.get(module);
90+
if(deps !== undefined) {
91+
for(const dep of deps) {
92+
queue.enqueue(dep);
93+
}
8194
}
8295
};
8396

84-
for(let i = 0; i < queue.length; i++) {
85-
module = queue[i];
97+
// Start with all modules without provided exports
98+
for(const module of modules) {
99+
if(!module.providedExports) {
100+
queue.enqueue(module);
101+
}
102+
}
103+
104+
while(queue.length > 0) {
105+
module = queue.dequeue();
86106

87107
if(module.providedExports !== true) {
88108
moduleWithExports = module.meta && module.meta.harmonyModule;

lib/FunctionModulePlugin.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,14 @@
55
"use strict";
66

77
const FunctionModuleTemplatePlugin = require("./FunctionModuleTemplatePlugin");
8-
const RequestShortener = require("./RequestShortener");
98

109
class FunctionModulePlugin {
11-
constructor(options, requestShortener) {
10+
constructor(options) {
1211
this.options = options;
13-
this.requestShortener = requestShortener;
1412
}
1513

1614
apply(compiler) {
1715
compiler.plugin("compilation", (compilation) => {
18-
compilation.moduleTemplates.javascript.requestShortener = this.requestShortener || new RequestShortener(compiler.context);
1916
compilation.moduleTemplates.javascript.apply(new FunctionModuleTemplatePlugin());
2017
});
2118
}

lib/Module.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ class Module extends DependenciesBlock {
5151
// Unique Id
5252
this.debugId = debugId++;
5353

54+
// Hash
55+
this.hash = undefined;
56+
this.renderedHash = undefined;
57+
5458
// Info from Factory
5559
// TODO refactor: pass as constructor argument
5660
this.context = null;
@@ -93,6 +97,9 @@ class Module extends DependenciesBlock {
9397
}
9498

9599
disconnect() {
100+
this.hash = undefined;
101+
this.renderedHash = undefined;
102+
96103
this.reasons.length = 0;
97104
this._rewriteChunkInReasons = undefined;
98105
this._chunks.clear();

0 commit comments

Comments
 (0)