工具篇
提到工程化(构建)工具,作为经验丰富的前端开发者,相信你能列举出不同时代的代表:从 Browserify + Gulp 到 Parcel,从 Webpack 到 Rollup,甚至 @尤雨溪编写的 Vite,相信你也并不陌生。没错,前端发展到现在,工程化工具琳琅满目。但很多工具的实现和设计非常复杂,甚至出现了「面向 webpack 编程」的调侃。ToolingReport 是由 Chrome core team 核心成员以及业内著名开发者打造的构建工具比对平台。这个平台对比了 Webpack v4、Rollup v2、Parcel v2、Browserify + Gulp 在不同维度下的表现,如下图所示: 测评通过的 test 得分只是一个方面,实际情况也和不同构建工具的设计目标有关。比如,Webpack 的构建主要依赖了插件和 loader,因此它的能力虽然强大,但配置信息较为烦琐。而 Parcel 的设计目标之一就是零配置,开箱即用,但是在功能的集成上相对有限。但从工程化的角度出发,我们还是从上面的分数分析,来看看这些分数评测的维度。这些分数来自以下 6 个维度的评测:- Code Splitting
- Hashing
- Importing Modules
- Non-JavaScript Resources
- Output Module Formats
- Transformations
线上问题篇
这一部分,让我们以一篇文章《报告老板,我们的 H5 页面在 iOS 11 系统上白屏了!》分析,我先简单梳理和总结一下文章表达的内容,读者看我总结即可:- 笔者发现某些机型上出现页面白屏情况;
- 出现在报错页面上的信息非常明显,即当前浏览器不支持 ... 扩展运算符;
- 出错的代码(使用了扩展运算符的代码)属于某个公共库代码,它没有使用 Babel 插件进行降级处理,因此线上源代码出现了 ... 扩展运算符。
'module-name/library-name' // 出现问题的那个库
],vue-cli 对 transpileDependencies 也有如下说明:
默认情况下 babel-loader 会忽略所有 node_modules 中的文件。如果你想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来。按照上述操作,却得到了新的报错:Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'。究其原因,module-name/library-name 这个库对外输出的是 CommonJS 类型源码,我们对该库进行编译后,项目基础设施中会通过 babel-transform-runtime 在编译时增加 helper 代码,而这些 helper 使用的是 import 引入。最终编译结果出现了 ESM 包含 CommonJS 的情况,是不会被 Webpack 处理的。我再次分析下出现的新的问题:
- plugin-transform-runtime 会根据 sourceType 选择注入 import 或者 require,sourceType 的默认值是 module,就会默认注入 import;
- Webpack 不会处理包含 import/export 的文件中的 module.exports 导出,所以需要让 Babel 自动判断 sourceType,根据文件内是否存在 import/export 来决定注入什么样的代码。
... // 省略的配置
sourceType: 'unambiguous',
... // 省略的配置
} 但是这种做法在工程上并不推荐,上述更改方式对所有编译文件都生效,但也增加了编译成本(因为设置 sourceType:unambiguous 后,编译时需要做的事情更多),还有个潜在问题:
Unambiguous can be quite useful in contexts where the type is unknown, but it can lead to false matches because it's perfectly valid to have a module file that does not use import/export statements.翻译过来,就是说并不是所有的 ESM 模块(这里指使用 ESNext 特性的文件)都含有 import/export,因此即便某个待编译文件属于 ESM 模块,也可能被 Babel 错误地判断为 CommonJS 模块而引发误判(微信搜索公众号 逆锋起笔,关注后回复 编程资源,领取各种经典学习资料)。**基于这一点,一个更合适的做法是:**只对目标第三方库 'module-name/library-name' 使用 sourceType:unambiguous,这时 Babel overrides 属性就派上用场了:
Allows users to provide an array of options that will be merged into the current configuration one at a time. This feature is best used alongside the "test"/"include"/"exclude" options to provide conditions for which an override should apply.具体使用方式:module.exports = {
... // 省略的配置
overrides: [
{ include: './node_modules/module-name/library-name/name.common.js', // 使用的第三方库
sourceType: 'unambiguous'
}
],
... // 省略的配置
};至此,这个“iOS 11 系统白屏”问题就算告一段落了(你有没有被各种配置和设计搞得云里雾里?)。我整理了解决路线,如下图所示: 我们回过头再来看这个问题,问题其实出现在一个公共库上,因而前端生态的混乱和复杂也许是更本质的原因,但这都转嫁为前端工程化的难点。我们进一步思考:
- 作为公共库,我应该如何构建编译代码,让业务方更有保障地使用?
- 作为使用者,我应该如何处理第三方公共库,是否还需要对其进行额外编译和处理?