首页
技术分享
实用工具 发布文章 新浪微博 Github

为什么需要 module bundler

很久很久以前,JS还只能运行在浏览器中,那时我们只需在页面中使用<script>标签来引入我们的代码,源代码和生产环境的代码是一模一样的,我们了解代码的每一个细节;随着前端项目规模的扩大,我们大量引入第三方库,怎样方便有效地获取和组织代码成了一个问题;页面大量的 <script> 标签意味着更多的 HTTP 请求,增加了页面的下载时间;于是模块打包工具(module bundler)应运而生,它们带来的几点好处:

  • 提供原生JS缺乏的模块化系统(ES2015 已经定义了模块系统,但大部分浏览器还未支持)
  • 将无数个微小的JS文件打包成一个或几个,减少 HTTP 请求
  • 提供一些附加功能,如代码转换(ES2015 to ES5)

module bundler 带来的问题

但是别以为使用 module bundler 就万事大吉了,因为这意味着源代码和生产环境的代码是有差异的,如果你不了解各个打包工具的基本工作原理,很可能会踩坑。这不,imgur 的工程师就遇到一个问题:为了提高页面渲染速度,他家把原来基于Backbone 的浏览器端渲染架构改为了基于React的服务端渲染,兴冲冲地测试性能的时候发现:纳尼,首页图片渲染到一半就卡住了,过了 2s 后才渲染完成!说好的服务端渲染大幅提高页面呈现速度呢?!后来使用 Chrome Timeline 分析结果如下:

原来浏览器主线程渲染图片的过程中,一大波JS开始执行,阻塞了渲染进程;好吧,看看是哪些JS运行这么耗时:

经过分析,那段JSbrowserify的模块加载逻辑!使用了模块打包工具后,我们失去了对自己代码“最后一公里”的控制权,但又不可能不用,不然你都不好意思和别人打招呼。面对如此多的打包工具,我们该如何抉择呢?下面抛砖引玉,对目前比较流行的三个打包工具做一个简单的介绍和比较,挑一个适合自己的吧。

browserify

browserify 是这三个打包工具中最早发布的,看了下 github 上的初始 commit,日期是 2010 年。当时的背景是node.js刚火起来,npm 上的模块越来越多,广大前端还在用 AMD 规范的加载器,如 require.js,眼巴巴地望着 npm 上数量巨大的各种模块;于是 browserify 顺势推出:

Browserify lets you require(‘modules’) in the browser by bundling up all of your dependencies.

这下好了,想用什么模块,直接npm install,然后require就行了,browserify 会自动帮你把所依赖的代码打包成一个 bundle,so easy!从此,前端界新潮的模块写法就从 AMD 变为 CommonJS 了(ES2015 module 出现之前)。browserify 目前的生态还是挺不错的,不过我只在 2014 年尝试了一下就转投 webpack 了,所以想更进一步了解的直接去 bowserify 官网吧。

webpack

webpack 的流行一定程度上是沾了 react 的光。记得当时 angular 红透半边天的时候,突然半路杀出个 react——JSX、virtual dom 让人眼前一亮;而且 react 作者之一 Pete Hunt 写了个 webpack howto 的教学文章,说他们内部用 react 开发 Instagram 就用的 webpack 打包。大神一推荐,大家肯定都要去试一试啰。我也是最初看 webpack 的官方文档看得想放弃的时候突然看到 Pete 的那篇文章,如沐春风,立马就 try 了一下。

webpack 的优势:

  • 支持多种模块规范:AMD、CommonJS、ES2015 module
  • 支持 code spliting,不用再像 browserify 那样只能生成一个大得吓人的 bundle.js 了
  • everything is a module,你能require一切静态文件(JS、CSS、HTML、images、fonts…)
  • 配套的开发工具,如热替换

rollup

rollup 相对来说还比较年轻,而且专注 ES2015 module 的打包。为什么在市场上拥有这么多打包工具的情况下还有 rollup 的一席之地呢?这其实和 ES2015 关系密切。当初JS规范中没有定义模块,于是大家依赖 AMD 和 CommonJS,现在血统纯正的模块规范出来了,寄人篱下的日子终于结束了;观察 github 上的开源库,基本已经从require 切换到import了;可 webpack 不是支持 ES2015 规范么?其实 webpack 只是支持 ES2015 的写法,底层还是转换为 CommonJS 的模块形式了(webpack2 已经原生支持 ES2015 module)。作为普通用户本来没必要管这些打包工具底层到底对我们的代码做了什么,但 ES2015 module 有个特点——静态结构(模块的importexport不能在运行时改变),利用这点可以对打包的代码做一些优化,这就是 rollup 的核心卖点之一:Tree Shaking.

Tree Shaking 是个什么玩意?大白话讲就是可以将你代码中没有用到的那部分剔除掉,如下(左边是是原始代码,右边是打包后的代码):
image

对比

推荐 github 上面的一个对比示例:rollup-comparison;里面使用 browserify、webpack、rollup 分别对相同的代码打包,结果如下(es5-only 的文件表示被打包的模块没有使用 ES2015 的模块系统):
image

可以看到 rollup 打包出来的代码确实要小很多,一个是因为 Tree Shaking 的作用,另一个是因为它把所有模块都提升到同一个作用域中,而不是像其他打包工具会加入很多自定义的模块逻辑(会一定程度地影响代码运行速度)。但这三个工具不论是功能还是关注点都有差别,没必要仅仅纠结于最后的结果。

后记

记得以前引入 scss 的时候也遇到过类似的问题,稍不留神的嵌套可能导致非常多的冗余代码;面对逐渐失去控制权的“最后一公里”,我们唯有深入了解这些工具,才能保证目标代码的质量。

参考资料

[m.imgur.com] page load performance 及里面的所有链接

Google 的打包工具——Closure Compiler

本文来源:https://github.com/szrenwei/blog/issues/2