webpack 4.8.2 → 4.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -52
- package/bin/webpack.js +128 -43
- package/lib/AmdMainTemplatePlugin.js +10 -0
- package/lib/AsyncDependencyToInitialChunkError.js +12 -2
- package/lib/BannerPlugin.js +115 -101
- package/lib/CaseSensitiveModulesWarning.js +20 -2
- package/lib/Chunk.js +1 -0
- package/lib/ChunkGroup.js +465 -465
- package/lib/ChunkRenderError.js +8 -0
- package/lib/ChunkTemplate.js +71 -71
- package/lib/Compilation.js +1 -1
- package/lib/Compiler.js +2 -1
- package/lib/ContextModule.js +8 -8
- package/lib/DllPlugin.js +3 -1
- package/lib/DllReferencePlugin.js +2 -1
- package/lib/Entrypoint.js +54 -54
- package/lib/EvalSourceMapDevToolModuleTemplatePlugin.js +115 -115
- package/lib/ExportPropertyMainTemplatePlugin.js +13 -0
- package/lib/Generator.js +52 -52
- package/lib/HotModuleReplacement.runtime.js +633 -633
- package/lib/JsonParser.js +2 -1
- package/lib/LibManifestPlugin.js +9 -0
- package/lib/LibraryTemplatePlugin.js +66 -33
- package/lib/MainTemplate.js +468 -468
- package/lib/Module.js +3 -3
- package/lib/ModuleDependencyError.js +12 -2
- package/lib/NormalModuleFactory.js +5 -3
- package/lib/Parser.js +27 -23
- package/lib/ProgressPlugin.js +1 -1
- package/lib/RecordIdsPlugin.js +3 -1
- package/lib/RuntimeTemplate.js +1 -1
- package/lib/SetVarMainTemplatePlugin.js +12 -0
- package/lib/SourceMapDevToolPlugin.js +11 -13
- package/lib/Template.js +289 -290
- package/lib/UmdMainTemplatePlugin.js +67 -32
- package/lib/WebpackError.js +8 -2
- package/lib/compareLocations.js +20 -0
- package/lib/debug/ProfilingPlugin.js +416 -416
- package/lib/dependencies/ContextDependencyHelpers.js +142 -142
- package/lib/dependencies/WebpackMissingModule.js +2 -2
- package/lib/optimize/RemoveEmptyChunksPlugin.js +42 -40
- package/lib/optimize/RuntimeChunkPlugin.js +9 -5
- package/lib/optimize/SplitChunksPlugin.js +195 -124
- package/lib/util/Queue.js +46 -46
- package/lib/util/SetHelpers.js +48 -48
- package/lib/util/SortableSet.js +106 -106
- package/lib/util/StackedSetMap.js +128 -128
- package/lib/util/cachedMerge.js +13 -0
- package/lib/util/identifier.js +5 -0
- package/lib/util/objectToMap.js +16 -16
- package/lib/wasm/WebAssemblyGenerator.js +280 -280
- package/lib/wasm/WebAssemblyParser.js +79 -79
- package/lib/web/JsonpMainTemplatePlugin.js +2 -2
- package/package.json +21 -17
- package/schemas/WebpackOptions.json +12 -1
- package/schemas/plugins/BannerPlugin.json +96 -85
- package/schemas/plugins/DllPlugin.json +4 -0
@@ -1,416 +1,416 @@
|
|
1
|
-
const fs = require("fs");
|
2
|
-
const { Tracer } = require("chrome-trace-event");
|
3
|
-
const validateOptions = require("schema-utils");
|
4
|
-
const schema = require("../../schemas/plugins/debug/ProfilingPlugin.json");
|
5
|
-
let inspector = undefined;
|
6
|
-
|
7
|
-
try {
|
8
|
-
inspector = require("inspector"); // eslint-disable-line node/no-missing-require
|
9
|
-
} catch (e) {
|
10
|
-
console.log("Unable to CPU profile in < node 8.0");
|
11
|
-
}
|
12
|
-
|
13
|
-
class Profiler {
|
14
|
-
constructor(inspector) {
|
15
|
-
this.session = undefined;
|
16
|
-
this.inspector = inspector;
|
17
|
-
}
|
18
|
-
|
19
|
-
hasSession() {
|
20
|
-
return this.session !== undefined;
|
21
|
-
}
|
22
|
-
|
23
|
-
startProfiling() {
|
24
|
-
if (this.inspector === undefined) {
|
25
|
-
return Promise.resolve();
|
26
|
-
}
|
27
|
-
|
28
|
-
try {
|
29
|
-
this.session = new inspector.Session();
|
30
|
-
this.session.connect();
|
31
|
-
} catch (_) {
|
32
|
-
this.session = undefined;
|
33
|
-
return Promise.resolve();
|
34
|
-
}
|
35
|
-
|
36
|
-
return Promise.all([
|
37
|
-
this.sendCommand("Profiler.setSamplingInterval", {
|
38
|
-
interval: 100
|
39
|
-
}),
|
40
|
-
this.sendCommand("Profiler.enable"),
|
41
|
-
this.sendCommand("Profiler.start")
|
42
|
-
]);
|
43
|
-
}
|
44
|
-
|
45
|
-
sendCommand(method, params) {
|
46
|
-
if (this.hasSession()) {
|
47
|
-
return new Promise((res, rej) => {
|
48
|
-
return this.session.post(method, params, (err, params) => {
|
49
|
-
if (err !== null) {
|
50
|
-
rej(err);
|
51
|
-
} else {
|
52
|
-
res(params);
|
53
|
-
}
|
54
|
-
});
|
55
|
-
});
|
56
|
-
} else {
|
57
|
-
return Promise.resolve();
|
58
|
-
}
|
59
|
-
}
|
60
|
-
|
61
|
-
destroy() {
|
62
|
-
if (this.hasSession()) {
|
63
|
-
this.session.disconnect();
|
64
|
-
}
|
65
|
-
|
66
|
-
return Promise.resolve();
|
67
|
-
}
|
68
|
-
|
69
|
-
stopProfiling() {
|
70
|
-
return this.sendCommand("Profiler.stop");
|
71
|
-
}
|
72
|
-
}
|
73
|
-
|
74
|
-
/**
|
75
|
-
* @param {string} outputPath The location where to write the log.
|
76
|
-
* @returns {{trace: ?, counter: number, profiler: Profiler, end: Function}} The trace object
|
77
|
-
*/
|
78
|
-
function createTrace(outputPath) {
|
79
|
-
const trace = new Tracer({
|
80
|
-
noStream: true
|
81
|
-
});
|
82
|
-
const profiler = new Profiler(inspector);
|
83
|
-
const fsStream = fs.createWriteStream(outputPath);
|
84
|
-
|
85
|
-
let counter = 0;
|
86
|
-
|
87
|
-
trace.pipe(fsStream);
|
88
|
-
// These are critical events that need to be inserted so that tools like
|
89
|
-
// chrome dev tools can load the profile.
|
90
|
-
trace.instantEvent({
|
91
|
-
name: "TracingStartedInPage",
|
92
|
-
id: ++counter,
|
93
|
-
cat: ["disabled-by-default-devtools.timeline"],
|
94
|
-
args: {
|
95
|
-
data: {
|
96
|
-
sessionId: "-1",
|
97
|
-
page: "0xfff",
|
98
|
-
frames: [
|
99
|
-
{
|
100
|
-
frame: "0xfff",
|
101
|
-
url: "webpack",
|
102
|
-
name: ""
|
103
|
-
}
|
104
|
-
]
|
105
|
-
}
|
106
|
-
}
|
107
|
-
});
|
108
|
-
|
109
|
-
trace.instantEvent({
|
110
|
-
name: "TracingStartedInBrowser",
|
111
|
-
id: ++counter,
|
112
|
-
cat: ["disabled-by-default-devtools.timeline"],
|
113
|
-
args: {
|
114
|
-
data: {
|
115
|
-
sessionId: "-1"
|
116
|
-
}
|
117
|
-
}
|
118
|
-
});
|
119
|
-
|
120
|
-
return {
|
121
|
-
trace,
|
122
|
-
counter,
|
123
|
-
profiler,
|
124
|
-
end: callback => fsStream.end(callback)
|
125
|
-
};
|
126
|
-
}
|
127
|
-
|
128
|
-
const pluginName = "ProfilingPlugin";
|
129
|
-
|
130
|
-
class ProfilingPlugin {
|
131
|
-
constructor(opts) {
|
132
|
-
validateOptions(schema, opts || {}, "Profiling plugin");
|
133
|
-
opts = opts || {};
|
134
|
-
this.outputPath = opts.outputPath || "events.json";
|
135
|
-
}
|
136
|
-
|
137
|
-
apply(compiler) {
|
138
|
-
const tracer = createTrace(this.outputPath);
|
139
|
-
tracer.profiler.startProfiling();
|
140
|
-
|
141
|
-
// Compiler Hooks
|
142
|
-
Object.keys(compiler.hooks).forEach(hookName => {
|
143
|
-
compiler.hooks[hookName].intercept(
|
144
|
-
makeInterceptorFor("Compiler", tracer)(hookName)
|
145
|
-
);
|
146
|
-
});
|
147
|
-
|
148
|
-
Object.keys(compiler.resolverFactory.hooks).forEach(hookName => {
|
149
|
-
compiler.resolverFactory.hooks[hookName].intercept(
|
150
|
-
makeInterceptorFor("Resolver", tracer)(hookName)
|
151
|
-
);
|
152
|
-
});
|
153
|
-
|
154
|
-
compiler.hooks.compilation.tap(
|
155
|
-
pluginName,
|
156
|
-
(compilation, { normalModuleFactory, contextModuleFactory }) => {
|
157
|
-
interceptAllHooksFor(compilation, tracer, "Compilation");
|
158
|
-
interceptAllHooksFor(
|
159
|
-
normalModuleFactory,
|
160
|
-
tracer,
|
161
|
-
"Normal Module Factory"
|
162
|
-
);
|
163
|
-
interceptAllHooksFor(
|
164
|
-
contextModuleFactory,
|
165
|
-
tracer,
|
166
|
-
"Context Module Factory"
|
167
|
-
);
|
168
|
-
interceptAllParserHooks(normalModuleFactory, tracer);
|
169
|
-
interceptTemplateInstancesFrom(compilation, tracer);
|
170
|
-
}
|
171
|
-
);
|
172
|
-
|
173
|
-
// We need to write out the CPU profile when we are all done.
|
174
|
-
compiler.hooks.done.tapAsync(
|
175
|
-
{
|
176
|
-
name: pluginName,
|
177
|
-
stage: Infinity
|
178
|
-
},
|
179
|
-
(stats, callback) => {
|
180
|
-
tracer.profiler.stopProfiling().then(parsedResults => {
|
181
|
-
if (parsedResults === undefined) {
|
182
|
-
tracer.profiler.destroy();
|
183
|
-
tracer.trace.flush();
|
184
|
-
tracer.end(callback);
|
185
|
-
return;
|
186
|
-
}
|
187
|
-
|
188
|
-
const cpuStartTime = parsedResults.profile.startTime;
|
189
|
-
const cpuEndTime = parsedResults.profile.endTime;
|
190
|
-
|
191
|
-
tracer.trace.completeEvent({
|
192
|
-
name: "TaskQueueManager::ProcessTaskFromWorkQueue",
|
193
|
-
id: ++tracer.counter,
|
194
|
-
cat: ["toplevel"],
|
195
|
-
ts: cpuStartTime,
|
196
|
-
args: {
|
197
|
-
src_file: "../../ipc/ipc_moji_bootstrap.cc",
|
198
|
-
src_func: "Accept"
|
199
|
-
}
|
200
|
-
});
|
201
|
-
|
202
|
-
tracer.trace.completeEvent({
|
203
|
-
name: "EvaluateScript",
|
204
|
-
id: ++tracer.counter,
|
205
|
-
cat: ["devtools.timeline"],
|
206
|
-
ts: cpuStartTime,
|
207
|
-
dur: cpuEndTime - cpuStartTime,
|
208
|
-
args: {
|
209
|
-
data: {
|
210
|
-
url: "webpack",
|
211
|
-
lineNumber: 1,
|
212
|
-
columnNumber: 1,
|
213
|
-
frame: "0xFFF"
|
214
|
-
}
|
215
|
-
}
|
216
|
-
});
|
217
|
-
|
218
|
-
tracer.trace.instantEvent({
|
219
|
-
name: "CpuProfile",
|
220
|
-
id: ++tracer.counter,
|
221
|
-
cat: ["disabled-by-default-devtools.timeline"],
|
222
|
-
ts: cpuEndTime,
|
223
|
-
args: {
|
224
|
-
data: {
|
225
|
-
cpuProfile: parsedResults.profile
|
226
|
-
}
|
227
|
-
}
|
228
|
-
});
|
229
|
-
|
230
|
-
tracer.profiler.destroy();
|
231
|
-
tracer.trace.flush();
|
232
|
-
tracer.end(callback);
|
233
|
-
});
|
234
|
-
}
|
235
|
-
);
|
236
|
-
}
|
237
|
-
}
|
238
|
-
|
239
|
-
const interceptTemplateInstancesFrom = (compilation, tracer) => {
|
240
|
-
const {
|
241
|
-
mainTemplate,
|
242
|
-
chunkTemplate,
|
243
|
-
hotUpdateChunkTemplate,
|
244
|
-
moduleTemplates
|
245
|
-
} = compilation;
|
246
|
-
|
247
|
-
const { javascript, webassembly } = moduleTemplates;
|
248
|
-
|
249
|
-
[
|
250
|
-
{
|
251
|
-
instance: mainTemplate,
|
252
|
-
name: "MainTemplate"
|
253
|
-
},
|
254
|
-
{
|
255
|
-
instance: chunkTemplate,
|
256
|
-
name: "ChunkTemplate"
|
257
|
-
},
|
258
|
-
{
|
259
|
-
instance: hotUpdateChunkTemplate,
|
260
|
-
name: "HotUpdateChunkTemplate"
|
261
|
-
},
|
262
|
-
{
|
263
|
-
instance: javascript,
|
264
|
-
name: "JavaScriptModuleTemplate"
|
265
|
-
},
|
266
|
-
{
|
267
|
-
instance: webassembly,
|
268
|
-
name: "WebAssemblyModuleTemplate"
|
269
|
-
}
|
270
|
-
].forEach(templateObject => {
|
271
|
-
Object.keys(templateObject.instance.hooks).forEach(hookName => {
|
272
|
-
templateObject.instance.hooks[hookName].intercept(
|
273
|
-
makeInterceptorFor(templateObject.name, tracer)(hookName)
|
274
|
-
);
|
275
|
-
});
|
276
|
-
});
|
277
|
-
};
|
278
|
-
|
279
|
-
const interceptAllHooksFor = (instance, tracer, logLabel) => {
|
280
|
-
if (Reflect.has(instance, "hooks")) {
|
281
|
-
Object.keys(instance.hooks).forEach(hookName => {
|
282
|
-
instance.hooks[hookName].intercept(
|
283
|
-
makeInterceptorFor(logLabel, tracer)(hookName)
|
284
|
-
);
|
285
|
-
});
|
286
|
-
}
|
287
|
-
};
|
288
|
-
|
289
|
-
const interceptAllParserHooks = (moduleFactory, tracer) => {
|
290
|
-
const moduleTypes = [
|
291
|
-
"javascript/auto",
|
292
|
-
"javascript/dynamic",
|
293
|
-
"javascript/esm",
|
294
|
-
"json",
|
295
|
-
"webassembly/experimental"
|
296
|
-
];
|
297
|
-
|
298
|
-
moduleTypes.forEach(moduleType => {
|
299
|
-
moduleFactory.hooks.parser
|
300
|
-
.for(moduleType)
|
301
|
-
.tap("ProfilingPlugin", (parser, parserOpts) => {
|
302
|
-
interceptAllHooksFor(parser, tracer, "Parser");
|
303
|
-
});
|
304
|
-
});
|
305
|
-
};
|
306
|
-
|
307
|
-
const makeInterceptorFor = (instance, tracer) => hookName => ({
|
308
|
-
register: ({ name, type, context, fn }) => {
|
309
|
-
const newFn = makeNewProfiledTapFn(hookName, tracer, {
|
310
|
-
name,
|
311
|
-
type,
|
312
|
-
fn
|
313
|
-
});
|
314
|
-
return {
|
315
|
-
name,
|
316
|
-
type,
|
317
|
-
context,
|
318
|
-
fn: newFn
|
319
|
-
};
|
320
|
-
}
|
321
|
-
});
|
322
|
-
|
323
|
-
// TODO improve typing
|
324
|
-
/** @typedef {(...args: TODO[]) => void | Promise<TODO>} PluginFunction */
|
325
|
-
|
326
|
-
/**
|
327
|
-
* @param {string} hookName Name of the hook to profile.
|
328
|
-
* @param {Tracer} tracer Instance of tracer.
|
329
|
-
* @param {object} options Options for the profiled fn.
|
330
|
-
* @param {string} options.name Plugin name
|
331
|
-
* @param {string} options.type Plugin type (sync | async | promise)
|
332
|
-
* @param {PluginFunction} options.fn Plugin function
|
333
|
-
* @returns {PluginFunction} Chainable hooked function.
|
334
|
-
*/
|
335
|
-
const makeNewProfiledTapFn = (hookName, tracer, { name, type, fn }) => {
|
336
|
-
const defaultCategory = ["blink.user_timing"];
|
337
|
-
|
338
|
-
switch (type) {
|
339
|
-
case "promise":
|
340
|
-
return (...args) => {
|
341
|
-
// eslint-disable-line
|
342
|
-
const id = ++tracer.counter;
|
343
|
-
tracer.trace.begin({
|
344
|
-
name,
|
345
|
-
id,
|
346
|
-
cat: defaultCategory
|
347
|
-
});
|
348
|
-
const promise = /** @type {Promise<*>} */ (fn(...args));
|
349
|
-
return promise.then(r => {
|
350
|
-
tracer.trace.end({
|
351
|
-
name,
|
352
|
-
id,
|
353
|
-
cat: defaultCategory
|
354
|
-
});
|
355
|
-
return r;
|
356
|
-
});
|
357
|
-
};
|
358
|
-
case "async":
|
359
|
-
return (...args) => {
|
360
|
-
// eslint-disable-line
|
361
|
-
const id = ++tracer.counter;
|
362
|
-
tracer.trace.begin({
|
363
|
-
name,
|
364
|
-
id,
|
365
|
-
cat: defaultCategory
|
366
|
-
});
|
367
|
-
fn(...args, (...r) => {
|
368
|
-
const callback = args.pop();
|
369
|
-
tracer.trace.end({
|
370
|
-
name,
|
371
|
-
id,
|
372
|
-
cat: defaultCategory
|
373
|
-
});
|
374
|
-
callback(...r);
|
375
|
-
});
|
376
|
-
};
|
377
|
-
case "sync":
|
378
|
-
return (...args) => {
|
379
|
-
// eslint-disable-line
|
380
|
-
const id = ++tracer.counter;
|
381
|
-
// Do not instrument ourself due to the CPU
|
382
|
-
// profile needing to be the last event in the trace.
|
383
|
-
if (name === pluginName) {
|
384
|
-
return fn(...args);
|
385
|
-
}
|
386
|
-
|
387
|
-
tracer.trace.begin({
|
388
|
-
name,
|
389
|
-
id,
|
390
|
-
cat: defaultCategory
|
391
|
-
});
|
392
|
-
let r;
|
393
|
-
try {
|
394
|
-
r = fn(...args);
|
395
|
-
} catch (error) {
|
396
|
-
tracer.trace.end({
|
397
|
-
name,
|
398
|
-
id,
|
399
|
-
cat: defaultCategory
|
400
|
-
});
|
401
|
-
throw error;
|
402
|
-
}
|
403
|
-
tracer.trace.end({
|
404
|
-
name,
|
405
|
-
id,
|
406
|
-
cat: defaultCategory
|
407
|
-
});
|
408
|
-
return r;
|
409
|
-
};
|
410
|
-
default:
|
411
|
-
break;
|
412
|
-
}
|
413
|
-
};
|
414
|
-
|
415
|
-
module.exports = ProfilingPlugin;
|
416
|
-
module.exports.Profiler = Profiler;
|
1
|
+
const fs = require("fs");
|
2
|
+
const { Tracer } = require("chrome-trace-event");
|
3
|
+
const validateOptions = require("schema-utils");
|
4
|
+
const schema = require("../../schemas/plugins/debug/ProfilingPlugin.json");
|
5
|
+
let inspector = undefined;
|
6
|
+
|
7
|
+
try {
|
8
|
+
inspector = require("inspector"); // eslint-disable-line node/no-missing-require
|
9
|
+
} catch (e) {
|
10
|
+
console.log("Unable to CPU profile in < node 8.0");
|
11
|
+
}
|
12
|
+
|
13
|
+
class Profiler {
|
14
|
+
constructor(inspector) {
|
15
|
+
this.session = undefined;
|
16
|
+
this.inspector = inspector;
|
17
|
+
}
|
18
|
+
|
19
|
+
hasSession() {
|
20
|
+
return this.session !== undefined;
|
21
|
+
}
|
22
|
+
|
23
|
+
startProfiling() {
|
24
|
+
if (this.inspector === undefined) {
|
25
|
+
return Promise.resolve();
|
26
|
+
}
|
27
|
+
|
28
|
+
try {
|
29
|
+
this.session = new inspector.Session();
|
30
|
+
this.session.connect();
|
31
|
+
} catch (_) {
|
32
|
+
this.session = undefined;
|
33
|
+
return Promise.resolve();
|
34
|
+
}
|
35
|
+
|
36
|
+
return Promise.all([
|
37
|
+
this.sendCommand("Profiler.setSamplingInterval", {
|
38
|
+
interval: 100
|
39
|
+
}),
|
40
|
+
this.sendCommand("Profiler.enable"),
|
41
|
+
this.sendCommand("Profiler.start")
|
42
|
+
]);
|
43
|
+
}
|
44
|
+
|
45
|
+
sendCommand(method, params) {
|
46
|
+
if (this.hasSession()) {
|
47
|
+
return new Promise((res, rej) => {
|
48
|
+
return this.session.post(method, params, (err, params) => {
|
49
|
+
if (err !== null) {
|
50
|
+
rej(err);
|
51
|
+
} else {
|
52
|
+
res(params);
|
53
|
+
}
|
54
|
+
});
|
55
|
+
});
|
56
|
+
} else {
|
57
|
+
return Promise.resolve();
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
destroy() {
|
62
|
+
if (this.hasSession()) {
|
63
|
+
this.session.disconnect();
|
64
|
+
}
|
65
|
+
|
66
|
+
return Promise.resolve();
|
67
|
+
}
|
68
|
+
|
69
|
+
stopProfiling() {
|
70
|
+
return this.sendCommand("Profiler.stop");
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
/**
|
75
|
+
* @param {string} outputPath The location where to write the log.
|
76
|
+
* @returns {{trace: ?, counter: number, profiler: Profiler, end: Function}} The trace object
|
77
|
+
*/
|
78
|
+
function createTrace(outputPath) {
|
79
|
+
const trace = new Tracer({
|
80
|
+
noStream: true
|
81
|
+
});
|
82
|
+
const profiler = new Profiler(inspector);
|
83
|
+
const fsStream = fs.createWriteStream(outputPath);
|
84
|
+
|
85
|
+
let counter = 0;
|
86
|
+
|
87
|
+
trace.pipe(fsStream);
|
88
|
+
// These are critical events that need to be inserted so that tools like
|
89
|
+
// chrome dev tools can load the profile.
|
90
|
+
trace.instantEvent({
|
91
|
+
name: "TracingStartedInPage",
|
92
|
+
id: ++counter,
|
93
|
+
cat: ["disabled-by-default-devtools.timeline"],
|
94
|
+
args: {
|
95
|
+
data: {
|
96
|
+
sessionId: "-1",
|
97
|
+
page: "0xfff",
|
98
|
+
frames: [
|
99
|
+
{
|
100
|
+
frame: "0xfff",
|
101
|
+
url: "webpack",
|
102
|
+
name: ""
|
103
|
+
}
|
104
|
+
]
|
105
|
+
}
|
106
|
+
}
|
107
|
+
});
|
108
|
+
|
109
|
+
trace.instantEvent({
|
110
|
+
name: "TracingStartedInBrowser",
|
111
|
+
id: ++counter,
|
112
|
+
cat: ["disabled-by-default-devtools.timeline"],
|
113
|
+
args: {
|
114
|
+
data: {
|
115
|
+
sessionId: "-1"
|
116
|
+
}
|
117
|
+
}
|
118
|
+
});
|
119
|
+
|
120
|
+
return {
|
121
|
+
trace,
|
122
|
+
counter,
|
123
|
+
profiler,
|
124
|
+
end: callback => fsStream.end(callback)
|
125
|
+
};
|
126
|
+
}
|
127
|
+
|
128
|
+
const pluginName = "ProfilingPlugin";
|
129
|
+
|
130
|
+
class ProfilingPlugin {
|
131
|
+
constructor(opts) {
|
132
|
+
validateOptions(schema, opts || {}, "Profiling plugin");
|
133
|
+
opts = opts || {};
|
134
|
+
this.outputPath = opts.outputPath || "events.json";
|
135
|
+
}
|
136
|
+
|
137
|
+
apply(compiler) {
|
138
|
+
const tracer = createTrace(this.outputPath);
|
139
|
+
tracer.profiler.startProfiling();
|
140
|
+
|
141
|
+
// Compiler Hooks
|
142
|
+
Object.keys(compiler.hooks).forEach(hookName => {
|
143
|
+
compiler.hooks[hookName].intercept(
|
144
|
+
makeInterceptorFor("Compiler", tracer)(hookName)
|
145
|
+
);
|
146
|
+
});
|
147
|
+
|
148
|
+
Object.keys(compiler.resolverFactory.hooks).forEach(hookName => {
|
149
|
+
compiler.resolverFactory.hooks[hookName].intercept(
|
150
|
+
makeInterceptorFor("Resolver", tracer)(hookName)
|
151
|
+
);
|
152
|
+
});
|
153
|
+
|
154
|
+
compiler.hooks.compilation.tap(
|
155
|
+
pluginName,
|
156
|
+
(compilation, { normalModuleFactory, contextModuleFactory }) => {
|
157
|
+
interceptAllHooksFor(compilation, tracer, "Compilation");
|
158
|
+
interceptAllHooksFor(
|
159
|
+
normalModuleFactory,
|
160
|
+
tracer,
|
161
|
+
"Normal Module Factory"
|
162
|
+
);
|
163
|
+
interceptAllHooksFor(
|
164
|
+
contextModuleFactory,
|
165
|
+
tracer,
|
166
|
+
"Context Module Factory"
|
167
|
+
);
|
168
|
+
interceptAllParserHooks(normalModuleFactory, tracer);
|
169
|
+
interceptTemplateInstancesFrom(compilation, tracer);
|
170
|
+
}
|
171
|
+
);
|
172
|
+
|
173
|
+
// We need to write out the CPU profile when we are all done.
|
174
|
+
compiler.hooks.done.tapAsync(
|
175
|
+
{
|
176
|
+
name: pluginName,
|
177
|
+
stage: Infinity
|
178
|
+
},
|
179
|
+
(stats, callback) => {
|
180
|
+
tracer.profiler.stopProfiling().then(parsedResults => {
|
181
|
+
if (parsedResults === undefined) {
|
182
|
+
tracer.profiler.destroy();
|
183
|
+
tracer.trace.flush();
|
184
|
+
tracer.end(callback);
|
185
|
+
return;
|
186
|
+
}
|
187
|
+
|
188
|
+
const cpuStartTime = parsedResults.profile.startTime;
|
189
|
+
const cpuEndTime = parsedResults.profile.endTime;
|
190
|
+
|
191
|
+
tracer.trace.completeEvent({
|
192
|
+
name: "TaskQueueManager::ProcessTaskFromWorkQueue",
|
193
|
+
id: ++tracer.counter,
|
194
|
+
cat: ["toplevel"],
|
195
|
+
ts: cpuStartTime,
|
196
|
+
args: {
|
197
|
+
src_file: "../../ipc/ipc_moji_bootstrap.cc",
|
198
|
+
src_func: "Accept"
|
199
|
+
}
|
200
|
+
});
|
201
|
+
|
202
|
+
tracer.trace.completeEvent({
|
203
|
+
name: "EvaluateScript",
|
204
|
+
id: ++tracer.counter,
|
205
|
+
cat: ["devtools.timeline"],
|
206
|
+
ts: cpuStartTime,
|
207
|
+
dur: cpuEndTime - cpuStartTime,
|
208
|
+
args: {
|
209
|
+
data: {
|
210
|
+
url: "webpack",
|
211
|
+
lineNumber: 1,
|
212
|
+
columnNumber: 1,
|
213
|
+
frame: "0xFFF"
|
214
|
+
}
|
215
|
+
}
|
216
|
+
});
|
217
|
+
|
218
|
+
tracer.trace.instantEvent({
|
219
|
+
name: "CpuProfile",
|
220
|
+
id: ++tracer.counter,
|
221
|
+
cat: ["disabled-by-default-devtools.timeline"],
|
222
|
+
ts: cpuEndTime,
|
223
|
+
args: {
|
224
|
+
data: {
|
225
|
+
cpuProfile: parsedResults.profile
|
226
|
+
}
|
227
|
+
}
|
228
|
+
});
|
229
|
+
|
230
|
+
tracer.profiler.destroy();
|
231
|
+
tracer.trace.flush();
|
232
|
+
tracer.end(callback);
|
233
|
+
});
|
234
|
+
}
|
235
|
+
);
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
const interceptTemplateInstancesFrom = (compilation, tracer) => {
|
240
|
+
const {
|
241
|
+
mainTemplate,
|
242
|
+
chunkTemplate,
|
243
|
+
hotUpdateChunkTemplate,
|
244
|
+
moduleTemplates
|
245
|
+
} = compilation;
|
246
|
+
|
247
|
+
const { javascript, webassembly } = moduleTemplates;
|
248
|
+
|
249
|
+
[
|
250
|
+
{
|
251
|
+
instance: mainTemplate,
|
252
|
+
name: "MainTemplate"
|
253
|
+
},
|
254
|
+
{
|
255
|
+
instance: chunkTemplate,
|
256
|
+
name: "ChunkTemplate"
|
257
|
+
},
|
258
|
+
{
|
259
|
+
instance: hotUpdateChunkTemplate,
|
260
|
+
name: "HotUpdateChunkTemplate"
|
261
|
+
},
|
262
|
+
{
|
263
|
+
instance: javascript,
|
264
|
+
name: "JavaScriptModuleTemplate"
|
265
|
+
},
|
266
|
+
{
|
267
|
+
instance: webassembly,
|
268
|
+
name: "WebAssemblyModuleTemplate"
|
269
|
+
}
|
270
|
+
].forEach(templateObject => {
|
271
|
+
Object.keys(templateObject.instance.hooks).forEach(hookName => {
|
272
|
+
templateObject.instance.hooks[hookName].intercept(
|
273
|
+
makeInterceptorFor(templateObject.name, tracer)(hookName)
|
274
|
+
);
|
275
|
+
});
|
276
|
+
});
|
277
|
+
};
|
278
|
+
|
279
|
+
const interceptAllHooksFor = (instance, tracer, logLabel) => {
|
280
|
+
if (Reflect.has(instance, "hooks")) {
|
281
|
+
Object.keys(instance.hooks).forEach(hookName => {
|
282
|
+
instance.hooks[hookName].intercept(
|
283
|
+
makeInterceptorFor(logLabel, tracer)(hookName)
|
284
|
+
);
|
285
|
+
});
|
286
|
+
}
|
287
|
+
};
|
288
|
+
|
289
|
+
const interceptAllParserHooks = (moduleFactory, tracer) => {
|
290
|
+
const moduleTypes = [
|
291
|
+
"javascript/auto",
|
292
|
+
"javascript/dynamic",
|
293
|
+
"javascript/esm",
|
294
|
+
"json",
|
295
|
+
"webassembly/experimental"
|
296
|
+
];
|
297
|
+
|
298
|
+
moduleTypes.forEach(moduleType => {
|
299
|
+
moduleFactory.hooks.parser
|
300
|
+
.for(moduleType)
|
301
|
+
.tap("ProfilingPlugin", (parser, parserOpts) => {
|
302
|
+
interceptAllHooksFor(parser, tracer, "Parser");
|
303
|
+
});
|
304
|
+
});
|
305
|
+
};
|
306
|
+
|
307
|
+
const makeInterceptorFor = (instance, tracer) => hookName => ({
|
308
|
+
register: ({ name, type, context, fn }) => {
|
309
|
+
const newFn = makeNewProfiledTapFn(hookName, tracer, {
|
310
|
+
name,
|
311
|
+
type,
|
312
|
+
fn
|
313
|
+
});
|
314
|
+
return {
|
315
|
+
name,
|
316
|
+
type,
|
317
|
+
context,
|
318
|
+
fn: newFn
|
319
|
+
};
|
320
|
+
}
|
321
|
+
});
|
322
|
+
|
323
|
+
// TODO improve typing
|
324
|
+
/** @typedef {(...args: TODO[]) => void | Promise<TODO>} PluginFunction */
|
325
|
+
|
326
|
+
/**
|
327
|
+
* @param {string} hookName Name of the hook to profile.
|
328
|
+
* @param {Tracer} tracer Instance of tracer.
|
329
|
+
* @param {object} options Options for the profiled fn.
|
330
|
+
* @param {string} options.name Plugin name
|
331
|
+
* @param {string} options.type Plugin type (sync | async | promise)
|
332
|
+
* @param {PluginFunction} options.fn Plugin function
|
333
|
+
* @returns {PluginFunction} Chainable hooked function.
|
334
|
+
*/
|
335
|
+
const makeNewProfiledTapFn = (hookName, tracer, { name, type, fn }) => {
|
336
|
+
const defaultCategory = ["blink.user_timing"];
|
337
|
+
|
338
|
+
switch (type) {
|
339
|
+
case "promise":
|
340
|
+
return (...args) => {
|
341
|
+
// eslint-disable-line
|
342
|
+
const id = ++tracer.counter;
|
343
|
+
tracer.trace.begin({
|
344
|
+
name,
|
345
|
+
id,
|
346
|
+
cat: defaultCategory
|
347
|
+
});
|
348
|
+
const promise = /** @type {Promise<*>} */ (fn(...args));
|
349
|
+
return promise.then(r => {
|
350
|
+
tracer.trace.end({
|
351
|
+
name,
|
352
|
+
id,
|
353
|
+
cat: defaultCategory
|
354
|
+
});
|
355
|
+
return r;
|
356
|
+
});
|
357
|
+
};
|
358
|
+
case "async":
|
359
|
+
return (...args) => {
|
360
|
+
// eslint-disable-line
|
361
|
+
const id = ++tracer.counter;
|
362
|
+
tracer.trace.begin({
|
363
|
+
name,
|
364
|
+
id,
|
365
|
+
cat: defaultCategory
|
366
|
+
});
|
367
|
+
fn(...args, (...r) => {
|
368
|
+
const callback = args.pop();
|
369
|
+
tracer.trace.end({
|
370
|
+
name,
|
371
|
+
id,
|
372
|
+
cat: defaultCategory
|
373
|
+
});
|
374
|
+
callback(...r);
|
375
|
+
});
|
376
|
+
};
|
377
|
+
case "sync":
|
378
|
+
return (...args) => {
|
379
|
+
// eslint-disable-line
|
380
|
+
const id = ++tracer.counter;
|
381
|
+
// Do not instrument ourself due to the CPU
|
382
|
+
// profile needing to be the last event in the trace.
|
383
|
+
if (name === pluginName) {
|
384
|
+
return fn(...args);
|
385
|
+
}
|
386
|
+
|
387
|
+
tracer.trace.begin({
|
388
|
+
name,
|
389
|
+
id,
|
390
|
+
cat: defaultCategory
|
391
|
+
});
|
392
|
+
let r;
|
393
|
+
try {
|
394
|
+
r = fn(...args);
|
395
|
+
} catch (error) {
|
396
|
+
tracer.trace.end({
|
397
|
+
name,
|
398
|
+
id,
|
399
|
+
cat: defaultCategory
|
400
|
+
});
|
401
|
+
throw error;
|
402
|
+
}
|
403
|
+
tracer.trace.end({
|
404
|
+
name,
|
405
|
+
id,
|
406
|
+
cat: defaultCategory
|
407
|
+
});
|
408
|
+
return r;
|
409
|
+
};
|
410
|
+
default:
|
411
|
+
break;
|
412
|
+
}
|
413
|
+
};
|
414
|
+
|
415
|
+
module.exports = ProfilingPlugin;
|
416
|
+
module.exports.Profiler = Profiler;
|