close

Web Workers

This page explains how to configure and use Web Workers in an Rsbuild project.

Web Workers

Web Workers are a type of JavaScript program that runs in the background, independently of other scripts, without affecting the performance of the page. This makes it possible to run long-running scripts, such as ones that handle complex calculations or access remote resources, without blocking the user interface or other scripts. Web workers provide an easy way to run tasks in the background and improve the overall performance of web applications.

Use Web Workers

Import with constructors

Rspack provides built-in support for Web Workers, so you can use them directly in Rsbuild projects without adding extra loaders.

For example, create a file called worker.js:

worker.js
self.onmessage = (event) => {
  const result = event.data * 2;
  self.postMessage(result);
};

Then use this worker in the main thread:

index.js
const worker = new Worker(new URL('./worker.js', import.meta.url));

worker.onmessage = (event) => {
  console.log('The results from Workers:', event.data);
};

worker.postMessage(10);

With the standard constructor syntax, you can also pass WorkerOptions as the second argument. Rsbuild preserves runtime options such as name and credentials, and uses type to determine whether the worker should be handled as a module worker or a classic worker.

index.js
const worker = new Worker(new URL('./worker.js', import.meta.url), {
  credentials: 'include',
  type: 'module',
});

Rspack supports multiple Worker syntaxes by default. See Rspack - Web Workers for more information.

Import with query suffixes

Rsbuild supports importing Web Workers with query suffixes in Rsbuild v2.0.7 and later.

Basic usage

You can also import a Web Worker script by appending ?worker to the import request. The default export is a custom Worker constructor.

index.js
import MyWorker from './worker.js?worker';

const worker = new MyWorker();

worker.postMessage(10);

Worker options

The constructor generated by ?worker only accepts the name option and passes it to the underlying Worker. It does not pass through the full WorkerOptions object. If you need options such as credentials or type, use the standard constructor syntax instead:

index.js
const worker = new Worker(new URL('./worker.js', import.meta.url), {
  credentials: 'include',
  type: 'module',
});

Worker type

For ?worker and ?worker&inline, Rsbuild determines whether to create a module worker based on output.module.

When output.module is true, the generated Worker constructor uses { type: 'module' }; otherwise, it omits type and the browser creates a classic worker.

Inline workers

By default, the worker script is emitted as a separate chunk in the production build. If you want to inline the worker script, add the inline query:

index.js
import InlineWorker from './worker.js?worker&inline';

const worker = new InlineWorker();

worker.postMessage(10);

Type declarations

When you import workers with ?worker in TypeScript code, TypeScript may prompt that the module is missing a type definition:

TS2307: Cannot find module './worker.ts?worker' or its corresponding type declarations.

To fix this, add a type declaration file, such as src/env.d.ts, and reference the preset types provided by @rsbuild/core:

/// <reference types="@rsbuild/core/types" />

The preset types include declarations for *?worker, *?worker&inline, and *?inline&worker.

Importing package workers

JavaScript files in node_modules are not compiled by Rsbuild's JavaScript rule by default. Since the ?worker query is handled by this rule, it will not take effect when the request resolves to a worker file in node_modules, whether the import is written in your application code or inside a package.

To make the query take effect, add the package that contains the worker file to source.include:

rsbuild.config.ts
export default {
  source: {
    include: [/node_modules[\\/]some-package[\\/]/],
  },
};

Loading scripts from remote URLs

By default, the worker script is emitted as a separate chunk. You can upload this file to a CDN, but it must follow the same-origin policy.

If you want your worker scripts to be accessible across domains, a common solution is to load them via importScripts (not subject to CORS). You can refer to the following code:

index.js
// https://github.com/jantimon/remote-web-worker
import 'remote-web-worker';

const worker = new Worker(new URL('./worker.js', import.meta.url), {
  type: 'classic',
});

worker.onmessage = (event) => {
  console.log('The results from Workers:', event.data);
};

worker.postMessage(10);

For detailed discussions on cross-domain issues, please refer to Discussions - webpack 5 web worker support for CORS?

Browser compatibility

When your application includes both main-thread code and Web Workers created with new Worker, note that Web Workers run in isolated threads and do not share the main-thread environment. As a result, Rsbuild’s polyfill entry mode does not apply to Web Workers. In this case, it’s recommended to use the usage mode to inject the required polyfills directly into each Web Worker.

rsbuild.config.ts
export default {
  output: {
    polyfill: 'usage',
  },
};

Standalone build

Rsbuild supports standalone building of Web Workers bundles. When you need to configure independent build options for Web Workers or provide Web Workers for use by other applications, you can use the following methods.

Set Rsbuild's output.target configuration option to 'web-worker' to generate build artifacts that run in Worker threads.

rsbuild.config.ts
export default {
  output: {
    target: 'web-worker',
  },
};

Use environments to build both Web Workers and the main application simultaneously:

rsbuild.config.ts
export default {
  environments: {
    web: {
      // Build configuration for the main application
    },
    webWorker: {
      // Build configuration for Web Workers
      output: {
        target: 'web-worker',
      },
    },
  },
};