likes
comments
collection
share

主流前端代码构建工具评测

作者站长头像
站长
· 阅读数 12

序言

现代化前端架构离不开构建工具的加持。构建工具的选择、理解和应用决定了是否能够打造一个流畅且接近完美的开发体验,本篇尝试通过“横向比对构建工具”来了解构建工具背后的架构理念。

一、构建工具解决了哪些问题?

首先我们对"构建"这个词重点解读一下:构建,本意为构造建立,那么在工程上来说就是流程化、标准化,最终指向较高的自动化,减少中间环节的繁琐,提高效率。构建工具主要为我们解决以下问题:

  • 代码的兼容性(js版本)
  • 抹平框架之间的差异性,生成统一可以在浏览器跑的代码(vue,react,ng)
  • CSS前缀补全/预处理器,JS压缩混淆,图片压缩
  • 前端部署(如+ hash,静态文件路径问题)

主流前端代码构建工具评测

二、有哪些主流的构建工具?

提到构建工具,作为经验丰富的前端开发者,相信你能列举出不同时代的代表:从 Browserify + GulpParcel,从WebpackRollup,甚至近来比较火的面向nobundleSnowpackVite,相信你也并不陌生。关于构建工具的详细使用有很多文章可参考,本篇就不再展开介绍,下面罗列了一些常用的构建工具和文档连接:

  • browserify, Browserify 可以让你使用类似于 node 的 require() 的方式来组织浏览器端的 Javascript 代码,通过预编译让前端 Javascript 可以直接使用 Node NPM 安装的一些库。

  • Grunt,Grunt是一个JavaScript任务运行器,本身就使用JavaScript开发,灵活的管理任务间的依赖和 执行定义的任务。

  • Gulp,gulp是基于流的自动化构建工具,除了管理和执行任务,还支持监听读写文件。

  • yeoman,Yeoman是一个强健的工具,库,及工作流程的组合,帮你网页开发者快速创建出漂亮而且引人入胜的网页程序。

  • YUI Compressor, YUI压缩器是一种JavaScript和CSS压缩器,除了删除注释和空格外,还使用尽可能小的变量名来混淆局部变量。

  • fis3, 集成了web开发中常用的构建功能,如资源定位,文件编译,压缩,雪碧图等。

  • rollup, 专注于ES6模块化,可将一小段代码编译成更大或更复杂的内容,例如库或应用程序。

  • webpack, 一切皆模块,支持模块打包及丰富的插件扩展功能。

  • parcel, 极速零配置Web应用打包工具,新兴地打包工具。

  • snowpack,利用ESM的高性能打包工具,nobundle构建工具的代表。

  • vite,受snowpack启发开发的一款高性能nobundle构建工具,官网称“下一代前端开发工具”。

  • esbuild是一个「JavaScript」Bundler 打包和压缩工具,它可以将「JavaScript」和「TypeScript」代码打包分发在网页上运行。值得一提的是snowpack和vite在底层也是使用了esbuild进行js、ts文件的转化。

此处分享一张2020受欢迎的构建工具统计图,感兴趣的可以查阅2020 JavaScript Rising Stars

主流前端代码构建工具评测

三、有什么可以借助的分析工具?

主流前端代码构建工具评测 Tooling.Report 是由 Chrome core team 核心成员以及业内著名开发者打造的构建工具比对平台,其对应 GitHub 地址为:GoogleChromeLabs tooling.report

借助该平台我们能看到Webpack v5Rollup v2Parcel v2Browserify在不通维度下的表现,如下图所示:

主流前端代码构建工具评测

从评测数据我们能清楚的看到:webpack 得分最高,其次是 Rollup,Parcel 和 Browserify 得分相近。

比较有意思的是,这里有份2020年7月评测报告,当时使用的还是webpack v4,结果如下:

主流前端代码构建工具评测

可以看到webpack5确实从性能上提升了许多,一跃成为榜首,想了解发生了甚么事的同学可以参阅 webpack5新特性一览

言归正传,虽然上述评测结果具有一定的参考价值,但测评通过的 test 得分只是一个方面,实际情况也和不同构建工具的设计目标有关。

