UNPKG

5.45 kBJavaScriptView Raw
1const path = require("path");
2const fs = require("fs");
3const chalk = require("chalk");
4const { WrappedPlugin } = require("./WrappedPlugin");
5const { getModuleName, getLoaderNames, prependLoader } = require("./utils");
6const {
7 getHumanOutput,
8 getMiscOutput,
9 getPluginsOutput,
10 getLoadersOutput,
11 smpTag,
12} = require("./output");
13
14const NS = path.dirname(fs.realpathSync(__filename));
15
16module.exports = class SpeedMeasurePlugin {
17 constructor(options) {
18 this.options = options || {};
19
20 this.timeEventData = {};
21 this.smpPluginAdded = false;
22
23 this.wrap = this.wrap.bind(this);
24 this.getOutput = this.getOutput.bind(this);
25 this.addTimeEvent = this.addTimeEvent.bind(this);
26 this.apply = this.apply.bind(this);
27 this.provideLoaderTiming = this.provideLoaderTiming.bind(this);
28 }
29
30 wrap(config) {
31 if (this.options.disable) return config;
32
33 config.plugins = (config.plugins || []).map(plugin => {
34 const pluginName =
35 Object.keys(this.options.pluginNames || {}).find(
36 pluginName => plugin === this.options.pluginNames[pluginName]
37 ) ||
38 (plugin.constructor && plugin.constructor.name) ||
39 "(unable to deduce plugin name)";
40 return new WrappedPlugin(plugin, pluginName, this);
41 });
42
43 if (config.module && this.options.granularLoaderData) {
44 config.module = prependLoader(config.module);
45 }
46
47 if (!this.smpPluginAdded) {
48 config.plugins = config.plugins.concat(this);
49 this.smpPluginAdded = true;
50 }
51
52 return config;
53 }
54
55 getOutput() {
56 const outputObj = {};
57 if (this.timeEventData.misc)
58 outputObj.misc = getMiscOutput(this.timeEventData.misc);
59 if (this.timeEventData.plugins)
60 outputObj.plugins = getPluginsOutput(this.timeEventData.plugins);
61 if (this.timeEventData.loaders)
62 outputObj.loaders = getLoadersOutput(this.timeEventData.loaders);
63
64 if (this.options.outputFormat === "json")
65 return JSON.stringify(outputObj, null, 2);
66 if (typeof this.options.outputFormat === "function")
67 return this.options.outputFormat(outputObj);
68 return getHumanOutput(outputObj, {
69 verbose: this.options.outputFormat === "humanVerbose",
70 });
71 }
72
73 addTimeEvent(category, event, eventType, data = {}) {
74 const allowFailure = data.allowFailure;
75 delete data.allowFailure;
76
77 const tED = this.timeEventData;
78 if (!tED[category]) tED[category] = {};
79 if (!tED[category][event]) tED[category][event] = [];
80 const eventList = tED[category][event];
81 const curTime = new Date().getTime();
82
83 if (eventType === "start") {
84 data.start = curTime;
85 eventList.push(data);
86 } else if (eventType === "end") {
87 const matchingEvent = eventList.find(e => {
88 const allowOverwrite = !e.end || !data.fillLast;
89 const idMatch = e.id !== undefined && e.id === data.id;
90 const nameMatch =
91 !data.id && e.name !== undefined && e.name === data.name;
92 return allowOverwrite && (idMatch || nameMatch);
93 });
94 const eventToModify =
95 matchingEvent || (data.fillLast && eventList.find(e => !e.end));
96 if (!eventToModify) {
97 console.error(
98 "Could not find a matching event to end",
99 category,
100 event,
101 data
102 );
103 if (allowFailure) return;
104 throw new Error("No matching event!");
105 }
106
107 eventToModify.end = curTime;
108 }
109 }
110
111 apply(compiler) {
112 if (this.options.disable) return;
113
114 compiler.plugin("compile", () => {
115 this.addTimeEvent("misc", "compile", "start", { watch: false });
116 });
117 compiler.plugin("done", () => {
118 this.addTimeEvent("misc", "compile", "end", { fillLast: true });
119
120 const outputToFile = typeof this.options.outputTarget === "string";
121 chalk.enabled = !outputToFile;
122 const output = this.getOutput();
123 chalk.enabled = true;
124 if (outputToFile) {
125 const writeMethod = fs.existsSync(this.options.outputTarget)
126 ? fs.appendFile
127 : fs.writeFile;
128 writeMethod(this.options.outputTarget, output + "\n", err => {
129 if (err) throw err;
130 console.log(
131 smpTag() + "Outputted timing info to " + this.options.outputTarget
132 );
133 });
134 } else {
135 const outputFunc = this.options.outputTarget || console.log;
136 outputFunc(output);
137 }
138
139 this.timeEventData = {};
140 });
141
142 compiler.plugin("compilation", compilation => {
143 compilation.plugin("normal-module-loader", loaderContext => {
144 loaderContext[NS] = this.provideLoaderTiming;
145 });
146
147 compilation.plugin("build-module", module => {
148 const name = getModuleName(module);
149 if (name) {
150 this.addTimeEvent("loaders", "build", "start", {
151 name,
152 fillLast: true,
153 loaders: getLoaderNames(module.loaders),
154 });
155 }
156 });
157
158 compilation.plugin("succeed-module", module => {
159 const name = getModuleName(module);
160 if (name) {
161 this.addTimeEvent("loaders", "build", "end", {
162 name,
163 fillLast: true,
164 });
165 }
166 });
167 });
168 }
169
170 provideLoaderTiming(info) {
171 const infoData = { id: info.id };
172 if (info.type !== "end") {
173 infoData.loader = info.loaderName;
174 infoData.name = info.module;
175 }
176
177 this.addTimeEvent("loaders", "build-specific", info.type, infoData);
178 }
179};