You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
5.7 KiB
JavaScript
195 lines
5.7 KiB
JavaScript
|
6 years ago
|
const path = require("path");
|
||
|
|
const fs = require("fs");
|
||
|
|
const chalk = require("chalk");
|
||
|
|
const { WrappedPlugin, clear } = require("./WrappedPlugin");
|
||
|
|
const {
|
||
|
|
getModuleName,
|
||
|
|
getLoaderNames,
|
||
|
|
prependLoader,
|
||
|
|
tap,
|
||
|
|
} = require("./utils");
|
||
|
|
const {
|
||
|
|
getHumanOutput,
|
||
|
|
getMiscOutput,
|
||
|
|
getPluginsOutput,
|
||
|
|
getLoadersOutput,
|
||
|
|
smpTag,
|
||
|
|
} = require("./output");
|
||
|
|
|
||
|
|
const NS = path.dirname(fs.realpathSync(__filename));
|
||
|
|
|
||
|
|
module.exports = class SpeedMeasurePlugin {
|
||
|
|
constructor(options) {
|
||
|
|
this.options = options || {};
|
||
|
|
|
||
|
|
this.timeEventData = {};
|
||
|
|
this.smpPluginAdded = false;
|
||
|
|
|
||
|
|
this.wrap = this.wrap.bind(this);
|
||
|
|
this.getOutput = this.getOutput.bind(this);
|
||
|
|
this.addTimeEvent = this.addTimeEvent.bind(this);
|
||
|
|
this.apply = this.apply.bind(this);
|
||
|
|
this.provideLoaderTiming = this.provideLoaderTiming.bind(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
wrap(config) {
|
||
|
|
if (this.options.disable) return config;
|
||
|
|
if (Array.isArray(config)) return config.map(this.wrap);
|
||
|
|
if (typeof config === "function")
|
||
|
|
return (...args) => this.wrap(config(...args));
|
||
|
|
|
||
|
|
config.plugins = (config.plugins || []).map(plugin => {
|
||
|
|
const pluginName =
|
||
|
|
Object.keys(this.options.pluginNames || {}).find(
|
||
|
|
pluginName => plugin === this.options.pluginNames[pluginName]
|
||
|
|
) ||
|
||
|
|
(plugin.constructor && plugin.constructor.name) ||
|
||
|
|
"(unable to deduce plugin name)";
|
||
|
|
return new WrappedPlugin(plugin, pluginName, this);
|
||
|
|
});
|
||
|
|
|
||
|
|
if (config.optimization && config.optimization.minimizer) {
|
||
|
|
config.optimization.minimizer = config.optimization.minimizer.map(
|
||
|
|
plugin => {
|
||
|
|
return new WrappedPlugin(plugin, plugin.constructor.name, this);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (config.module && this.options.granularLoaderData) {
|
||
|
|
config.module = prependLoader(config.module);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!this.smpPluginAdded) {
|
||
|
|
config.plugins = config.plugins.concat(this);
|
||
|
|
this.smpPluginAdded = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return config;
|
||
|
|
}
|
||
|
|
|
||
|
|
getOutput() {
|
||
|
|
const outputObj = {};
|
||
|
|
if (this.timeEventData.misc)
|
||
|
|
outputObj.misc = getMiscOutput(this.timeEventData.misc);
|
||
|
|
if (this.timeEventData.plugins)
|
||
|
|
outputObj.plugins = getPluginsOutput(this.timeEventData.plugins);
|
||
|
|
if (this.timeEventData.loaders)
|
||
|
|
outputObj.loaders = getLoadersOutput(this.timeEventData.loaders);
|
||
|
|
|
||
|
|
if (this.options.outputFormat === "json")
|
||
|
|
return JSON.stringify(outputObj, null, 2);
|
||
|
|
if (typeof this.options.outputFormat === "function")
|
||
|
|
return this.options.outputFormat(outputObj);
|
||
|
|
return getHumanOutput(outputObj, {
|
||
|
|
verbose: this.options.outputFormat === "humanVerbose",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
addTimeEvent(category, event, eventType, data = {}) {
|
||
|
|
const allowFailure = data.allowFailure;
|
||
|
|
delete data.allowFailure;
|
||
|
|
|
||
|
|
const tED = this.timeEventData;
|
||
|
|
if (!tED[category]) tED[category] = {};
|
||
|
|
if (!tED[category][event]) tED[category][event] = [];
|
||
|
|
const eventList = tED[category][event];
|
||
|
|
const curTime = new Date().getTime();
|
||
|
|
|
||
|
|
if (eventType === "start") {
|
||
|
|
data.start = curTime;
|
||
|
|
eventList.push(data);
|
||
|
|
} else if (eventType === "end") {
|
||
|
|
const matchingEvent = eventList.find(e => {
|
||
|
|
const allowOverwrite = !e.end || !data.fillLast;
|
||
|
|
const idMatch = e.id !== undefined && e.id === data.id;
|
||
|
|
const nameMatch =
|
||
|
|
!data.id && e.name !== undefined && e.name === data.name;
|
||
|
|
return allowOverwrite && (idMatch || nameMatch);
|
||
|
|
});
|
||
|
|
const eventToModify =
|
||
|
|
matchingEvent || (data.fillLast && eventList.find(e => !e.end));
|
||
|
|
if (!eventToModify) {
|
||
|
|
console.error(
|
||
|
|
"Could not find a matching event to end",
|
||
|
|
category,
|
||
|
|
event,
|
||
|
|
data
|
||
|
|
);
|
||
|
|
if (allowFailure) return;
|
||
|
|
throw new Error("No matching event!");
|
||
|
|
}
|
||
|
|
|
||
|
|
eventToModify.end = curTime;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
apply(compiler) {
|
||
|
|
if (this.options.disable) return;
|
||
|
|
|
||
|
|
tap(compiler, "compile", () => {
|
||
|
|
this.addTimeEvent("misc", "compile", "start", { watch: false });
|
||
|
|
});
|
||
|
|
tap(compiler, "done", () => {
|
||
|
|
clear();
|
||
|
|
this.addTimeEvent("misc", "compile", "end", { fillLast: true });
|
||
|
|
|
||
|
|
const outputToFile = typeof this.options.outputTarget === "string";
|
||
|
|
chalk.enabled = !outputToFile;
|
||
|
|
const output = this.getOutput();
|
||
|
|
chalk.enabled = true;
|
||
|
|
if (outputToFile) {
|
||
|
|
const writeMethod = fs.existsSync(this.options.outputTarget)
|
||
|
|
? fs.appendFileSync
|
||
|
|
: fs.writeFileSync;
|
||
|
|
writeMethod(this.options.outputTarget, output + "\n");
|
||
|
|
console.log(
|
||
|
|
smpTag() + "Outputted timing info to " + this.options.outputTarget
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
const outputFunc = this.options.outputTarget || console.log;
|
||
|
|
outputFunc(output);
|
||
|
|
}
|
||
|
|
|
||
|
|
this.timeEventData = {};
|
||
|
|
});
|
||
|
|
|
||
|
|
tap(compiler, "compilation", compilation => {
|
||
|
|
tap(compilation, "normal-module-loader", loaderContext => {
|
||
|
|
loaderContext[NS] = this.provideLoaderTiming;
|
||
|
|
});
|
||
|
|
|
||
|
|
tap(compilation, "build-module", module => {
|
||
|
|
const name = getModuleName(module);
|
||
|
|
if (name) {
|
||
|
|
this.addTimeEvent("loaders", "build", "start", {
|
||
|
|
name,
|
||
|
|
fillLast: true,
|
||
|
|
loaders: getLoaderNames(module.loaders),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
tap(compilation, "succeed-module", module => {
|
||
|
|
const name = getModuleName(module);
|
||
|
|
if (name) {
|
||
|
|
this.addTimeEvent("loaders", "build", "end", {
|
||
|
|
name,
|
||
|
|
fillLast: true,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
provideLoaderTiming(info) {
|
||
|
|
const infoData = { id: info.id };
|
||
|
|
if (info.type !== "end") {
|
||
|
|
infoData.loader = info.loaderName;
|
||
|
|
infoData.name = info.module;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.addTimeEvent("loaders", "build-specific", info.type, infoData);
|
||
|
|
}
|
||
|
|
};
|