close
CC 4.0 协议

本节内容派生于以下链接指向的内容 ,并遵守 CC BY 4.0 许可证的规定。

以下内容如果没有特殊声明,可以认为都是基于原内容的修改和删减后的结果。

Web Workers

Rspack 提供了对 Web Workers 的内置支持,这意味着你不需要任何的 Loader 就可以直接使用 Web Workers。

使用方式

基本的 Worker 创建语法:

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

通过 name 属性可以自定义 Worker 的 chunk 名称(需要属性值可被静态分析),此名称会替换生成的 chunk 文件名中的 [name] 占位符:

new Worker(new URL('./worker.js', import.meta.url), {
  name: 'my-worker',
});
Info

选择该语法的原因在于其具备良好的标准兼容性,它基于标准的 ECMAScript 模块规范,即使在没有构建工具的环境中也能正常运行。例如,它可以直接在支持 ES modules 的现代浏览器中执行。

使用示例可参考:

限制

  1. 由于 Rspack 依赖静态分析来识别 worker 的使用,因此它有以下两个限制:

    • new Worker 以及其他类似 worker 的 API 通常也可以接受 URL 的字符串表示,但 Rspack 只支持传入 URL 对象。实际使用中,你必须传入 new URL('./worker.js', import.meta.url)

    • Rspack 无法静态分析 new Worker 中使用的变量。例如,以下代码将不起作用:

      const url = new URL('./path/to/worker.js', import.meta.url);
      const worker = new Worker(url);
  2. /* webpackEntryOptions: { filename: "workers/[name].js" } */ 魔法注释目前仍不支持。

内置语法

new Worker() 以外,Rspack 还默认支持以下内置形式:

const sharedWorker = new SharedWorker(
  new URL('./shared-worker.js', import.meta.url),
);
sharedWorker.port.start();
  • import { Worker } from "worker_threads":常用于 Node.js 环境,详见 Worker threads
import { Worker } from 'worker_threads';

const worker = new Worker(new URL('./node-worker.js', import.meta.url));
navigator.serviceWorker
  .register(new URL('./sw.js', import.meta.url))
  .then((registration) => {
    console.log('Service worker registration succeeded:', registration);
  });

自定义语法

可以通过 module.parser.javascript.worker 告诉 Rspack 哪些表达式要按 Worker 类入口语法处理:

  • true 保留内置默认值,等价于 ['...']
  • false 关闭 worker 语法分析,等价于 []
  • 数组会替换默认值;如果你想在内置默认值的基础上扩展,记得保留 '...'

内置语法也是通过同一套机制实现的。'...' 这个占位符可以展开为以下条目:

rspack.config.mjs
export default {
  module: {
    parser: {
      javascript: {
        worker: [
          'Worker',
          'SharedWorker',
          'navigator.serviceWorker.register()',
          'Worker from worker_threads',
        ],
      },
    },
  },
};

下面这些规则说明了自定义语法的含义:

  • 末尾带 () 表示匹配调用语法;末尾不带 () 表示匹配构造器语法。
    • 'worker' 会匹配 new worker(new URL("./worker.js", import.meta.url))
    • 'worker()' 会匹配 worker(new URL("./worker.js", import.meta.url))
  • 也支持成员语法,但根对象必须是一个自由变量。
    • 'foo.bar' 会匹配 new foo.bar(new URL("./worker.js", import.meta.url))
    • 'foo.bar()' 会匹配 foo.bar(new URL("./worker.js", import.meta.url))
  • 可以使用 * 按变量名精确匹配根对象。和普通成员语法不同,这里的根对象是一个已声明变量,而不是自由变量。
    • '*foo.bar()' 会匹配 const foo = new WorkletContext(); foo.bar(new URL("./processor.js", import.meta.url))
    • 目前仅支持调用语法,构造器语法如 '*foo.bar' 暂不支持。
  • 可以使用 from 来匹配来自某个精确 ESM source 的导入绑定。
    • 'Foo from pkg' 会匹配 import { Foo } from 'pkg'; new Foo(new URL("./worker.js", import.meta.url))
    • 'default from pkg' 会匹配 import Foo from 'pkg'; new Foo(new URL("./worker.js", import.meta.url))
    • 'foo() from pkg' 会匹配 import { foo } from 'pkg'; foo(new URL("./worker.js", import.meta.url))
