公司希望我提升产品在移动端的体验,于是我就打开了 Lighthouse,然后看了眼代码,发现有几个问题:
- 移动端和 pc 端一起编译,共用一套编译配置
- 目标平台包括 IE11
- 还在使用 babel@6
- 全量引用 babel-polyfill
- CDN 没有完全 http2
最后一项联系运维同学解决就好了,我开始尝试解决其它几项。
0. 目标
- 移动端和 pc 端采用不同的编译配置
- 尽量用最新的工具链
- 兼容 IE11
1. 尝试升级到 babel@7
babel@6 停留在 4 年前,存在着各种各样的问题,包括本身的实现和兼容代码都有问题。我希望先升级到 babel@7,以便使用 useBuiltIns: 'usage'
,减少打包后的代码量。
实际结果很不理想。升级后代码膨胀了很多,经过研究,发现原因是 core-js 策略比较保守,所以多引用了很多兼容性代码,远远多于 babel@6。
比较详细的分析可见:babel@6 升级到 babel@7,兼容性代码膨胀的原因。
2. 尝试从 webpack 迁移到 esbuild
接下来尝试离开 webpack + babel 体系,使用 esbuild。先打包一套 es6 代码,供移动端和现代化浏览器使用,然后再生成一套兼容性代码,给 IE11 使用。
相比于 webpack + babel,esbuild 的优势是统一+快。按照官网统计,它的速度可能是 webpack 体系的 100 倍。它的问题是只支持 ES6,也不支持扩展,不像 babel 那样,加个插件就能什么标准都支持。
这次尝试我也失败了。原因倒不是因为 IE11,而是原来的架构跟 webpack 深度耦合,比如:lazy-loading、分割模块、i18n,等。所以迁移成本非常高,发现情况不对之后只好止损。
3. 使用 esbuild-loader 替换 babel-loader
webpack 暂时不能放弃,那就用 esbuild-loader 吧,毕竟要快很多。于是就遭遇到上面说的规范问题。
- esbuild 只支持标准规范,原本可以通过 babel plugins 支持的特性现在都不支持了,需要改回去
- 一些写的不规范的代码,比如变量先使用后定义,在
var
能跑,但是在let
,const
阶段就跑不了,也得改回去
经过一段时间的折腾,这个尝试还是比较成功。构建时间缩短了一半以上。
4. 尝试用 babel-standalone 编译 IE11 代码
上面一步只算完成一半,因为还不支持 IE11。接下来我们计划使用 babel-standalone 实时转译代码。
babel-standalone 提供在 JS 运行时里实时编译代码的功能,比如做一个在线编辑器,或者应用里内嵌了 V8 等运行时,希望提升兼容性,就可以用这个工具。
这个尝试失败了,因为 IE11 的 JS 运行时效率太差,我们生产级别的 JS 直接就卡死了,且优化不能。
5. 尝试用 SWC 在编译时生成 IE11 代码
SWC 跟 esbuild 比较类似,是另一套新生态尝试,基于 Rust 开发,也能提供极高的编译效率,且支持 ES3。
我希望能把需要在 IE11 运行的几个工具组件单独编译一套,然后根据浏览器入口加载不同的 JS。在开发阶段,则通过 swc-loader 提供兼容性代码。
重构工具链的过程也挺费时,现有构建工具链实在是反人类。不过最终还是完成了,真正使得这次尝试又失败的原因是 SWC 本身的 bug。几个无法 work around 的 bug 导致我只能放弃这个方案。
详见:初试 SWC(Speedy Web Compiler)
不过,SWC 最新版本已经修好了我提交的 bug,大家有机会试试吧。
6. 用 babel 在编译时生成 IE11 代码
最后回到 babel+babel-loader。为了体积考虑,我决定继续使用 babel@6。
然后我发现,目前的代码架构实在是,哎……相信看过 Git 操作特定分支的小技巧 一文的同学都明白我在痛苦什么。
所以又跟项目结构搏斗许久,终于基本完成了这次重构。
7. 结果
这次重构达成了几个目的:
- 移除了全量 babel-polyfill,只有 IE11 继续加载
- 大部分模块用 ES5 构建,但是不加载 polyfill
- 少数模块,不需要兼容 IE11 的使用 ES6 构建
具体数字没机会统计,就不列了。
8. 总结
维持老代码的兼容性很难;新工具让人兴奋的同时,成熟度和完成度很难跟老工具相比;小项目对工具的要求跟大项目也有很大区别。
很多时候,选工具真的是一眼千年。
欢迎吐槽,共同进步