如何选择JavaScript构建工具之Babel、Browserify、Webpack、Grunt以及Gulp
当我们开始一个新的 JavaScript 项目时,我们需要考虑的第一件事就是搭建一个前端编译环境。但是在面对众多的 JavaScript 构建工具时,我们却无所适从,不知道究竟哪一个才是最适合我们的。
想象一下,如果有一个很简单的判断标准,让你知道如何针对不同的项目,选取不同的前端构建工具,那是不是很美好呢?事实上,在使用自动构建系统,积累了5年的项目开发经验之后,我总结出来如下内容。相信我,它是可以让你明白不同构建工具的使用场景的。
开门见山
判断你的项目需要使用哪种构建工具是很容易的:
- 如果是小项目的话,基于 ES6 编译器即可
- 如果是单页应用的话,还需要一个模块打包器
- 如果你的项目,部署在了生产环境之中,除了上述之外,还需要一个能够自动执行的任务运行器
下面是我推荐的一些可以满足你上述需求的前端构建工具:
- 使用 Babel 可以编译适配 ES6 代码
- 使用 Webpack 能够打包 JavaScript 文件以及其相关的依赖
- 使用 Gulp 能够自动化地将文件批量重命名,从而刷新静态资源文件的缓存
那么,问题来了,前端构建工具那么多,为什么我偏偏推荐上述的这些呢?
追根溯源
我对很多流行的 JavaScript 构建工具进行了分析,并分别找出了它们的优点与缺点。既然编译器是无论如何都需要的,那么我们就从编译器开始说起吧。
编译器
ES5 版的 JavaScript 并不是那么优雅。尽管 ES6 与 ES7 有了非常大的提升,但是目前的浏览器并不支持 ES6 与 ES7。
幸运的是,有很多工程师打造了能够将新的 JavaScript 代码
转换成浏览器支持的 JavaScript 代码
的工具,有的甚至还在此基础上,加入了新的特性与功能 — 例如微软的 TypeScript 。但是如果我们要在众多的 JavaScript 编译器中,选取一个最接近 JavaScript 官方代码风格的,那么Babel
无疑是最优秀的。
Babel
在阅读完这篇文章之后,如果你必须要学着使用一款工具,那么这款工具肯定就是 Babel
了
Babel
的功能并不会让你大吃一惊,正如你所期待的那样,它主要是将 ES6 代码转换成浏览器所支持的 ES5 代码而已。还有一点就是它允许你在 Babel 已拥有的转换器基础之上,定制属于你自己的转换器——欣喜的是,Babel的社区在转换器方面,已经为我们提供了很多。此外,它还能够对 ES7 的特性进行转换,例如 async
/ await
以及 decorators
。同时,它也提供了对 React JSX 的支持。
尽管 Babel 在转换 JavaScript 代码方面做得足够优秀,但是除此之外,它不能帮我们做任何事情。事实上,它甚至不能通过ES6 的 import
和export
的表达式,将我们的多个文件进行合并。这也是为什么我们接下来需要一个模块打包器了。
模块打包器
大多数的项目,无论规模多大,程序员都习惯于将代码拆分到多个文件之中。尽管你可以使用 script
标签,来一个个地引入这些单独的js文件,但是你终究还是需要基于不同文件的依赖关系来决定它们引入的先后顺序的。在这件事情上,工具能够比你做得更好,这也就是为什么你需要使用模块打包器,来将一些文件自动化地打包进一个单独的文件的原因。
尽管我们可以从网上找到很多的模块打包器,但是只有两款是最具代表性的:Browserify 与 Webpack
Browserify
Browserify 可以使得 Node packages 获得浏览器的支持。当然,它也可以帮助我们将 Web 应用打包成一个 Node Packages。
这种以 Node 为中心的哲学,有很多的好处。使用 Browserify 来打包一个应用是非常容易的。你可以使用 Node 内置的模块,例如 path
,也可以引用你之前在Node项目中写过的任何代码。当然,缺点就是,你的单页应用通常需要的资源,正好是 Node 项目不需要的,例如 CSS 、图片以及字体。
尽管存在这种问题,但是这种问题也并不是不能解决的。许多人已经写了插件来使得 Browserify 可以打包这些资源。这些插件可以使你能够转换 ES6 到 ES5 、打包 CSS、分离你的代码到多个文件之中等等。但是考虑到这些插件违背了 Browserify 这一工具的设计初衷,它的配置将会比较混乱。
因此,尽管 Browserify 是一款能够优雅地将 Node Packages 打包成浏览器支持形式的工具,但是如果你写的是一个单页应用,那么你最好选择一款专门用于打包、并且能够打包所有资源文件的模块打包器。
Webpack
Webpack 是一款能够将许多的 JavaScript 模块以及它的相关依赖打包进一个单独文件的工具。它并不需要你给出这些模块的依赖具体是哪些,只要它能够打包成 JavaScript 模块即可。
只要它能够打包成 JavaScript 模块即可?这意味着它不支持 CSS 和图片,是吗?当然不是,Webpack 是可以通过各种 loaders 来将各种资源文件都转换成 JavaScript 模块的神器。
Loaders 是一种能够处理不同资源文件的转换器。它们能够接收任何形式的资源文件,也能输出任何形式的资源文件,而不仅仅是 JavaScript 。而且,这种操作也是可以链式的,它允许你先将 SCSS 文件转换为 CSS,然后再将CSS 转换为 JavaScript 模块。然后,Webpack 再对这些 JavaScript 模块统一打包!
如果非要说 Webpack 有什么缺点的话,那就是许多流行的案例项目都包含了令开发人员感到害怕的 Webpack 复杂的配置文件。如果你按照我的 configuring Webpack with Babel in 26 lines 教程来做的话,你就会发现解决这种问题是如此的 so easy。
经过上述的分析,现在你应该知道怎样使用模块打包器来生成一系列的静态资源文件了。但是有了这些文件之后,我们该怎么做呢?这就是该任务运行器发挥作用的时候了。
任务运行器
任务运行器是用来定义并运行任务的工具。简言之,任何你可能在命令行上执行的操作,都可以通过任务运行器来完成。
在使用任务运行器之前,理解这句话是非常重要的:不要为了编写任务而编写任务。也就是说,如果你自己编写一个任务来进行模块打包,这也是可以的。但是如果你把这件事情交给Webpack去做的话,你只需要启动一下 Webpack 就好了,我们完全没有必要重复造轮子。
编写一些你的模块打包器不能处理的任务才是有必要的,例如为模块打包器自动生成的静态资源文件在网页中插入一个 script
标签。
Grunt
Grunt 是一个运行你先前定义过的任务的工具。也就是说,如果你不定义任务,基本上 Grunt 不能为你做任何事情。
原因就在于,Grunt 的任务并不是使用 JavaScript 的代码来定义,而是通过一系列的配置对象来进行声明式的定义的。为了保持 Grunt 核心包的大小,它的所有配置对象都是插件化的 — 从监控文件变化到复制、串联文件。
这种做法也是有它的优势的。Grunt 有成千上万的插件,基本上你不需要编写任何代码,针对不同的需求,选取其一,直接拿来用即可。它最大的问题其实在于,如果你感觉确实有必要对插件运行的效果做一些微调的话,通过编写纯 JavaScript 代码是没法实现的,你必须要为此重新编写一个 Grunt 插件了。
Gulp
Gulp,类似 Grunt ,也是一个用来定义并运行任务的工具。
Grunt 与Gulp 最大的不同就在于 Grunt 使用配置对象来声明任务的运行方式,而 Gulp 则使用 JavaScript 的函数来定义任务。也正是由于 Gulp 知道如何处理 JavaScript 返回的 streams 或者 promises ,这使得你在编写任务的时候具备很大的灵活性。
Gulp 与 Grunt 一样,也拥有非常丰富的插件库。不过考虑到 Gulp 的插件提供的都是一些原始基础的功能,你也可以引用 Node 模块来丰富完善你的 Gulp 任务体系。
Gulp 最大的问题在于 streams
与 promises
的对接对新手来说可能是比较困难的。但是这也是一个双刃剑,随着实践经验的积累,你就能够体会到 streams
与 promises
的好处了。
译自:James K Nelson, http://jamesknelson.com/which-build-system-should-i-use-for-my-javascript-app/