# The instrumented build is not your build: reading speed-measure-webpack-plugin

> speed-measure-webpack-plugin wraps your webpack config and times each plugin and loader, which is a small change and a useful answer. The measurements themselves reach into your loader chain, and the advertised runtime floor and the repository's own toolchain have drifted apart.

**stephencookdev/speed-measure-webpack-plugin** — ⏱ See how fast (or not) your plugins and loaders are, so you can optimise your builds

- Repository: https://github.com/stephencookdev/speed-measure-webpack-plugin
- Stars: 2,439 · Forks: 79
- Language: JavaScript
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/stephencookdev-speed-measure-webpack-plugin

## The runtime floor is Node 6 while the test suite runs on bun

The requirements section says the plugin needs at least Node v6 and accepts every webpack version from 1 to 5. The manifest agrees on both counts: `engines.node` is `>=6.0.0`, and the single peer dependency is `webpack` at `^1 || ^2 || ^3 || ^4 || ^5`. What the same manifest also carries is a different toolchain. `packageManager` pins bun at 1.3.8, the `test` script is `bun test`, and the `ci` script chains bun across four steps ending in lint. A `devEngines` block with `runtime` set to node `>=18` and `onFail` set to `error` sits alongside, and a `.nvmrc` file is at the root. Installation itself has two forms and neither of them constrains anything further:

```bash
npm install --save-dev speed-measure-webpack-plugin
```

```bash
yarn add -D speed-measure-webpack-plugin
```

So the floor a consumer inherits is Node 6, while the code that exercises this plugin is verified under a Node 18 requirement on bun. That gap is the first thing to be aware of, because nothing in the compatibility claim was rechecked against the modern toolchain in the manifest.

## No files whitelist, so .npmignore alone decides what ships

The manifest has no `files` array. Nothing in package.json restricts the published tarball, so what reaches npm is decided entirely by `.npmignore` at the root. That is the opposite of a manifest that enumerates its payload, and the difference shows in the tree, which carries directories unrelated to running the plugin at runtime: `__tests__/`, `examples/`, `scripts/`, `.husky/`, plus `preview.png` and `logo.svg`. Whether any of them travels to a consumer is decided by one ignore file that the manifest does not reference, so a directory added at the root ships unless somebody remembers to add a line.

## Every markdown file passes two prose linters

One script in the manifest runs `alex ./*.md && write-good --no-adverb ./*.md`, which means every markdown file at the repository root goes through an inclusive language checker and a weasel word detector before it can merge. A second script is `prettier --check "*.{js,json,css,md}"`, so the same formatter covers prose and source together. The `--no-adverb` flag is the revealing part: adverb detection is deliberately switched off, which reads as a concession to a technical audience that writes densely. The consequence for anyone reading the guide is that the wording has been through two automated prose gates, and the same gates apply to any change that touches those files.

## Per loader timing rewrites your loader chain to take the measurement

`granularLoaderData` is marked experimental, and the stated reason is not that the numbers are imprecise but that taking them changes what is being built. With the flag on, the plugin prepends its own timing loader to each matching loader rule. Two categories are called out as giving inaccurate results: loaders that run in separate processes, with `thread-loader` as the example, and loaders that emit file output, with `file-loader` as the example. The escape hatch is `excludedLoaders`, accepting strings or regular expressions, matching both the original loader request and the normalized package name:

```javascript
const smp = new SpeedMeasurePlugin({
  granularLoaderData: true,
  excludedLoaders: ["thread-loader", /mini-css-extract-plugin/],
});
```

With the flag off, loaders are measured in groups, which is the default and the safer one. Two of the printed rows have their own glossary entries, and both admit a limit: general output time is attributed to webpack reading from the file system or to anything outside what the plugin can measure, and modules without loaders means vanilla JavaScript files that webpack handles without a loader in the chain. So the report has two buckets it cannot break down, and both are larger than they look on a project with a big vendor bundle.

## Plugin names come from plugin.constructor.name

`pluginNames` defaults to an empty object, so by default each entry is labelled by reading `plugin.constructor.name`, and the documentation says plainly that this does not work for some plugins. The option exists to override that, taking an object of `pluginName: PluginConstructor` pairs, so you hand the plugin an instance and give it a label. Exclusions sit on the same naming layer and accept three shapes: a plugin name, a plugin constructor, or a regular expression matched against names, with matching checking both the aliases you registered and the raw constructor names. Identification and exclusion therefore share one weak point, which is why a single override tends to fix both. The override takes an instance rather than a class, so the same object is both the thing being named and the thing being timed:

```javascript
const uglify = new UglifyJSPlugin();
const smp = new SpeedMeasurePlugin({
  pluginNames: {
    customUglifyName: uglify,
  },
});
```

