开发者通常在最新的浏览器上进行开发,以使用最新的 JavaScript 语法,浏览器接口,让工作更有效率,代码质量更高,更不容易过时。
而用户则可能仍然在使用旧版的浏览器。比如,某些政府教育系统仍然在使用 Windows XP,用户能安装的最新的浏览器是 Chrome 49。有些用户可能只是没有升级浏览器的习惯,导致他们的 Chrome 还是 86 版本。
为了平衡两方面,开发者通常会借助一些工具,自动转译语法,并 Polyfill 新的接口,以兼容某个特定版本的旧浏览器。通过语法分析,按需插入 core-js 中的 Polyfill 代码。而 core-js 缺少的部分,可以用 polyfill-service 补全。
ES 语法转译
Optional Chaining 这种比较新的 ES 语法在老版本浏览器无法支持,运行时会报错。
const dogName = adventurer.dog?.name;
为了兼容旧版浏览器,必须在构建过程中就将其转译成 ES5/ES3 语法。
var _adventurer$dog;
const dogName = (_adventurer$dog = adventurer.dog) === null || _adventurer$dog === void 0 ? void 0 : _adventurer$dog.name;
转译功能通常由 babel,swc,esbuild 等 JavaScript 转译工具提供。
ES 特性 Polyfill
Promise,Stream 等特性需要插入特定的 Polyfill 代码。
esbuild 无法自动分析并 Polyfill 特性,只能手动导入。而 swc 和 babel 可以自动分析源码,按需自动导入。swc 和 babel 都是基于 core-js 进行 polyfill。
浏览器特性 Polyfill
core-js 只提供 ES 特性的 Polyfill(准确的说,是浏览器和 Node.js 交集的部分)。但是浏览器除了 ES 标准外,还提供其他浏览器特有的特性,比如 ResizeObserver。这就需要一个补充方案。
方案1是手动取导入 Polyfill 代码,比如在项目中 import ‘whatwg-fetch’ 就可以支持 fetch() 接口。但是这对开发者而言是一个比较大的负担。
方案2是在页面中引用 polyfill.io 的脚本,服务器会根据 UA 判断需要加载哪些 Polyfill 代码。开发者也可以用 polyfill-service 自建服务器。缺点是增加了一些服务器开销,也会阻塞应用本体脚本的加载,造成一些成本提高和性能损失。
CSS 语法转译
很多 CSS 特性在未成为正式标准之前便已经被浏览器支持了。但是在这个成为标准的过程中,CSS 特性的提案可能会修改。浏览器厂商为了防止自己目前的实现与之后的正式标准冲突,会在选择器和属性名之前,加上自己的前缀,Firefox 是 -moz-,Chrome 和 Safari 是 -webkit-,而 IE 是 -ms-。
比如下面这段代码:
:fullscreen {
}
为了兼容不同的浏览器,尤其是老浏览器,需要转译成:
:-webkit-full-screen {
}
:-ms-fullscreen {
}
:fullscreen {
}
转译过程主要通过 postcss 实现。当前流行的前端应用构建工具,比如 create-react-app, vite, parcel, 都集成了 postcss。
横向对比
babel | esbuild | swc | polyfill service | postcss | |
---|---|---|---|---|---|
ES 语法转译 | ✅ | ✅ | ✅ | ||
ES 特性 Polyfill | ✅ | ✅ | ✅ | ||
浏览器特性 Polyfill | ✅ | ||||
CSS 语法转译 | ✅ |
总结
综合来看,目前没有一种技术能够单独达成全自动的浏览器兼容性。建议使用 babel + polyfill service + postcss 之类的融合方案。
当使用一种新特性时,可以先查一下 caniuse.com 支持的浏览器版本。如果需要 Polyfill,要看自己选用的 Polyfill 方案(core-js,polyfill-service)是否支持。如果不支持,则需要手动 Polyfill 这个特性。
实践
Create React App
以 create-react-app 项目为例。首先我们需要配置项目支持的浏览器版本,让底层的 babel 和 postcss 将 JS 和 CSS 转译到目标浏览器支持的语法,并尽可能注入所需的 Polyfill 代码:
{
"browserslist": {
"production": [
"chrome > 49",
"firefox > 53",
"ie 11"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
然后,我们需要修改 HTML 模板,注入 Polyfill.io 的脚本,以支持额外的 Polyfill 特性:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://polyfill.io/v3/polyfill.min.js"></script>
</head>
<body>
...
</body>
</html>
Vite
Vite 默认只支持 Chrome 87+,这个要求对大部分实际应用可能太过苛刻。如果要支持更早的浏览器,则必须使用 legacy 插件:
import react from '@vitejs/plugin-react';
import legacy from '@vitejs/plugin-legacy';
import { defineConfig } from 'vite';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
legacy({
targets: [
"chrome > 49",
"firefox > 53",
"ie 11"
],
}),
],
});
然后,我们需要修改 HTML 模板,注入 Polyfill.io 的脚本,以支持额外的 Polyfill 特性:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://polyfill.io/v3/polyfill.min.js"></script>
</head>
<body>
...
</body>
</html>
发表回复