比如,Webpack 的构建主要依赖了插件loader,因此它的能力虽然强大,但配置信息较为烦琐。而 Parcel 的设计目标之一就是零配置,开箱即用,但是在功能的集成上相对有限。

从横向发展来看,各大构建工具之间也在互相借鉴发展。比如,以 Webpack 为首的工具中,历史上编译构建速度较慢,即便监听文件启动增量构建,也无法解决初始时构建时间过长的问题。而 Parcel 主要内置了多核并行构建,利用多线程实现编译能力,在初始构建阶段就能获得较理想的构建速度。同时 Parcel 还内置了文件系统缓存,可以保存每个文件的编译结果。这一方面 Webpack 新版本(v5)也都有相应跟进。

因此,在构建工具的横向对比上,功能是否强大是一方面,而构建效率也将会是开发者考虑的核心指标。

那么对于构建工具来说,在一个现代化的项目中,哪些功能是“必备”的呢?

四、有哪些需要考量的核心指标?

我们还是从上面的分数出发,分析具体的测试维度。

这些分数来自以下 6 个维度的评测:

  • Code Splitting

  • Hashing

  • Importing Modules

  • Non-JavaScript Resources

  • Output Module Formats

  • Transformations

主流前端代码构建工具评测

从上面的结果统计表可以清晰的看到:

  • Code Splitting 方面,Rollup 表现最好,这是 Rollup 现代化的一个重要体现,而 Browserify 表层最差;

  • Hashing、Importing Modules 以及Transformation方面,各大构建工具表现相对趋近;

  • Output Module Formats上,除了 Browserify,其他工具表现相对一致。

你可能比较困惑,为何官方要从这6个维度进行评测?实际上,这个问题反馈的一个技术信息是:一个现代化构建工具或构建方案,需要重点考量/实现哪些环节?

下面我们逐一进行分析:

注:下文中涉及的框架统计结果图,主体上从做到右依次是:Browserify、Parcel、Rollup、Webpack

1.Code Splitting

Code Splitting,即代码分割。这意味着在构建打包时,能够导出公共模块,避免重复打包,以及在页面加载运行时,实现最合理的按需加载策略。

实际上,Code Splitting 是一个很大的话题。比如:

  • 不同模块间的代码分割机制能否支持不同的上下文环境(Web worker 环境等特殊上下文情况)
  • 如何实现对 Dynamic Import 语法特性的支持
  • 应用配置多入口/单入口时是否支持重复模块的抽取并打包
  • 代码模块间是否支持 Living Bindings(如果被依赖的 module 中的值发生了变化,则会映射到所有依赖该值的模块中)。

Code Splitting 是现代化构建工具的标配,因为它直接决定了前端的静态资源产出情况,影响着项目应用的性能表现,对此部分想深入了解的同学可以移步Webpack 大法之 Code Splitting

主流前端代码构建工具评测

图注 : 框架评测数据 -- Code Spltiting:

2.Hashing

Hashing,即对打包资源进行版本信息映射。这个话题背后的重要技术点是最大化地利用缓存机制。我们知道有效的缓存策略将直接影响页面加载表现,决定用户体验。那么对于构建工具来说,为了实现更合理的 hash 机制,构建工具就需要分析各种打包资源,导出模块间依赖关系,依据依赖关系上下文决定产出包的哈希值。因为一个资源的变动,将会引起其依赖下游的关联资源变动,因此构建工具进行打包的前提就是对各个模块依赖关系进行分析,并根据依赖关系,支持开发者自行定义哈希策略(比如,Webpack 提供的不同类型 hash 的区别:hash/chunkhash/contenthash)。

这就涉及一个知识点:如何区分 Webpack 中的 hash/chunkhash/contenthash?

  • hash 反映了项目的构建版本,因此同一次构建过程中生成的 hash 都是一样的。换句话说,如果项目里某个模块发生更改,触发项目的重新构建,那么文件的 hash 值将会相应地改变。如果使用 hash 策略,存在一个问题:即使某个模块的内容压根没有改变,但是重新构建后会产生一个新的 hash 值,使得缓存命中率较低。

  • 针对以上问题,chunkhash 和 contenthash 就不一样了,chunkhash 会根据入口文件(Entry)进行依赖解析

  • contenthash 则会根据文件具体内容,生成 hash 值。

