Library / SDK
webpack/thread-loader avatar
webpack/thread-loader

webpack/thread-loader: run your expensive loaders in a worker pool, and pay for it in process overhead

Runs the following loaders in a worker pool

1,130 stars69 forksJavaScriptMIT

At a glance

What is it?
thread-loader is a webpack loader that pushes the loaders behind it into a pool of worker processes, which is a large speedup for expensive transforms and a real cost everywhere else, since each worker is a separate Node.js process with about 600ms of startup overhead. Its README is unusually honest about the three things a loader cannot do once it is in a worker, and it offers a warmup call to hide the boot cost before the real build starts.
Who is it for?
Adopt thread-loader when a single loader dominates your build time, for example a Babel or Sass transform over a large source tree, since moving it off the main thread is the entire point and the configuration is small. Do not adopt it to speed up a build in general, because every worker is a separate process with about 600ms of startup cost and inter-process communication overhead, and the README tells you to use it only for expensive operations.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What it actually does

thread-loader runs the specified loaders in a worker pool, and that is the whole feature. You put it in front of the loader you want to move off webpack's main thread, and the work happens in a pool of child processes instead.

The value is real for one specific case. A Babel or TypeScript transform over a few thousand files is CPU bound and serial, so the build is as slow as one core can make it. Moving that work into a pool spreads it across cores, which is where the speedup comes from.

The cost is stated just as plainly, in the same short section. Each worker is a separate Node.js process, which has an overhead of about 600 milliseconds, and there is additional overhead from inter-process communication. So the loader trades a fixed cost for a variable one, and it only pays off when the variable one is large.

Hence the instruction that appears as a quote right after the limitations, to use this loader only for expensive operations. That is the entire guidance in one sentence, and a great deal of configuration exists to make the fixed cost manageable.

Three things a loader loses inside a worker

The limitations list is short and specific, and knowing them before you configure this saves an afternoon.

Loaders cannot emit files. A loader that writes an asset to disk as a side effect of transforming a module will not behave the same way in a worker, because the work has moved out of the process that webpack is watching.

Loaders cannot use custom loader APIs, that is, APIs provided by plugins. If your loader talks to a plugin through a custom hook, that plugin is not in the worker with it.

Loaders cannot access webpack options. This is the one that surprises people, because a loader that reads `mode`, `devtool` or a path from the webpack configuration will find nothing inside a worker, because the webpack configuration does not travel with the job.

So the rule of thumb is that a loader must be a pure function of the file content and its own options. If it needs to know anything about the build beyond its own options, it does not belong behind this loader.

The documentation also notes that loaders with equal options will share worker pools, which is a useful detail, because it means the pool is keyed by the options you pass and two rules with the same configuration reuse the same workers rather than each spawning their own.

Tuning the pool

The options block in the README is where the trade-off is made explicit, and the defaults are sensible.

`workers` is the number of spawned workers, defaulting to the number of CPUs minus one, with a fallback to 1 when `require('os').cpus()` is undefined. `workerParallelJobs` is the number of jobs a worker processes in parallel, defaulting to 20. `workerNodeArgs` passes extra Node.js arguments, and the example is `['--max-old-space-size=1024']`, which is what you reach for when the transform is memory hungry.

`poolRespawn` allows a dead worker pool to be respawned, and the comment is a warning, since respawning slows down the entire compilation and should be set to false for development. `poolTimeout` is the timeout for killing the worker processes when idle, defaulting to 500 milliseconds, and it can be set to Infinity for watching builds to keep the workers alive.

`poolParallelJobs` is the number of jobs the pool distributes to the workers, defaulting to 200, and the comment says to decrease it for a less efficient but fairer distribution. That distinction between workerParallelJobs and poolParallelJobs is easy to miss, because the names look like variants of each other, and they are not: one is how much a single worker does at once, the other is how much the pool hands out.

And `name` gives the pool a name, which can be used to create different pools with otherwise identical options, so two rules that need the same worker count but should not share workers can be separated.

Warming the pool before the build starts

The prewarming section exists to solve the one problem the worker model creates, and it is the most useful thing in the README for a build that runs often.

To prevent the high delays when booting workers, it is possible to warm up the worker pool. This boots the maximum number of workers in the pool and loads the specified modules into the Node.js module cache.

js
const threadLoader = require('thread-loader');

threadLoader.warmup(
  {
    // pool options, like passed to loader options
    // must match loader options to boot the correct pool
  },
  [
    // modules to load
    // can be any module, i.e.
    'babel-loader',
    '@babel/preset-env',
    'sass-loader',
  ],
);

Two details in that snippet matter. The pool options must match the loader options, because the pool is keyed by them, so a warmup with different options would boot a pool your build never uses. And the modules list is where the real win is, since loading Babel and its preset into the module cache before the first file is transformed removes the per-worker parse and compile cost that would otherwise be paid once per worker.

In practice the warmup belongs in a build script that runs before webpack, not inside the webpack config, because it has to happen before the compilation starts.

