Skip to content

Commit 76c7adc

Browse files
Merge pull request #18 from stephencookdev/1.0.0-beta
1.0.0 release
2 parents 7f80942 + 5a42bd0 commit 76c7adc

12 files changed

Lines changed: 3242 additions & 134 deletions

File tree

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 Stephen Cook <stephen@stephencookdev.co.uk>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,23 @@ This plugin measures your webpack build speed, giving an output like this:
1616
## Install
1717

1818
```bash
19-
npm install --save speed-measure-webpack-plugin
19+
npm install --save-dev speed-measure-webpack-plugin
2020
```
2121

2222
or
2323

2424
```bash
25-
yarn add speed-measure-webpack-plugin
25+
yarn add -D speed-measure-webpack-plugin
2626
```
2727

28+
## Migrating
29+
30+
SMP follows [semver](https://semver.org/). If upgrading a major version, you can consult [the migration guide](./migration.md).
31+
32+
## Requirements
33+
34+
SMP requires at least Node v6.
35+
2836
## Usage
2937

3038
Change your webpack config from
@@ -43,53 +51,32 @@ to
4351
```javascript
4452
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
4553

46-
const webpackConfig = {
47-
plugins: SpeedMeasurePlugin.wrapPlugins({
48-
MyPlugin: new MyPlugin(),
49-
MyOtherPlugin: new MyOtherPlugin()
50-
})
51-
}
52-
```
53-
54-
If you're using `webpack-merge`, then you can do:
55-
56-
```javascript
5754
const smp = new SpeedMeasurePlugin();
5855

59-
const baseConfig = {
60-
plugins: smp.wrapPlugins({
61-
MyPlugin: new MyPlugin()
62-
}).concat(smp)
63-
// ^ note the `.concat(smp)`
64-
};
65-
66-
const envSpecificConfig = {
67-
plugins: smp.wrapPlugins({
68-
MyOtherPlugin: new MyOtherPlugin()
69-
})
70-
// ^ note no `.concat(smp)`
71-
}
72-
73-
const finalWebpackConfig = webpackMerge([
74-
baseConfig,
75-
envSpecificConfig
76-
]);
77-
56+
const webpackConfig = smp.wrap({
57+
plugins: [
58+
new MyPlugin(),
59+
new MyOtherPlugin()
60+
]
61+
});
7862
```
7963

64+
and you're done! SMP will now be printing timing output to the console by default
65+
8066
## Options
8167

82-
Options are passed in to the constructor
68+
Options are (optionally) passed in to the constructor
8369

8470
```javascript
8571
const smp = new SpeedMeasurePlugin(options);
8672
```
8773

88-
or as the second argument to the static `wrapPlugins`
74+
### `options.disable`
8975

90-
```javascript
91-
SpeedMeasurePlugin.wrapPlugins(pluginMap, options);
92-
```
76+
Type: `Boolean`<br>
77+
Default: `false`
78+
79+
If truthy, this plugin does nothing at all. It is recommended to set this with something similar to `{ disable: !process.env.MEASURE }` to allow opt-in measurements with a `MEASURE=true npm run build`
9380

9481
### `options.outputFormat`
9582

@@ -111,9 +98,44 @@ Default: `console.log`
11198
* If a string, it specifies the path to a file to output to.
11299
* If a function, it will call the function with the output as the first parameter
113100

114-
### `options.disable`
101+
### `options.pluginNames`
102+
103+
Type: `Object`<br>
104+
Default: `{}`
105+
106+
By default, SMP derives plugin names through `plugin.constructor.name`. For some
107+
plugins this doesn't work (or you may want to override this default). This option
108+
takes an object of `pluginName: PluginConstructor`, e.g.
109+
110+
```javascript
111+
const uglify = new UglifyJSPlugin();
112+
const smp = new SpeedMeasurePlugin({
113+
pluginNames: {
114+
customUglifyName: uglify
115+
}
116+
});
117+
118+
const webpackConfig = smp.wrap({
119+
plugins: [
120+
uglify
121+
]
122+
});
123+
```
124+
125+
### `options.granularLoaderData` _(experimental)_
115126

116127
Type: `Boolean`<br>
117128
Default: `false`
118129

119-
If truthy, this plugin does nothing at all. It is recommended to set this with something similar to `{ disable: !process.env.MEASURE }` to allow opt-in measurements with a `MEASURE=true npm run build`
130+
If truthy, this plugin will attempt to break down the loader timing data to give per-loader timing information.
131+
132+
Points of note that the following loaders will have inaccurate results in this mode:
133+
134+
* loaders using separate processes (e.g. `thread-loader`) - these make it difficult to get timing information on the subsequent loaders, as they're not attached to the main thread
135+
* loaders emitting file output (e.g. `file-loader`) - the time taken in outputting the actual file is not included in the running time of the loader
136+
137+
These are restrictions from technical limitations - ideally we would find solutions to these problems before removing the _(experimental)_ flag on this options
138+
139+
## License
140+
141+
[MIT](/LICENSE)

WrappedPlugin/index.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,32 @@ let idInc = 0;
22

33
const genPluginMethod = (orig, pluginName, smp, type) =>
44
function(method, func) {
5+
const timeEventName = pluginName + "/" + type + "/" + method;
56
const wrappedFunc = (...args) => {
67
const id = idInc++;
7-
const timeEventName = pluginName + "/" + type + "/" + method;
88
// we don't know if there's going to be a callback applied to a particular
99
// call, so we just set it multiple times, letting each one override the last
10-
let endCallCount = 0;
11-
const addEndEvent = () => {
12-
endCallCount++;
13-
smp.addTimeEvent("plugins", timeEventName, "end", { id });
14-
};
10+
const addEndEvent = () =>
11+
smp.addTimeEvent("plugins", timeEventName, "end", {
12+
id,
13+
// we need to allow failure, since webpack can finish compilation and
14+
// cause our callbacks to fall on deaf ears
15+
allowFailure: true,
16+
});
1517

1618
smp.addTimeEvent("plugins", timeEventName, "start", {
1719
id,
1820
name: pluginName,
1921
});
22+
// invoke an end event immediately in case the callback here causes webpack
23+
// to complete compilation. If this gets invoked and not the subsequent
24+
// call, then our data will be inaccurate, sadly
25+
addEndEvent();
2026
const ret = func.apply(
2127
this,
2228
args.map(a => wrap(a, pluginName, smp, addEndEvent))
2329
);
24-
25-
// If the end event was invoked as a callback immediately, we can
26-
// don't want to add another end event here (and it can actually cause
27-
// errors, if webpack has finished compilation entirely)
28-
if (!endCallCount) addEndEvent();
30+
addEndEvent();
2931

3032
return ret;
3133
};

colours.js

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
1-
const greenFg = "\x1b[32m";
2-
const yellowFg = "\x1b[33m";
3-
const redFg = "\x1b[31m";
4-
const blackBg = "\x1b[40m";
5-
const bold = "\x1b[1m";
6-
const end = "\x1b[0m";
1+
const chalk = require("chalk");
72

83
module.exports.fg = (text, time) => {
9-
let colour;
10-
if (time >= 0) colour = greenFg;
11-
if (time > 2000) colour = yellowFg;
12-
if (time > 10000) colour = redFg;
4+
let textModifier = chalk.bold;
5+
if (time > 10000) textModifier = textModifier.red;
6+
else if (time > 2000) textModifier = textModifier.yellow;
7+
else textModifier = textModifier.green;
138

14-
return (colour || "") + bold + text + end;
9+
return textModifier(text);
1510
};
1611

17-
module.exports.bg = text => blackBg + greenFg + bold + text + end;
18-
19-
module.exports.stripColours = text => text.replace(/\x1b\[[0-9]+m/g, "");
12+
module.exports.bg = text => chalk.bgBlack.green.bold(text);

index.js

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,55 @@
1+
const path = require("path");
12
const fs = require("fs");
3+
const chalk = require("chalk");
24
const { WrappedPlugin } = require("./WrappedPlugin");
3-
const { getModuleName, getLoaderNames } = require("./utils");
5+
const { getModuleName, getLoaderNames, prependLoader } = require("./utils");
46
const {
57
getHumanOutput,
68
getMiscOutput,
79
getPluginsOutput,
810
getLoadersOutput,
11+
smpTag,
912
} = require("./output");
10-
const { stripColours } = require("./colours");
13+
14+
const NS = path.dirname(fs.realpathSync(__filename));
1115

1216
module.exports = class SpeedMeasurePlugin {
1317
constructor(options) {
1418
this.options = options || {};
1519

1620
this.timeEventData = {};
21+
this.smpPluginAdded = false;
1722

18-
this.wrapPlugins = this.wrapPlugins.bind(this);
23+
this.wrap = this.wrap.bind(this);
1924
this.getOutput = this.getOutput.bind(this);
2025
this.addTimeEvent = this.addTimeEvent.bind(this);
2126
this.apply = this.apply.bind(this);
27+
this.provideLoaderTiming = this.provideLoaderTiming.bind(this);
2228
}
2329

24-
static wrapPlugins(plugins, options) {
25-
if (options.disable) return Object.keys(plugins).map(k => plugins[k]);
26-
27-
const smp = new SpeedMeasurePlugin(options);
28-
29-
const wrappedPlugins = smp.wrapPlugins(plugins);
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+
});
3042

31-
return wrappedPlugins.concat(smp);
32-
}
43+
if (config.module && this.options.granularLoaderData) {
44+
config.module = prependLoader(config.module);
45+
}
3346

34-
wrapPlugins(plugins) {
35-
if (Array.isArray(plugins)) {
36-
let i = 1;
37-
plugins = plugins.reduce((acc, p) => {
38-
acc["plugin " + i++] = p;
39-
return acc;
40-
});
47+
if (!this.smpPluginAdded) {
48+
config.plugins = config.plugins.concat(this);
49+
this.smpPluginAdded = true;
4150
}
42-
plugins = plugins || {};
4351

44-
return Object.keys(plugins).map(
45-
pluginName => new WrappedPlugin(plugins[pluginName], pluginName, this)
46-
);
52+
return config;
4753
}
4854

4955
getOutput() {
@@ -65,6 +71,9 @@ module.exports = class SpeedMeasurePlugin {
6571
}
6672

6773
addTimeEvent(category, event, eventType, data = {}) {
74+
const allowFailure = data.allowFailure;
75+
delete data.allowFailure;
76+
6877
const tED = this.timeEventData;
6978
if (!tED[category]) tED[category] = {};
7079
if (!tED[category][event]) tED[category][event] = [];
@@ -85,12 +94,13 @@ module.exports = class SpeedMeasurePlugin {
8594
const eventToModify =
8695
matchingEvent || (data.fillLast && eventList.find(e => !e.end));
8796
if (!eventToModify) {
88-
console.log(
97+
console.error(
8998
"Could not find a matching event to end",
9099
category,
91100
event,
92101
data
93102
);
103+
if (allowFailure) return;
94104
throw new Error("No matching event!");
95105
}
96106

@@ -107,15 +117,19 @@ module.exports = class SpeedMeasurePlugin {
107117
compiler.plugin("done", () => {
108118
this.addTimeEvent("misc", "compile", "end", { fillLast: true });
109119

120+
const outputToFile = typeof this.options.outputTarget === "string";
121+
chalk.enabled = !outputToFile;
110122
const output = this.getOutput();
111-
if (typeof this.options.outputTarget === "string") {
112-
const strippedOutput = stripColours(output);
123+
chalk.enabled = true;
124+
if (outputToFile) {
113125
const writeMethod = fs.existsSync(this.options.outputTarget)
114126
? fs.appendFile
115127
: fs.writeFile;
116-
writeMethod(this.options.outputTarget, strippedOutput + "\n", err => {
128+
writeMethod(this.options.outputTarget, output + "\n", err => {
117129
if (err) throw err;
118-
console.log("Outputted timing info to " + this.options.outputTarget);
130+
console.log(
131+
smpTag() + "Outputted timing info to " + this.options.outputTarget
132+
);
119133
});
120134
} else {
121135
const outputFunc = this.options.outputTarget || console.log;
@@ -126,6 +140,10 @@ module.exports = class SpeedMeasurePlugin {
126140
});
127141

128142
compiler.plugin("compilation", compilation => {
143+
compilation.plugin("normal-module-loader", loaderContext => {
144+
loaderContext[NS] = this.provideLoaderTiming;
145+
});
146+
129147
compilation.plugin("build-module", module => {
130148
const name = getModuleName(module);
131149
if (name) {
@@ -148,4 +166,14 @@ module.exports = class SpeedMeasurePlugin {
148166
});
149167
});
150168
}
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+
}
151179
};

0 commit comments

Comments
 (0)