我们来具体分析下,假设我们的应用项目中做到了把公共库和业务项目入口文件区分开单独进行打包,采用 chunkhash 策略,如果改动了业务项目入口文件,就不会引起公共库的 hash 值改变。对应以下示例:

entry:{
    main: path.join(__dirname,'./main.js'),
    vendor: ['react']
},
output:{
    path:path.join(__dirname,'./build'),
    publicPath: '/build/',
    filname: 'bundle.[chunkhash].js'
}

我们再看一个例子,在 index.js 中出现了对 index.css 的引用:

// index.js
require('./index.css')

此时因为 index.js 和 index.css 具有依赖关系,所以共用相同的 chunkhash 值。如果 index.js 内容发生变化,index.css 即使没有改动,在使用 chunkhash 策略时,被单独拆分的 index.css 的 hash 值也发生了变化。如果想让 index.css 完全根据文件内容来确定 hash 值,就可以使用 contenthash 策略了。

主流前端代码构建工具评测

图注 : 框架评测数据 -- Hashing

3.Importing Modules

Importing Modules,即依赖机制。当然它对于一个构建流程或工具来说非常重要,因为历史和设计原因,前端开发者一般要面对包括 ESM、CommonJS 等不同模块化方案。而一个构建工具的设计当然也就要兼容不同类型的 modules importing 方案。除此之外,由于 Node.js 的 npm 机制设计,构建工具也要支持对从 node_modules 引入公共包的支持。

主流前端代码构建工具评测

图注 :框架评测数据 -- Importing Modules

4.Non-JavaScript Resources

Non-JavaScript Resources,是指对其他非 JavaScript 类型资源导入的支持能力。这里的 Non-JavaScript Resources 可以是 HTML 文档、CSS 样式资源、JSON 资源、富媒体资源等。这些资源也是构成一个应用的关键内容,构建流程/工具当然要进行理解和支持。

主流前端代码构建工具评测

图注 :框架评测数据 -- Non-JavaScript Resourcess

5.Output Module Formats

Output Module Formats 对应上面的 Importing Modules 话题。构建输出内容的模块化方式也需要更加灵活,比如开发者可配置 ESM、CommonJS 等规范的构建内容导出。

主流前端代码构建工具评测

图注 :框架评测数据 -- Output Module Formats

6.Transformations

  1. Transformations,现代化前端开发离不开编译/转义过程。比如对 JavaScript 代码的压缩、对无用代码的删除(DCE)等。这里需要注意的是,我们在设计构建工具时,对于类似 JSX 的编译、.vue 文件的编译,不会内置到构建工具当中,而是利用 Babel 等社区能力,“无缝融合”到构建流程里。构建工具只做构建分内的事情,其他扩展能力通过插件化机制来完成,显然是一个合理而必要的设计。

主流前端代码构建工具评测

图注 :框架评测数据 -- Transformations

结语

本文简单介绍了主流的打包工具,并借助Tooling.Report从6个维度对部分工具进行了横向比对。其实对比只是一方面,更重要的是我们需要通过对比结果,去了解各构建工具需要做哪些事情?基础建设和工程化要考虑哪些事情?搞清楚这些信息,我们就能站在更高的视角,进行技术选型,审视工程化和基础建设。

另外本篇文章提及的Tooling.Report评测的框架有些较为老旧,文章结束也提供一些新兴构建工具的评测资料,以下统计主要涉及esbuildvitesnowpackwmr,评测维度和结果如下:

  • 用例

主流前端代码构建工具评测

  • 设置

主流前端代码构建工具评测

  • 开发服务器

主流前端代码构建工具评测

  • 生产构建

主流前端代码构建工具评测

  • 其他特性

主流前端代码构建工具评测

完整文章参见Comparing the New Generation of Build Tools,觉得英文吃力的可以参考这篇中文翻译