UNPKG

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