Rsbuild 默认支持 现代浏览器,同时提供语法降级和 API 降级能力,最低可以兼容到支持 ES5 的旧版浏览器(如 IE11)。
本章节介绍如何使用 Rsbuild 提供的能力来处理浏览器兼容性问题。
在处理兼容性问题之前,首先需要明确你的项目需要支持的浏览器范围,并添加相应的 browserslist 配置。
如果你还没有设置浏览器范围,请先阅读 设置浏览器范围 章节。
如果你已经设置了浏览器范围,那么 Rsbuild 会自动根据该范围进行编译,对 JavaScript 语法和 CSS 语法进行降级处理,并注入所需的 polyfill 代码。大部分情况下,你可以放心地使用现代 ECMAScript 特性,无须担心兼容性问题。
在设置浏览器范围之后,如果你依然在开发中遇到了一些兼容性问题,请继续阅读下面的内容来寻找解决方案。
polyfill 是一种用于解决浏览器兼容问题的技术。它用于模拟某些浏览器不支持的新特性,使得这些特性能在不支持的浏览器中正常工作。例如,如果某个浏览器不支持 Array.prototype.flat()
方法,那么我们可以使用 polyfill 来模拟这个方法,从而让代码在这个浏览器中也能正常工作。
在处理兼容性问题之前,建议你了解以下背景知识,以更好地处理相关问题。
当你在项目中使用高版本语法和 API 时,为了让编译后的代码能稳定运行在低版本浏览器中,需要完成两部分降级:语法降级和 API 降级。
Rsbuild 通过语法转译来对语法进行降级,通过 polyfill 来对 API 进行进行降级。
语法和 API 并不是强绑定的,浏览器厂商在实现引擎的时候,会根据规范或者自身需要提前支持一些语法或者提前实现一些 API。因此,同一时期的不同厂商的浏览器,对语法和 API 的兼容都不一定相同。所以在一般的实践中,语法和 API 是分成两个部分进行处理的。
语法是编程语言如何组织代码的一系列规则,不遵守这些规则的代码无法被编程语言的引擎正确识别,因此无法被运行。在 JavaScript 中,以下几个示例都是语法规则:
const foo = 1
中,const
表示声明一个不可变的常量。foo?.bar?.baz
中,?.
表示可选链访问属性。async function () {}
中,async
表示声明一个异步函数。由于不同浏览器的解析器所能支持的语法不同,尤其是旧版本浏览器引擎所能支持的语法较少,因此一些语法在低版本浏览器引擎中运行时,就会在解析 AST 的阶段报错。
比如下面这段代码在 IE 浏览器或低版本 Node.js 下会报错:
我们在低版本 Node.js 中运行这段代码,会出现以下错误信息:
从错误信息里可以明显看到,这是一个语法错误(SyntaxError)。这说明这个语法在低版本的引擎中是不受支持的。
语法是不能通过 polyfill 或者 shim 进行支持的。如果想在低版本浏览器中运行一些它原本不支持的语法,那么就需要对代码进行转译,转译成低版本引擎所能支持的语法。
将上述代码转译为以下代码即可在低版本引擎中运行:
转译后,代码的语法变了,把一些低版本引擎无法理解的语法用其可理解的语法替代,但代码本身的意义没有变。
如果引擎在转换为 AST 的时候遇到了无法识别的语法,就会报语法错误,并中止代码执行流程。在这种情况下,如果你的项目没有使用 SSR 或 SSG 等能力的话,页面将会直接白屏,导致页面不可用。
如果代码被转换为 AST 成功,引擎会将 AST 转为可执行代码,并在引擎内部正常执行。
JavaScript 是解释型脚本语言,不同于 Rust 等编译型语言。Rust 会在编译阶段对代码中的调用进行检查,而 JavaScript 在真正运行到某一行代码之前,并不知道这一行代码所调用的函数是否存在,因此一些错误只有在运行时才会出现。
举个例子,下面这段代码:
上面这段代码有着正确的语法,在引擎运行时的第一个阶段也能正确转换为 AST,但是在真正运行的时候,由于 String.prototype
上不存在 notExistedMethod
这个方法,所以在实际运行的时候会报错:
随着 ECMAScript 的迭代,一些内置对象也会迎来新的方法。比如 String.prototype.replaceAll
是在 ES2021 中被引入的,那么在大部分 2021 年前的浏览器的引擎的内置对象 String.prototype
中是不存在 replaceAll
方法的,因此下面这段代码在最新的 Chrome 里可以运行,但是在较早的版本里无法运行:
为了解决在旧版浏览器中的 String.prototype
缺少 replaceAll
的问题,我们可以在老版本的浏览器里扩展 String.prototype
对象,给它加上 replaceAll
方法,例如:
这种为旧环境提供实现来对齐新 API 的技术被称作 polyfill。
在 Rsbuild 中,我们将代码分为三类:
默认情况下,Rsbuild 只会对第一类代码进行编译和降级,而其他类型的代码默认是不进行降级处理的。
之所以这样处理,主要有几个考虑:
当前项目的代码会被默认降级,因此你不需要添加额外的配置,只需要保证正确设置了浏览器范围即可。
当你发现某个第三方依赖的代码导致了兼容性问题时,你可以将这个依赖添加到 Rsbuild 的 source.include 配置中,使 Rsbuild 对该依赖进行额外的编译。
以 query-string
这个 npm 包为例,你可以做如下的配置:
请查看 source.include 文档来查看更详细的用法说明。
当你引用非当前项目的代码时,如果该代码未经过编译处理,那么你也需要配置 source.include 来对它进行编译。
比如,你需要引用 monorepo 中 packages
目录下的某个模块,可以添加如下的配置:
Rsbuild 通过 SWC 编译 JavaScript 代码,并支持注入 core-js、@swc/helpers 等 polyfills。
在不同的使用场景下,你可能会需要不同的 polyfill 方案。Rsbuild 提供了 output.polyfill 配置项来切换不同的 polyfill 方案。
Rsbuild 默认不注入任何 polyfill:
当你开启 usage 方案时,Rsbuild 会分析项目中的源代码,并判断需要注入哪些 polyfill。
比如代码中使用了 Map
:
编译后,只会在该文件中注入 Map
所需的 polyfill:
这种方式的优点是注入的 polyfill 体积更小,适合对包体积有较高要求的项目使用。缺点是 polyfill 可能注入不全,因为第三方依赖默认不会被编译和降级处理,因此第三方依赖所需的 polyfill 不会被分析到,如果需要分析某个第三方依赖,也需要将其加入到 source.include 配置中。
usage 方案对应的配置为:
在使用 entry 方案时,Rsbuild 会根据当前项目设置的浏览器范围来计算需要注入哪些 core-js
方法,并在每个页面的入口文件中进行注入。这种方式注入的 polyfill 较为全面,不需要再担心项目源码和第三方依赖的 polyfill 问题,但是因为包含了一些没有用到的 polyfill 代码,所以最终的包大小可能会有所增加。
entry 方案对应的配置为:
Cloudflare 提供了一个 polyfill 服务,可以根据用户浏览器的 User-Agent 自动生成 polyfill 文件。
你可以通过 Rsbuild 的 html.tags 配置来注入脚本,例如在 <head>
标签的开头插入 <script>
标签: