> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.

# 浏览器兼容性

Rsbuild 默认支持 [现代浏览器](/zh/guide/advanced/browserslist.md#default-values)，同时提供语法降级和 API 降级能力，最低可以兼容到支持 ES5 的旧版浏览器（如 IE11）。

本章节介绍如何使用 Rsbuild 提供的能力来处理浏览器兼容性问题。

## 浏览器范围

在处理兼容性问题之前，首先需要明确你的项目需要支持的浏览器范围，并添加相应的 browserslist 配置。

- 如果你还没有设置浏览器范围，请先阅读 [浏览器范围](/zh/guide/advanced/browserslist.md) 章节。

- 如果你已经设置了浏览器范围，那么 Rsbuild 会自动根据该范围进行编译，对 JavaScript 语法和 CSS 语法进行降级处理，并注入所需的 polyfill 代码。大部分情况下，你可以放心地使用现代 ECMAScript 特性，无须担心兼容性问题。

在设置浏览器范围之后，如果你依然在开发中遇到了一些兼容性问题，请继续阅读下面的内容来寻找解决方案。

:::tip 什么是 polyfill
polyfill 是一种用于解决浏览器兼容问题的技术。它用于模拟某些浏览器不支持的新特性，使得这些特性能在不支持的浏览器中正常工作。例如，如果某个浏览器不支持 `Array.prototype.flat()` 方法，那么我们可以使用 polyfill 来模拟这个方法，从而让代码在这个浏览器中也能正常工作。
:::

## 背景知识

在处理兼容性问题之前，建议你了解以下背景知识，以更好地处理相关问题。

### 语法降级和 API 降级

当你在项目中使用高版本语法和 API 时，为了让编译后的代码能稳定运行在低版本浏览器中，需要完成两部分降级：语法降级和 API 降级。

**Rsbuild 通过语法转译来对语法进行降级，通过 polyfill 来对 API 进行进行降级。**

> 语法和 API 并不是强绑定的，浏览器厂商在实现引擎的时候，会根据规范或者自身需要提前支持一些语法或者提前实现一些 API。因此，同一时期的不同厂商的浏览器，对语法和 API 的兼容都不一定相同。所以在一般的实践中，语法和 API 是分成两个部分进行处理的。

### 语法转译

**语法是编程语言如何组织代码的一系列规则**，不遵守这些规则的代码无法被编程语言的引擎正确识别，因此无法被运行。在 JavaScript 中，以下几个示例都是语法规则：

- 在 `const foo = 1` 中，`const` 表示声明一个不可变的常量。
- 在 `foo?.bar?.baz` 中，`?.` 表示可选链访问属性。
- 在 `async function () {}` 中，`async` 表示声明一个异步函数。

由于不同浏览器的解析器所能支持的语法不同，尤其是旧版本浏览器引擎所能支持的语法较少，因此一些语法在低版本浏览器引擎中运行时，就会在解析 AST 的阶段报错。

比如下面这段代码在 IE 浏览器或低版本 Node.js 下会报错：

```js
const foo = {};
foo?.bar();
```

我们在低版本 Node.js 中运行这段代码，会出现以下错误信息：

```bash
SyntaxError: Unexpected token .
  at Object.exports.runInThisContext (vm.js:73:16)
  at Object.<anonymous> ([eval]-wrapper:6:22)
  at Module._compile (module.js:460:26)
  at evalScript (node.js:431:25)
  at startup (node.js:90:7)
  at node.js:814:3
```

从错误信息里可以明显看到，这是一个语法错误（SyntaxError）。这说明这个语法在低版本的引擎中是不受支持的。

**语法是不能通过 polyfill 或者 shim 进行支持的**。如果想在低版本浏览器中运行一些它原本不支持的语法，那么就需要对代码进行转译，转译成低版本引擎所能支持的语法。

将上述代码转译为以下代码即可在低版本引擎中运行：

```js
var foo = {};
foo === null || foo === void 0 ? void 0 : foo.bar();
```

转译后，代码的语法变了，把一些低版本引擎无法理解的语法用其可理解的语法替代，**但代码本身的意义没有变**。

如果引擎在转换为 AST 的时候遇到了无法识别的语法，就会报语法错误，并中止代码执行流程。在这种情况下，如果你的项目没有使用 SSR 或 SSG 等能力的话，页面将会直接白屏，导致页面不可用。

如果代码被转换为 AST 成功，引擎会将 AST 转为可执行代码，并在引擎内部正常执行。

### API polyfill

JavaScript 是解释型脚本语言，不同于 Rust 等编译型语言。Rust 会在编译阶段对代码中的调用进行检查，而 JavaScript 在真正运行到某一行代码之前，并不知道这一行代码所调用的函数是否存在，因此一些错误只有在运行时才会出现。

举个例子，下面这段代码：

```js
var str = 'Hello world!';
console.log(str.notExistedMethod());
```

上面这段代码有着正确的语法，在引擎运行时的第一个阶段也能正确转换为 AST，但是在真正运行的时候，由于 `String.prototype` 上不存在 `notExistedMethod` 这个方法，所以在实际运行的时候会报错：

```bash
Uncaught TypeError: str.notExistedMethod is not a function
  at <anonymous>:2:17
```

随着 ECMAScript 的迭代，一些内置对象也会迎来新的方法。比如 `String.prototype.replaceAll` 是在 ES2021 中被引入的，那么在大部分 2021 年前的浏览器的引擎的内置对象 `String.prototype` 中是不存在 `replaceAll` 方法的，因此下面这段代码在最新的 Chrome 里可以运行，但是在较早的版本里无法运行：

```js
'abc'.replaceAll('abc', 'xyz');
```

为了解决在旧版浏览器中的 `String.prototype` 缺少 `replaceAll` 的问题，我们可以在老版本的浏览器里扩展 `String.prototype` 对象，给它加上 `replaceAll` 方法，例如：

```js
// 该 polyfill 的实现并不一定符合标准，仅作为示例。
if (!String.prototype.replaceAll) {
  String.prototype.replaceAll = function (str, newStr) {
    // If a regex pattern
    if (
      Object.prototype.toString.call(str).toLowerCase() === '[object regexp]'
    ) {
      return this.replace(str, newStr);
    }
    // If a string
    return this.replace(new RegExp(str, 'g'), newStr);
  };
}
```

> 这种为旧环境提供实现来对齐新 API 的技术被称作 polyfill。

## 编译范围

默认情况下，Rsbuild 使用 [SWC](/zh/guide/configuration/swc.md) 编译所有 JavaScript 和 TypeScript 模块，但排除了 `node_modules` 目录下的 JavaScript 模块。

采用这种设计，是为了避免对所有第三方依赖进行降级时影响构建性能，同时防止对已预编译的第三方依赖进行重复降级而引入潜在问题。

### 源代码

当前项目的源代码会被默认降级，因此你不需要添加额外的配置，只需要保证正确设置了浏览器范围即可。

### 第三方依赖

当你发现某个第三方依赖导致了兼容性问题时，你可以将这个依赖添加到 Rsbuild 的 [source.include](/zh/config/source/include.md) 配置中，使 Rsbuild 对该依赖进行额外的编译。

以 `query-string` 这个 npm 包为例，你可以做如下的配置：

```ts title="rsbuild.config.ts"
import path from 'node:path';

export default {
  source: {
    include: [/node_modules[\\/]query-string[\\/]/],
  },
};
```

查看 [source.include](/zh/config/source/include.md) 文档来了解更详细的用法。

## Polyfills

Rsbuild 通过 SWC 编译 JavaScript 代码，并支持注入 [core-js](https://github.com/zloirock/core-js)、[@swc/helpers](https://npmjs.com/package/@swc/helpers) 等 polyfills。

在不同的使用场景下，你可能会需要不同的 polyfill 方案。Rsbuild 提供了 [output.polyfill](/zh/config/output/polyfill.md) 配置项来切换不同的 polyfill 方案。

### 默认行为

Rsbuild 默认不注入任何 polyfill：

```ts
export default {
  output: {
    polyfill: 'off',
  },
};
```

### usage 方案 \{#usage-mode}

当你开启 usage 方案时，Rsbuild 会分析项目中的源代码，并判断需要注入哪些 polyfill。

比如代码中使用了 `Map`:

```js
var b = new Map();
```

编译后，只会在该文件中注入 `Map` 所需的 polyfill：

```js
import 'core-js/modules/es.map';
var b = new Map();
```

这种方式的优点是注入的 polyfill 体积更小，适合对包体积有较高要求的项目使用。缺点是 polyfill 可能注入不全，因为第三方依赖默认不会被编译和降级处理，因此第三方依赖所需的 polyfill 不会被分析到，如果需要分析某个第三方依赖，也需要将其加入到 [source.include](/zh/config/source/include.md) 配置中。

usage 方案对应的配置为：

```ts
export default {
  output: {
    polyfill: 'usage',
  },
};
```

### entry 方案 \{#entry-mode}

在使用 entry 方案时，Rsbuild 会根据当前项目设置的浏览器范围来计算需要注入哪些 `core-js` 方法，并在每个页面的入口文件中进行注入。这种方式注入的 polyfill 较为全面，不需要再担心项目源码和第三方依赖的 polyfill 问题，但是因为包含了一些没有用到的 polyfill 代码，所以最终的包大小可能会有所增加。

entry 方案对应的配置为：

```ts
export default {
  output: {
    polyfill: 'entry',
  },
};
```

### UA polyfill

Cloudflare 提供了一个 [polyfill 服务](https://cdnjs.cloudflare.com/polyfill/)，可以根据用户浏览器的 User-Agent 自动生成 polyfill 文件。

你可以通过 Rsbuild 的 [html.tags](/zh/config/html/tags.md) 配置来注入脚本，例如在 `<head>` 标签的开头插入 `<script>` 标签：

```ts
export default {
  html: {
    tags: [
      {
        tag: 'script',
        attrs: {
          defer: true,
          src: 'https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=default',
        },
        append: false,
      },
    ],
  },
};
```

### 缺少的 polyfill

需要注意的是，core-js 并不能提供所有 JavaScript API 的 polyfill。有一些 API 由于其底层实现的复杂性或者性能考虑，是无法通过 polyfill 来完全模拟的。

最典型的例子是 `Proxy` 对象。由于 `Proxy` 需要引擎级别的支持来实现对象操作的拦截，无法通过纯 JavaScript 代码来完全模拟其行为，因此 core-js 并不提供 `Proxy` 的 polyfill。

请查看 [core-js - Missing polyfills](https://github.com/zloirock/core-js#missing-polyfills) 来了解 core-js 无法提供哪些 API 的 polyfill。
