## Webpack 在现代化前端开发中的作用与未来 文/李成煕 ### 前端构建工具的百家争鸣 “现代化”前端开发,这个术语在近几年的前端文章上时不时出现,常用于区分“刀耕火种”时代的前端开发。这些文章都没有对“现代化”、“刀耕火种”进行解释,仅仅是描述了一些现象。“刀耕火种”往往指的是没有规范、没有模块化、工程化落后、框架初级,而“现代化”则相反,指的是规范到位、模块化成熟、工程化先进、框架高级。现在,我们正处于从“刀耕火种”过渡到“现代化”开发的重要阶段,webpack 正是这一阶段诞生的重要工具(演进过程请见图1)。 图1  前端构建工具的演进过程 图1 前端构建工具的演进过程 这一过渡阶段,大约从2011年开始,前端社区许多工具、框架方案纷纷诞生。率先火起来的是模块加载器(Module Loader),经典的作品有 RequireJS。虽然在此之前,有不少前端类库都有自己的模块化开发方法,如 Yui、Dojo,但那些都只是 JavaScript 一些约定俗成的写法,而 RequireJS 是真正意义上落实白纸黑字的前端模块化规范,推动了社区的发展。但单纯依靠 RequireJS 进行生产环境代码部署,有很明显的缺点,因为所有的模块都会异步加载,因此后来诞生了如 r.js 一类可以帮模块加载器进行文件合并与压缩的工具。 JavaScript 的模块化缺失问题得到初步解决之后,人们发现对每个页面的入口文件都要运行 r.js 一类的打包工具进行打包。能否让这一过程更为自动化呢?此时,便轮到任务自动化工具(Task Runner)大红大紫,如 Grunt、Gulp。Grunt 与 Gulp 虽然实现机制不同(前者基于临时文件进行构建,后者通过文件流处理),但本质上都是文件自动化处理工具,通过结合模块加载器、加载器打包工具以及任务自动化工具,可以将整个开发流程自动化。 模块加载器与任务自动化工具让模块化、自动化逐渐普及到前端开发领域,让前端的开发效率大大提升。这一切看似已经运行流畅,但此时,以 Browserify、webpack 为代表的模块打包工具(Module Bundler)开始入场搅局(尽管 Browserify 诞生比 Grunt 和 Gulp 都要早,但似乎红得比两者都晚)。模块加载器与模块打包工具的区别在于,前者在浏览器运行中,当资源被请求的时候才被加载,而后者则事先将模块打包好成静态资源,然后再进行各种优化。因此模块打包工具对比模块加载器来说,具体碾压性的优势。而 webpack 便是模块打包工具中的绞绞者。它不仅支持 AMD、CommonJS 等模块的引用方式,而且也跟进支持了 ES Module 的模块化写法;它不仅将 JavaScript 资源视作模块,其它资源如 CSS、Image 等也都一律视作模块。加上它本身支持多个 JavaScript 文件同时编译,强大且完善的加载器(loader)和插件(plugin)生态,它既可用简单的配置搞定小而美的应用,又可以通过拆包、自定义插件等特性来应对大规模、变化多端的应用带来的挑战。 ### webpack 一统天下? 大约在2013年前后,目前市面上主流的所使用的构建方案都基本诞生,包括模块加载器、模块打包工具和任务自动化工具。它们并非绝对的对立分割,它们相互间有借鉴学习,也能混合使用。 一图胜千言,图2大体描述了这些方案的关系。 图2  各主流构建方案之间的关系 图2 各主流构建方案之间的关系 webpack 是支持 AMD、CommonJS 和 UMD 模块打包工具,加上强大且完善的加载器(loader)和插件(plugin)生态,基本覆盖 Browserfiy 和 RequireJS+r.js 的功能,另外它还具备多文件打包、拆包、压缩、文件哈希、热更新等 Browserify 不具备的功能。虽然有些团队喜欢采用 Gulp+webpack 的混合方案,但其实 webpack 自身就能满足大部分项目生成静态资源的功能。 基于这些工具各自的功能,不同的前端团队根据自己的业务特点,发展出不同的前端静态资源构建方案,在2017年,主流方案主要有以下三种,我们来逐一分析下列方案的优势劣势。 #### 方案1 Gulp 主要是依靠 Gulp 及其插件生态,优势在于有庞大的插件生态(尽管比 Grunt 少,但比 webpack 要多很多),能满足各种各样奇葩的需求。但插件多只是方案采纳的一个参考条件,插件的考量准则应该是“够用就好”和“质量过关”。 这种方案的缺点也很明显,资源目录都被强制约定好了,不利于模块化、组件化开发。Gulp 风靡前端界的时候,开发者都会被规定好只能在哪里放 JavaScript,哪里放 CSS,如图3所示。 图3  使用Gulp的项目文件结构 图3 使用 Gulp 的项目文件结构 这类约束方式一旦遇到像 React、Vue 一类框架的时候,将会非常头疼,因为这类框架的组件,通常是组件相关的 JavaScript、CSS、Image 资源都置于同一个组件目录里,这样需要手动书写大量构建匹配逻辑,去处理组件对应的 JavaScript、CSS、Image 等资源。因为我们一般都是一个 Gulp 任务只处理一种资源,然后将各种任务组合起来,如图4所示。一旦让 Gulp 去处理一个组件的所有资源,构建的逻辑复杂度必然是成倍增加。 图4  代码显示一个Gulp任务只能处理一种资源 图4 代码显示一个 Gulp 任务只能处理一种资源 此外,Gulp 这种非配置化、按任务拆分的构建方式,看似是容易上手,但实质是将许多的难题暴露给构建的开发者。对于构建的使用者来说,也并不好定制,修改一个任务,或者往里面添加一个任务,都增加了许多的不确定性,对构建开发者和使用者来说,说白了就是在维护一个小型的 webpack。 #### 方案2 Gulp+Browserify 或 Gulp+webpack 将 Browserify 或 webpack 引入 Gulp,是尝试解决方案1模块化开发的重大举措,不过一般只是针对 JavaScript 文件。此方案看似引入新的工具来解决问题,其实也增加了额外的构建维护复杂度。 有的开发者甚至让 Browserify 或 webpack 直接接管其它大部分静态资源的处理,Gulp 则主要是负责传入文件流,但实则上这种方案应该归类为方案3,Gulp 在这里只是一个任务的流程控制,比方说图5所示的处理办法,Gulp 只是启动了一个 webpack 任务,让 webpack 去负责构建,然后再将一个又一个的 Gulp 任务组装起来,形成一定的任务流程。 图5  Gulp主要负责任务的流程控制 图5 Gulp 主要负责任务的流程控制 其实这些都可以用 npm scripts 轻松替代,如图6所示,可以在 package.json 的 scripts 里设置这些 npm run 的命令,然后去启动一个脚本,在脚本里运行 webpack 的构建。如果你有其它并行或者串行的任务,你既可以在脚本里实现,也可以简单通过`&`或者`&&`符号拼接这些命令。 图6  npm scripts也可轻松控制任务流程 图6 npm scripts 也可轻松控制任务流程 #### 方案3 webpack 该方案完全依靠 webpack 及其插件生态,只需要定义好入口文件,便可以用配置化的方式,去处理所有的静态资源依赖。与方案2相比,少了引入 Gulp 的额外复杂度,虽然 webpack 本身也有非常庞大的文档,但相比于自己去四处找寻合适的插件进行组装,学习 webpack 的文档并使用推荐的加载器和插件进行项目构建的配置,其实会更为容易,而且也更容易定制。 如图7,是一个最简单的 webpack 构建配置,webpack 最基本的四大概念 entry、output、module、plugins 在这个配置中都有体现。entry 相当于 JavaScript 源文件,output 就是生成的文件的路径、名字、CDN 等,module 则是配置这些资源模块(如上文提到的,webpack 将一切资源都看作模块)处理规则的地方,基本都是用 loader 去进行编译处理。而 plugins 则是 webpack 提供给开发者进一步优化应用的能力。 图7  webpack的构建配置代码 图7 webpack 的构建配置代码 而像 Gulp 下面的几个任务(见图8),在 webpack 里仅用 module 里的 rule 进行配置就可以达到类似的效果。但像合并公共模块(CommonChunk),压缩代码这些 webpack 一个插件就可以完成的事情,Gulp 要么难以完成,要么需要额外的逻辑。因此往往 webpack 100行配置就能完成的事情,Gulp 需要超过300行的任务逻辑。 图8  Gulp任务 图8 Gulp 任务 另外 webpack 的构建定制方面也有其独特的优势。我们一般可以预先配置好基础配置 webpack.base.js,然后允许用户在 webpack.project.js 中进行自定义配置,最后通过 webpack-merge 将两者合并,便可在节省重复工作的基础上,进行较大程度的构建定制。而像 Gulp 这类配置,你只能直接破坏原有的逻辑,在 Gulp 任务里直接加插新的构建逻辑。 #### 基于 webpack 的方案逐步成为主流 尽管目前方案1依然是大部份项目的首要构建方案,但方案2和方案3也越来越受欢迎。这是由于像 React、Vue 这一类更主张模块化、组件化开发的框架的普及所带来的改变。webpack 作者 Tobias Koppers 在自己的分享里也披露,webpack 首次迎来重大增长,主要是由于 React 的使用,以及 React 核心开发们在社区里的推广。加上 Vue 等一些重量级玩家的加入(Vue 作者为了让使用者都更好地启动 Vue 项目,特意基于 webpack,弄了一个 vue-cli 项目),webpack 由此迅速走红,见图9所示。 图9  webpack项目stars趋势 图9 webpack 项目 stars 趋势 与前端框架 Angular、React、Vue 三足争霸的局面不同,webpack 看似要率先在前端构建工具这块一统天下了。但2015年,一款轻量小巧,配置方式与 webpack 几近一致的模块打包工具横空出世,它的名字叫 rollup.js。它的最大特色是 Tree Shaking 和扁平化的打包,能够在支持各大模块化开发方式的情况下,将打包后的文件大小控制到最小,并且打包后文件的执行速度也相当快。这一特性相当受类库和框架开发者的欢迎,Vue 和 React 为了控制包大小,都分别于2016和2017年转用 rollup.js 对框架进行打包。不过由于 rollup.js 在生态方面仍未成熟,许多功能特性仍不支持(如拆包、热更新等),webpack 在2.0版本也引入了 Tree Shaking 这一重大特性,虽然 rollup.js 对 webpack 的地位产生一定的挑战,但仅限类库和框架开发领域。rollup.js 作者 Rich Harris 在博文里也建议,开发应用时用 webpack,开发类库的时候用 rollup.js。 ### webpack 的未来,以及我们能做些什么? 相信未来几年,webpack 依然会是最受欢迎的构建工具之一。它的目标依然是努力改善开发者体验以及优化 Web 应用的性能。 随着 webpack 2.x 的到来,webpack 不仅严格控制配置项,逐步收归与规范许多常用的第三方加载器和插件(许多已经将仓库转交到 webpack-contrib 下面),而且全面优化整个文档,虽然庞大,但却更为有条理。webpack 中文社区率先对文档进行了完整的翻译,整理出一套完善的社区文档翻译流程,并且正在协助韩文文档的翻译。 webpack 目前有一点需要努力的是改善插件和加载器的开发体验,降低开发插件和加载器的难度门槛。目前仅仅通过阅读官方的开发文档,只能开发出较为简单的入门级插件,如果你仔细阅读 extract-text-webpack-plugin、html-webpack-plugin、happypack 等功能更为复杂的插件,你就会发现许多插件开发的高级用法,不仅需要你了解 webpack 本身基于的 tappble 事件流机制,还需要理解 webpack 的一些内置插件的功能。除此以外,webpack 在对资源处理的过程中,将一切配置和内容往内存中的一个对象中存储,你还要分析每个事件触发后,这个对象变成什么样了。对于这点,中文社区开发者贡献了许多的源码分析、插件开发等的文章,基本都收录在 awesome-webpack-cn 这个仓库里。迟迟未见动作的官方社区,在 webpack 布道者 Sean Larkin 的推动下最近也逐步开始撰文介绍 webpack 的内部机制和周边的一些重要依赖,如 tappble、enhanced-resolve、memory-fs 等等。 webpack 及其社区对新技术的支持力度也比其它构建工具要领先。例如 webpack 不仅支持浏览器、Node 环境的打包方式,而且还支持像 webworker、node-webkit、electron 应用的打包,相信日后只要 JavaScript 所触及之处,都能看见 webpack 的身影。最近 Google 力推的离线应用方案 Progrss Web Application,webpack 也有对应的社区插件方案 offline-plugin。为了满足更多开发者的需求,官方还建立的投票页面,供大家在上面票选自己最需要的特性,其中 WebAssembly 支持的人气也相当高涨,相信不久将来,这项特性要么由社区率先通过加载器实践,要么由官方进行引入,见图10所示。 图10  开发者所需的webpack特性中,WebAssembly的呼声很高 图10 开发者所需的 webpack 特性中,WebAssembly 的呼声很高 webpack 最近另一项值得留意的重大动作是官方将 webpack 的命令行拆出来,单独创建了 webpack-cli 项目。该项目初期主要的专注任务有三个,一是将 webpack 自带的命令行融合进来,二是将 webpack 1 升级至 webpack 2 的流程自动化,最后也是带来最多遐想的,webpack-cli 基于 Yeoman 和 Inquirer,允许开发者搭建自己的项目脚手架。构建工具本来便是脚手架必不可少的一部分,webpack 这次借助 webpack-cli 染指这一领域,预计未来还有不少围绕脚手架进行的优化,例如协助分析脚手架的构建性能瓶颈、分析脚手架生产环境的性能问题、协助脚手架接入开发者所在的开发体系等等。 对于 webpack 的近况,可以关注 webpack 在 medium.com 开通的官方博客(需要翻墙)。如果英文不够好,也可以关注 webpack 中文社区社区,我们也会定期更新官方的文档和相关资讯。也希望大家可以贡献自己的余力,帮忙翻译文档、文章,或者维护 webpack 社区的各类插件、加载器等等。 *** **相关资料** [1] https://medium.com/webpack/webpack-and- rollup-the-same-but-different-a41ad427058c [2] [http://webpack-china.org](http://webpack-china.org) [3] https://github.com/webpack-china/ awesome-webpack-cn#mortar_board- webpack%E5%BC%80%E5%8F%91 [4] [https://webpack.js.org/vote/](https://webpack.js.org/vote/) [5] [https://medium.com/webpack](https://medium.com/webpack) **其它参考文献** http://sokra.github.io/slides/webpack/ https://medium.com/@housecor/browserify-vs-webpack-b3d7ca08a0a9 https://www.toptal.com/front-end/webpack-browserify-gulp-which-is-better https://medium.com/webpack/webpack-and-rollup-the-same-but-different-a41ad427058c