前端开发工具集(五):模块打包器(browserify、parcel、rollup、webpack)

2,848 阅读5分钟

本文是开发工具集系列文章之一,其他请点击这里

本文只包含整体性讨论,具体打包器介绍请参考


模块打包器(module bundler),顾名思义就是将各个模块(module)打包到一个或几个文件中,然后在html文件中引入。
在es module中,module是对应script的一个概念,即可以理解为应用中使用到的非全局js。

1 打包器的由来

古时候,我们将自己写的源码和第三方library在html文件中用script标签引入,这种做法随着项目规模的扩大会带来很多问题,主要问题为

  • 变量都在全局作用域,容易命名冲突
  • 需要处理好文件之间的依赖,代码引入要注意加载顺序
  • http请求过多,出现性能问题
  • 代码维护复杂

1.1 IIEF

这里我们先试着解决作用域的问题,在es5中除了全局作用域还有函数作用域,那么我们可以将代码拆分到不同函数作用域中,可以使用IIFE结合revealing module,如

var revealed = function(){
   var a = [1,2,3];
   function abc(){
     return (a[0]*a[1])+a[2];
   }
   return {
      name: 'revealed',
      abcfn: abc
   }
}();

这样一定程度上改善了作用域问题,可以使用gulp等task-runner将不同文件中的代码拼接在一起,但是没解决依赖的问题。

1.2 module loader

我们需要真正的模块才能解决这些问题,虽然后来node.js端出现了commonjs,但是浏览器端并没有对应的语法,因此出现了amd、cmd等module formats,对应requirejs、systemjs等module loader实现了这些规范,浏览器端也就有了自己的模块化,这些module loader可以在js运行时解析依赖关系,并实现了按需加载。

1.3 module bundler

module loader并没有解决http请求数量多的问题,module bundler的出现在前者的基础上将所有模块打包到一个(也可以多个)js文件中,实际在浏览器运行时使用的是打包后的文件。当然如果没有配置好,将所有模块同时放在同一个文件一起加载,也会产生负面效果。

参考

2 打包器的现状

现在的打包器已经是在模块处理方面占据着主流位置,虽然浏览器端已经出现了语法上的模块,模块打包器依然被人们广为使用。

现在可用的打包器很多,这里会选两种使用较广泛的打包器来深入讨论,即webpack和rollup,另外parcel和browserify也有一定的用户量,感兴趣可以自己了解,其中前者使用简单,后者发布较早,npm trends显示如下

3 打包器评测

一个合格的打包器,我们需要利用一些评价指标来评测,以便选择更合适的打包器,并做出最佳实践。这里 我们参考 A health checkup for web build tools

这里我们分6个维度、若干个小项来对browserify、parcel、rollup、webpack进行评价,分别为

  • 代码拆分(Code Splitting)通过将bundle拆分成多个文件,从而是先按需加载。
    • 新的worker之间代码拆分,包括Service Workers, Module Workers and Worklets.
    • main线程和worker线程之间代码拆分
    • 动态引入
    • 多个入口之间的重复依赖
    • 同一个入口的不同文件之间的重复依赖
    • 不同bundle之间导入的变量是不是live binding
  • 用于缓存的hash值得生成
    • js依赖的asset修改,对应的hash值是否修改
    • asset依赖的asset修改,两者对应的hash是否修改,比如css依赖的img
    • 对于js之间的引用,应有一个import map避免一个文件修改时所以依赖该文件的文件hash都要改
    • hash值可禁用
    • 基本的根据内容生成的hash
    • 只更改路径不修改内容,hash应一样
    • hash值是否是根据最终文件的内容生成
    • 如果没有import map,被依赖js修改,对应js的hash应修改
  • 模块输入,注意browserify不支持esmodule
    • 是否支持commonjs
    • 是否支持esmodule
    • 是否支持在node_modules引入
  • 非js资源处理
    • 二进制,比如通过arrayBuffer或url
    • css
      • 是否支持css模块化(有独立作用域)
      • 是否能在js文件中import
      • 能否由导入的css获取对应的url
    • 自定义类型
    • 依赖
      • 被引入的非js文件能否有依赖
      • js和非js的依赖项要去重
    • 入口可以是非js文件么,比如html
    • 在html文件中生成资源
      • 内联script
      • 根据最新生成的bundle更新html中用来引入的script标签
      • preload相关asset
      • preload js依赖
    • 图片,通过data url或者asset url
    • Service Workers
  • 模块导出格式
    • 生成commonjs
    • 自定义模块
    • es 模块
  • 转换
    • 使用Brotli压缩
    • 图片自动优化
    • svg自动优化
    • 清理未使用的代码
    • 清理动态引入但未使用的代码
    • 环境变量等flag
    • 压缩js
    • 转换js以兼容旧版本

最终成绩我们关注的rollup和webpack很接近,webpack稍稍领先,相比其他两个差距较大。其中rollup在hashing项失分较多,webpack不可以导出es module。

4 打包器的未来

这里借用rollup作者在Rollup - Next-generation ES6 module bundler - Interview with Rich Harris中提到的,当浏览器自身支持es module语法后,打包本身的意义就会失去意义,而打包器这类工具会为应用程序提供性能优化和其他更为复杂的功能而继续存在。


完结撒花