浏览器兼容性与 Polyfill 技术

开发者通常在最新的浏览器上进行开发,以使用最新的 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。

横向对比

babelesbuildswcpolyfill servicepostcss
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>

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据