The label in the output is then whatever string you chose, which is worth picking carefully if the report is going into a dashboard or a shared file.

## compareLoadersBuild writes a file into your project

`compareLoadersBuild` takes an object with a required `filePath`, and the documented example points it at `./buildInfo.json`. Enabled, it records module count and time spent per loader so that a later run can be diffed against your codebase over time. Two things follow. The file lands wherever you say, inside the project in the example, so a measured build starts producing a new artefact that belongs in your ignore list rather than in a commit. And the option only adds detail when `outputFormat` is `"humanVerbose"`, so the on disk record and the console output are two separate settings that have to agree with each other to be useful. That verbose format is also what unlocks the per loader file ranking, where `loaderTopFiles` is a plain number defaulting to zero:

```javascript
const smp = new SpeedMeasurePlugin({
  outputFormat: "humanVerbose",
  loaderTopFiles: 10,
});
```

At zero nothing is ranked, at ten you get the ten costliest files per loader, and the option has no effect at all outside the verbose format.

## neutrino.js sits in the tree and no option mentions it

The root listing carries `neutrino.js`, and the examples folder holds `neutrinorc.js` beside `basic.webpack.config.js` and `webpack-merge.config.js`. None of the documented options has anything to do with Neutrino, and no section of the guide describes that file, so it is an integration in the repository that the documentation does not cover. Next to it is a capitalised `WrappedPlugin/` directory, whose purpose is also unstated, although the name matches the proxy wrapping the usage section describes. Two capitalised or unexplained shapes in one root, one of them mentioned in prose and one of them not.

## Five years between releases, and three spellings of the version

The release history has a long gap in it. v1.4.2 was published on 2021-01-23, v1.5.0 on 2021-03-28, and then nothing until v1.6.0 on 2026-03-24, which carries the same timestamp as the last recorded push to the default branch. The three releases also disagree on how to write a version: the tags carry a v and a patch number, the release titles drop the patch and read v1.6, v1.5 and v1.4, and the manifest carries a bare 1.6.0. The project states that it follows semver and points major upgrades at a migration guide in `migration.md`. With 2,439 stars, 79 forks and 64 open issues, the commit date is what tells you how current this is; the release titles alone would suggest a much older package.

## Conclusion

This plugin answers one question well, namely which of your webpack plugins and loaders is costing you the time, and the wrapping model costs you one line of change to a config. Two things to weigh first. The runtime floor is advertised as Node v6, while the repository's own test, lint and audit path runs on bun 1.3.8, so the compatibility claim is inherited rather than demonstrated here. And the per loader numbers are experimental precisely because the measurement prepends its own loader to your rules, which means the build being timed is not the build you ship. Verify which webpack major you are on against the declared peer range, add whatever compareLoadersBuild writes to your ignore list, and treat the last recorded push, 2026-03-24, as the current state rather than the release titles.

## FAQ

### How do I install speed-measure-webpack-plugin?

Two forms are documented: npm install --save-dev speed-measure-webpack-plugin, or yarn add -D speed-measure-webpack-plugin. Once installed you pass your existing config object through smp.wrap(...) and timing output prints to the console by default.

### Which webpack versions does speed-measure-webpack-plugin support?

The requirements section says every webpack version from 1 through 5, and the manifest declares webpack as a peer dependency at ^1 || ^2 || ^3 || ^4 || ^5. The same section puts the Node floor at v6, while a devEngines block requires node 18 or newer for repository work.

### What does granularLoaderData do in speed-measure-webpack-plugin?

It is an experimental flag that switches from grouped loader timing to per loader timing by prepending the plugin's timing loader to each matching rule. The documentation warns that loaders using separate processes, such as thread-loader, and loaders emitting file output, such as file-loader, return inaccurate results under it.

### How do I stop speed-measure-webpack-plugin from measuring normal builds?

The disable option is a boolean defaulting to false, and the documented pattern is { disable: !process.env.MEASURE }. That lets you opt in per run with MEASURE=true npm run build and leave ordinary builds untouched.

### Does speed-measure-webpack-plugin write any files?

Only when you configure it to. The compareLoadersBuild option requires a filePath, and its example points at ./buildInfo.json, recording module count and time per loader so that runs can be compared over time.

## Sources

- [Issues](https://github.com/stephencookdev/speed-measure-webpack-plugin/issues)
- [License: MIT](https://github.com/stephencookdev/speed-measure-webpack-plugin/blob/master/LICENSE)
- [README](https://github.com/stephencookdev/speed-measure-webpack-plugin/blob/master/README.md)
- [Releases](https://github.com/stephencookdev/speed-measure-webpack-plugin/releases)
- [stephencookdev/speed-measure-webpack-plugin on GitHub](https://github.com/stephencookdev/speed-measure-webpack-plugin)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/stephencookdev-speed-measure-webpack-plugin
