1 | const path = require("path");
|
2 | const fs = require("fs");
|
3 | const chalk = require("chalk");
|
4 | const { WrappedPlugin, clear } = require("./WrappedPlugin");
|
5 | const {
|
6 | getModuleName,
|
7 | getLoaderNames,
|
8 | prependLoader,
|
9 | tap,
|
10 | } = require("./utils");
|
11 | const {
|
12 | getHumanOutput,
|
13 | getMiscOutput,
|
14 | getPluginsOutput,
|
15 | getLoadersOutput,
|
16 | smpTag,
|
17 | } = require("./output");
|
18 |
|
19 | const NS = path.dirname(fs.realpathSync(__filename));
|
20 |
|
21 | module.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 | };
|