rspack.config.mjs
export default {
  module: {
    parser: {
      javascript: {
        worker: [
          // 自定义语法
          'MyWorker',
          'myWorker()',
          'CSS.paintWorklet.addModule()',
          '*audioContext.audioWorklet.addModule()',
          'default from web-worker',
          // 扩展内置语法
          '...',
        ],
      },
    },
  },
};

下面的示例展示了这些自定义语法规则在实际中的用法。

前几个示例基于内置语法。实际使用中,你不需要把它们再写进配置里,因为它们已经包含在默认的 '...' 中。这里展示它们只是为了说明自定义语法规则是如何工作的:

  • 内置构造器语法:WorkerSharedWorker

    WorkerSharedWorker 都是构造器,而不是函数,因此对应的语法字符串末尾不需要带 ()

    const worker = new Worker(new URL('./worker.js', import.meta.url));
    
    const sharedWorker = new SharedWorker(
      new URL('./shared-worker.js', import.meta.url),
    );
  • 内置调用语法:navigator.serviceWorker.register()

    这个例子展示了为什么末尾的 () 很重要:register() 是函数调用,而不是构造器。

    await navigator.serviceWorker.register(new URL('./sw.js', import.meta.url));
  • 内置导入语法:Worker from worker_threads

    这个例子展示了 from 规则。import source 必须精确匹配。

    import { Worker } from 'worker_threads';
    
    const worker = new Worker(new URL('./node-worker.js', import.meta.url));

下面这些示例展示了一些生态中常见、可以通过自定义语法支持的模式:

  • CSS Worklet

    CSS.paintWorklet.addModule() 是一个直接的成员函数调用,因此配置字符串末尾需要带 ()

    rspack.config.mjs
    export default {
      module: {
        parser: {
          javascript: {
            worker: [
              'CSS.paintWorklet.addModule()',
              'CSS.layoutWorklet.addModule()',
              'CSS.animationWorklet.addModule()',
              '...',
            ],
          },
        },
      },
    };
    await CSS.paintWorklet.addModule(
      new URL('./paint-worklet.js', import.meta.url),
    );
    
    await CSS.layoutWorklet.addModule(
      new URL('./layout-worklet.js', import.meta.url),
    );
    
    await CSS?.animationWorklet?.addModule(
      new URL('./animation-worklet.js', import.meta.url),
    );
  • AudioWorklet

    和 CSS Worklet 不同,audioContext.audioWorklet.addModule(...) 中的根对象不是自由变量,而是由 const audioContext = new AudioContext(); 声明出来的变量。因此,自定义语法需要以 * 开头,用来表示根对象是一个已声明变量。

    rspack.config.mjs
    export default {
      module: {
        parser: {
          javascript: {
            worker: ['*audioContext.audioWorklet.addModule()', '...'],
          },
        },
      },
    };
    const audioContext = new AudioContext();
    
    await audioContext.audioWorklet.addModule(
      new URL('./noise-processor.js', import.meta.url),
    );
  • 从第三方包导入 worker

    下面这些例子展示了 from 在不同导入方式下是如何工作的。

    rspack.config.mjs
    export default {
      module: {
        parser: {
          javascript: {
            worker: [
              'default from web-worker',
              'createWorker() from @myorg/worker',
              '...',
            ],
          },
        },
      },
    };
    import Worker from 'web-worker';
    
    const browserWorker = new Worker(
      new URL('./browser-worker.js', import.meta.url),
    );
    import { createWorker } from '@myorg/worker';
    
    const helperWorker = createWorker(
      new URL('./helper-worker.js', import.meta.url),
    );

worker-loader

Warning

worker-loader 仅作为项目迁移到 Rspack 的临时便捷方案,推荐使用 new Worker() 语法。

Rspack 也支持了 worker-loader,不过由于 worker-loader 已不再维护,请使用 worker-rspack-loader 进行替换。

使用 resolveLoader 替换 worker-loader 为 worker-rspack-loader:

ESM
CJS
rspack.config.mjs
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);

export default {
  resolveLoader: {
    alias: {
      'worker-loader': require.resolve('worker-rspack-loader'),
    },
  },
};