The project behind the loader

The repository is a small, conventional webpack loader project, and its tooling tells you the standards it holds itself to.

The build is Babel, with `cross-env NODE_ENV=production babel src -d dist --copy-files` and a clean step before it. The published artefact is the `dist` directory, and the entry point is `dist/cjs.js`.

Testing is Jest, with `test:only` running the suite, `test:watch` for the watch mode, `test:manual` that builds and opens the manual test application in webpack-dev-server, and `test:coverage` collecting coverage from the source files. The `pretest` hook runs the lint suite first, so a pull request that fails lint does not reach the tests.

The lint family is three tools run in parallel. Prettier checks formatting, ESLint checks the code, and cspell checks spelling, with a `.cspell.json` dictionary for the terms the project uses. There is a `commitlint` configuration that runs from the main branch, a `husky` configuration installed by the `prepare` script, and a `lint-staged` configuration, so the commit message format and the staged file formatting are both enforced locally.

There is also an `example/` directory with a Babel configuration, a shared stylesheet, an entry point and a webpack configuration, which is the usual way a loader documents its own usage.

The package declares `webpack` as a peer dependency at `^5.0.0` and requires Node `>= 16.10.0`, and the author is Tobias Koppers.

Where this is the wrong choice

The first wrong case is a build that is not dominated by one loader. A typical modern build spends its time in several places, and parallelising one of them while paying 600 milliseconds per worker and adding inter-process communication can make the build slower. The measurement to make is which loader is the bottleneck, and if the answer is that none of them dominates, this loader is overhead.

The second is a loader with side effects. Anything that emits a file, uses a plugin's custom loader API, or reads the webpack configuration cannot go behind thread-loader, and a project with a plugin that extends the loader API has to leave this out.

The third is a development watch loop, unless you set the pool timeout to Infinity. Killing idle workers after half a second is right for a one off build and wrong for a watch session, where the workers would be respawned on every rebuild. The documentation says as much, that pool respawning slows down the entire compilation and should be disabled for development, and the timeout can be set to Infinity to keep workers alive.

The fourth is a project that has moved off webpack's older architecture. The peer dependency is webpack 5 or later, and a loader that depends on a plugin's custom loader API is explicitly out of scope, which means the ecosystem of loaders that would benefit is narrower than it was when this was written.

On the state of the project, the releases are v4.0.2 from May 2023, v4.0.3 and v4.0.4 from September 2024, and the last push was on 2026-08-12 with the repository not archived. The licence is MIT, and the funding link points at the webpack project's OpenCollective.

Editorial conclusion

Adopt thread-loader when a single loader dominates your build time, for example a Babel or Sass transform over a large source tree, since moving it off the main thread is the entire point and the configuration is small. Do not adopt it to speed up a build in general, because every worker is a separate process with about 600ms of startup cost and inter-process communication overhead, and the README tells you to use it only for expensive operations. Verify first that the loader you want to parallelise does not emit files, does not rely on a custom loader API from a plugin, and does not need the webpack options, since those three capabilities are lost inside a worker, and that your webpack version satisfies the peer dependency of 5.0.0 or later.

Frequently asked questions

What does thread-loader do in a webpack build?

It runs the loaders placed after it in a pool of worker processes instead of on webpack's main thread, which speeds up builds where one transform dominates. Each worker is a separate Node.js process with about 600 milliseconds of overhead, plus inter-process communication cost, so the documentation advises using it only for expensive operations.

What are the limitations of running a loader in a worker pool?

Loaders in a worker pool cannot emit files, cannot use custom loader APIs provided by plugins, and cannot access the webpack options. In other words the loader has to be a pure function of the file content and its own options, and a rule that needs anything else from the build does not belong behind thread-loader.

How do I configure the thread-loader worker pool?

The options are workers, the number of spawned workers, defaulting to the CPU count minus one, workerParallelJobs for jobs per worker, defaulting to 20, poolParallelJobs for jobs the pool distributes, defaulting to 200, poolTimeout for killing idle workers, defaulting to 500 milliseconds, poolRespawn, workerNodeArgs for extra Node.js flags, and name to separate pools that would otherwise share workers.

How do I warm up the thread-loader worker pool?

Call `threadLoader.warmup(options, modules)` before the compilation starts, passing the same pool options the loader uses and the modules to preload, for example `babel-loader`, `@babel/preset-env` and `sass-loader`. The options must match the loader options, because the pool is keyed by them, and the modules are loaded into the Node.js module cache to avoid the boot delay on every worker.

Which webpack versions does thread-loader support?

The package declares webpack as a peer dependency with a range of `^5.0.0`, and it requires Node 16.10.0 or later. The current release is 4.0.4, published in September 2024, with the last push to the repository on 2026-08-12, and the licence is MIT.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. webpack/thread-loader on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/webpack-thread-loader.svg)](https://hysenlabs.com/projects/webpack-thread-loader)

Was this useful?