前言

TimeTable 的开发已经快一年了,一年前的这个时候我还在学 Java,也算是走上了不归路。 随着开发的深入,越来越多的问题浮现了出来,有架构层面的,有用户层面的,而我们能做到也只有去不断去解决。

怎么了?

简单来说,我们的一个新功能在大部分浏览器都能正常使用,但唯独到了校园 APP 里就寄了。众所周知,我校 APP 使用 WebView 技术,而 WebView 的版本和系统相关,所以问题整体方向还是挺好锁定的,一定是新语法和旧版本浏览器之间的兼容问题。

很快啊,我们从 Nginx 的 Log 中抓出来这么一条:

[09/Dec/2024:12:16:37 +0800] "POST /api/courseSchedule/listWeekEvents HTTP/1.0" 200 5168 "Mozilla/5.0 (Linux; Android 12; NOH-AN01 Build/HUAWEINOH-AN01; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.72 MQQBrowser/6.2 TBS/046291 Mobile Safari/537.36"

重点是这里的Chrome/89.0.4389.72,其实看到这里大概就知道是怎么个事了,前些时候咱们一位同学因为 Chrome 版本在 7 开头,一度导致页面直接白屏。结合上边的整体兼容性方向判断,必然是我使用的某个新方法导致的,恰好这个页面的代码量较小,一下子就锁定到了针对数组的at方法身上,通过 MDN 的文档查看兼容性,配合 UA 信息即可定位问题所在,不过这里我们还是走一遍流程,万一下次没法瞪眼法呢?

这里提供一个查看浏览器兼容性的小工具:Can I use… Support tables for HTML5, CSS3, etc

怎么定位?

由于我们的应用只是校园 APP 中的一个小应用,而校园 APP 采用 WebView 技术,连调试都成了难题。Android 的机器没法信任根证书也就没法抓包,移动端应用更没有开发者工具,怎么搞?

好在腾讯有个 vConsole 的小玩意可以通过在页面上挂载一个小组件用于模拟开发者工具,用以辅助移动端 Web 应用调试,挂上 vConsole,果然问题出在新的语法:array.at is not a function

根据 MDN 的文档,Array.prototype.at()在 Chrome92 才开始支持,方法本身也是ES2022所提出的,这里有一份原始提案:https://github.com/tc39/proposal-relative-indexing-method,我们 Chrome89 自然是支持不了。

问题找到了,如何解决呢?

兼容性

在 Vite 的构建生产版本中提到可以指定构建目标:

Vite构建目标

而构建目标的默认值之一是chrome87,那么理论上我使用 Chrome89 不应该有问题啊,最后我们会回答这个问题。

实际上,对于兼容性支持,有两种不同的概念:

Vite构建生产版本

这里的重点是请注意,默认情况下 Vite 只处理语法转译,且不包含任何 polyfill,什么是语法转译,什么又是 Polyfill?

无论是语法转译还是 Polyfill,他们都是在老旧浏览器上使用规范的一种兼容方法。这里的规范指的是 ECMA 所规定的标准,标准制定后由 JS 引擎实现。

  • 针对如上文提到的Array.prototype.at()等新方法,需要通过引入对应的 Polyfill 来实现支持。

  • 而针对如箭头函数let/const等传统浏览器不支持的 JavaScript 语法,语法转移将这些语句转换为等效的传统语法。

总的来说,Polyfill 更多的是一种模拟的方式,通过引入工具包,让浏览器可以支持这些新的功能。而语法转译则是将无法通过模拟实现的功能使用较老的语法等效实现,一般来说两种方式是需要结合一起使用的。

Polyfill

Polyfill 一般来说有这两种实现方式:

  • Runtime Polyfill:这是 Vite 官方推荐的使用方式,这个网站会根据用户 UA 自动生成对应的 Polyfill 包。优势是可以根据用户浏览器的版本尽可能减少代码体积,缺点是在业务代码运行前需要先完成 Polyfill 包的加载,由于其部署在 CouldFlare,而 CloudFlare 在大陆的访问算不上优秀,理论上会导致加载时间有一定延长,不过这个问题可以通过私有部署来解决。

  • core-js:主流的 Polyfill 库,语法转换插件 Babel 也对其进行了封装。与动态加载 Polyfill 不同,这种方式在构建时就将 Polyfill 引入构建产物。只是需要手动指定一个最低支持版本。显然,指定版本越低,需要引入的内容越多,加载开销也就越大,但胜在加载速度与业务代码相同。

上述两种方式没有好坏之分,需要根据应用场景自行选择。

Vite 怎么做?

在解释 Vite 怎么做前,需要先了解现代浏览器和传统浏览器的区别。

根据 Vite 官方文档的说明,现代浏览器应当支持现代 JavaScript 语法,即:支持原生 ESM script 标签支持原生 ESM 动态导入import.meta 的浏览器。对于 Chrome 来说,这个版本应该是 Chrome64,其他浏览器可以在上述链接中查看比较。

为了方便,在这里我们简单将剩下的浏览器定义为传统浏览器,当然这是不准确的,我们用 IE11 等老掉牙的浏览器来举例子。

根据 Vite 文档的说明,Vite 根据 Build Target 进行代码转译,也明确表示不会为构建产物添加 Polyfill。一开始我并没有搞清楚这两个操作的区别,单纯地以为指定了构建目标就应当在目标浏览器上能够运行,实际上并非如此,这里我们列举两个常见的情况:

  • 版本较老的现代浏览器(如上述 Chrome89)不支持一些较新的方法(如Array.prototype.at()

可以通过引入 Polyfill 包来解决,如果计划使用core-js方式,同样也可以使用@vitejs/plugin-legacy,通过填写modernTargets,插件会自动引入需要的 Polyfill 包,如果确定只需要指定的 Polyfill,可以单独填写modernPolyfills来减小构建体积。

  • 传统浏览器不支持上述现代 JavaScript 特性

可以通过@vitejs/plugin-legacy插件进行支持,通过在targets配置最低支持浏览器版本,在构建时会单独生成一份供传统浏览器使用的 js 文件,一般命名形如index-legacy.js,通过一些特殊的标签,现代浏览器和传统浏览器会分别使用属于自己的 JS 文件。

我怎么做?

通过一波猛如虎分析,我们终于可以回答上面的问题了,虽然 Vite 的构建目标是Chrome 87,但由于 Vite 只处理语法转译,这里我们属于缺失 Polyfill 包的情况,我们需要引入针对Array.prototype.at()的 Polyfill 包就可以了,我的配置如下:

legacy({
    modernPolyfills: ['es.array.at'],
}),

收工,下班!

海量的参考资料