Browser API

@rspack/browser 是专为浏览器环境打造的 Rspack 版本,无需依赖 WebContainers 或特定平台。其 API 与 @rspack/coreJavaScript API 保持一致,并在此基础上,额外提供了适配浏览器环境的特性和接口。

欢迎前往 Rspack Playground 体验在浏览器中运行 Rspack 的效果。

WARNING

目前 @rspack/browser 仍处于实验阶段,我们将持续完善在线打包能力,后续可能会引入不兼容变更。

基本示例

以下示例展示了 @rspack/browser 的基本用法。除了需要使用额外的 API 读写项目文件和产物外,其他 API 与 @rspack/coreJavaScript API 保持一致。

import { rspack, builtinMemFs } from '@rspack/browser';

// Write files to memfs
builtinMemFs.volume.fromJSON({
  // ...project files
});

rspack({}, (err, stats) => {
  if (err || stats.hasErrors()) {
    // ...
  }
  // Get output from memfs after bundling
  const files = builtinMemFs.volume.toJSON();
});

响应头设置

@rspack/browser 内部使用了 SharedArrayBuffer 来实现多线程的共享内存,因此你需要为开发服务器或线上部署环境设置响应头。

如果你正在使用 Rspack 作为项目的打包器,可以通过 devServer.headers 设置:

rspack.config.mjs
export default {
  //...
  devServer: {
    headers: {
      'Cross-Origin-Opener-Policy': 'same-origin',
      'Cross-Origin-Embedder-Policy': 'require-corp',
    },
  },
};

如果你正在使用 Rsbuild 作为项目的打包器,可以通过 server.headers 设置:

rsbuild.config.ts
export default {
  server: {
    headers: {
      'Cross-Origin-Opener-Policy': 'same-origin',
      'Cross-Origin-Embedder-Policy': 'require-corp',
    },
  },
};

对于线上环境,请参考你的项目部署平台的相关文档。

内存文件系统

由于浏览器无法直接访问本地文件系统,@rspack/browser 提供了基于 memfs 的内存文件系统对象 builtinMemFs,用于在浏览器环境下读写文件。Node.js 和 Rust 层对文件系统的读写均会重定向到该内存文件系统,包括项目配置、源代码、node_modules 依赖及产物。

以下是基本用法示例,完整 API 可参考 memfs 文档

import { builtinMemFs } from '@rspack/browser';

// Write files to memfs
builtinMemFs.volume.fromJSON({
  // ...project files
});

// Read files from memfs
const files = builtinMemFs.volume.toJSON();

浏览器专用插件

为更好地满足浏览器环境下的打包需求,@rspack/browser 提供了若干专用插件。

BrowserHttpImportEsmPlugin

在本地开发环境中,开发者通常通过包管理器将项目依赖下载并存储在根目录的 node_modules 目录中。在使用 @rspack/browser 时,你可以将依赖项预先写入内存文件系统的 node_modules 目录。然而,当项目依赖的模块存在不确定性时(例如允许用户自由选择第三方依赖),预先写入所有依赖就变得不切实际。

import { builtinMemFs } from '@rspack/browser';

builtinMemFs.volume.fromJSON({
  '/node_modules/react/index.js': '...',
});

@rspack/browser 提供了 BrowserHttpImportEsmPlugin 插件。该插件在解析模块时,会将第三方依赖的模块名重写为 ESM CDN 的 URL。例如,import React from "react" 会被重写为 import React from "https://esm.sh/react"。结合 Rspack 的 buildHttp 功能,即可在打包时通过 HTTP 动态加载依赖。

rspack.config.mjs
import { BrowserHttpImportEsmPlugin } from '@rspack/browser';

export default {
  plugins: [new BrowserHttpImportEsmPlugin({ domain: 'https://esm.sh' })],
  experiments: {
    buildHttp: {
      allowedUris: ['https://'],
    },
  },
};

如下所示,BrowserHttpImportEsmPlugin 支持通过选项指定 ESM CDN 的域名,或者指定某些依赖的特定版本或 URL。

interface BrowserHttpImportPluginOptions {
  /**
   * ESM CDN 域名
   */
  domain: string | ((request: string, packageName: string) => string);
  /**
   * 为特定的依赖指定 URL
   */
  dependencyUrl?:
    | Record<string, string | undefined>
    | ((packageName: string) => string | undefined);
  /**
   * 为依赖指定版本。
   * 如果未指定,默认为 "latest"。
   */
  dependencyVersions?: Record<string, string | undefined>;
}

BrowserRequirePlugin

在 Rspack 中,某些场景需要动态加载和执行 JavaScript 代码,如 LoaderHtmlRspackPlugin 的模板函数。由于这些代码可能来自不可信的第三方用户,直接在浏览器环境中执行会带来潜在的安全风险。为了保障安全性,@rspack/browser 在遇到此类场景时会默认抛出错误,阻止不安全代码的执行。

WARNING

Rspack 在打包过程中不会执行项目的用户代码。为了安全起见,建议在 iframe 中运行最终的打包产物。

BrowserRequirePlugin 插件开放了此类能力:

rspack.config.mjs
import { BrowserRequirePlugin } from '@rspack/browser';

export default {
  plugins: [
    new BrowserRequirePlugin({ execute: BrowserRequirePlugin.unsafeExecute }),
  ],
};

你需要提供一个 execute 函数,用于动态执行和加载 CommonJS 模块,并修改 runtime.module.exports 来设置该模块导出的内容。@rspack/browser 提供了一个不安全的实现 BrowserRequirePlugin.unsafeExecute,其内部直接使用 new Function 执行代码。你也可以根据实际需求,基于该 API 封装更安全的实现,例如:

function safeExecute(code: string, runtime: CommonJsRuntime) {
  const safeCode = sanitizeCode(code);
  BrowserRequirePlugin.unsafeExecute(safeCode, runtime);
}

function uselessExecute(_code: string, runtime: CommonJsRuntime) {
  runtime.module.exports.hello = 'rspack';
}

BrowserRequirePlugin 的选项如下:

/**
 * 加载 CommonJS 模块的运行时上下文
 */
interface CommonJsRuntime {
  module: any;
  exports: any;
  require: BrowserRequire;
}

interface BrowserRequirePluginOptions {
  /**
   * 执行动态代码的函数
   */
  execute: (code: string, runtime: CommonJsRuntime) => void;
}
如何选择是否使用此插件?

如果你的项目无需动态加载和执行 JavaScript 代码,则无需使用此插件。

若项目不会分发不可信代码,或即使分发也不会造成安全问题,可直接使用 BrowserRequirePlugin.unsafeExecute。例如 Rspack Playground 就不涉及用户隐私或账户安全。

已知问题

  • napi-rs#2867: 目前你需要在使用了 @rspack/browser 的项目中注入 process.env.NODE_DEBUG_NATIVE。 如果你正在使用 Rspack 打包你的项目,那么可以使用 DefinePlugin

    rspack.config.mjs
    new rspack.DefinePlugin({
      'process.env.NODE_DEBUG_NATIVE': JSON.stringify(false),
    });

    如果你正在使用 Rsbuild 打包你的项目,那么可以使用 source.define

    rsbuild.config.ts
    export default {
      source: {
        define: {
          'process.env.NODE_DEBUG_NATIVE': JSON.stringify(false),
        },
      },
    };