我们一定会感叹前端技术发展之快,各种可以提高开发效率的新思想和框架层出不穷。但是他们都有一个共同特点:源代码无法直接运行,必须通过转换后才能正常运行。
本文前两部分摘自吴浩麟所著《深入浅出Webpack》1.2,版权归原作者所所有
构建工具就是做这件事,将源代码转换成可以执行的JavaScript、CSS、HTML 代码,包括如下内容:
- 代码转换:将 TypeScript 编译成JavaScript、将 SCSS 编译成 CSS等。
- 文件优化:压缩JavaScript、CSS、HTML 代码,压缩合并图片等。
- 代码分割:提取多个页面的公共代码,提取首屏不需要执行部分代码让其异步记在。
- 模块合并:在采用模块化的项目里会有很多个模块和文件,需要通过构建功能将模块分类合并成一个文件。
- 自动刷新:监听本地源代码变化,自动重新构建、刷新浏览器。
- 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
- 自动发布:更新代码后,自动构建出线上发布代码并传输给发布系统。
构建其实是工程化、自动化思想在前端开发中的体现,将一系列流程用代码去实现,让代码自动化地执行这一系列复杂的流程。构建为前端开发注入了更大的活力,解放了我们的生产力。
历史上先后出现了一系列构建工具,他们各有优缺点。由于前端工程师很熟悉 JavaScript,Node.js 又可以胜任所有构建需求,所以大多数构建工具都是用 Node.js 开发的,下面来一一介绍他们。
一、构建工具发展史及趋势
Npm Scripts
Npm Scripts(NPM脚本)是一个任务执行者。NPM是安装Node时附带的一个包管理器,Npm Script 则是 NPM 内置的一个功能,允许在 package.json 文件里面使用 scripts 字段定义任务:
1
2
3
4
5
6
|
{
"scripts":{
"dev": "node dev.js",
"pub": "node build.js"
}
}
|
里面的 scripts 字段是一个对象,每个属性对应一个 Shell 脚本,以上代码定义了两个任务,dev 和 pub。其底层实现原理是通过调用 Shell 去运行脚本命令,例如,执行 npm run pub 命令等同于执行 node build.js 命令。
优点:npm scripts 的优点是内置,无需安装其他依赖
缺点:功能太简单,虽然提供了 pre 和 post 两个钩子,但不能方便的管理多个任务之间的依赖。
Grunt
Grunt 现在已基本不用了,简单说一下。Grunt 和 Npm Scripts 类似,也是一个任务执行者。Grunt 有大量现成的插件封装了常见任务,也能管理任务之间的依赖关系,自动化地执行依赖任务,每个任务的具体执行代码和依赖关系写在配置文件 gruntfile.js 里。
Grunt 的优点是:
- 灵活,它只负责执行我们定义好的任务;
- 大量可复用插件封装好了常见的构建任务。
Grunt 的缺点是集成度不高,要写很多配置后才可以用,无法做到开箱即用。
Grunt 相当于进化版的 Npm scripts,它的诞生其实是为了弥补 Npm Scripts 的不足。
Gulp
Gulp 是一个基于流的自动化构建工具。除了可以管理任务和执行任务,还支持监听文件、读写文件。Gulp 被设计的非常简单,只通过下面5个方法就可以支持几乎所有构建场景:
- 通过 gulp.task 注册一个任务;
- 通过 gulp.run 执行任务;
- 通过 gulp.watch 监听文件变化;
- 通过 gulp.src 读取文件;
- 通过 gulp.dest 写完文件。
Gulp 最大的特点是引入了流的概念,同时提供了一系列常用插件去处理流,流可以在插件之间传递,大致使用如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
// 引入 Gulp
var gulp = require("gulp");
// 引入插件
var jshint = require("gulp-jshint");
var sass = require("gulp-sass");
var concat = require("gulp-concat");
....
// 便宜SCSS任务
gulp.task('scss', function() {
// 读取文件,通过管道喂给插件
gulp.src('./scss/*.scss')
// SCSS 插件将 scss 文件编译成 css
.pipe(sass())
// 输出文件
.pipe(guilp.dest('./css'));
});
// 合并压缩 JavaScript 文件
gulp.task('scripts', function() {
gulp.src('./js/*.js')
.pipe(concat('all.js'))
.pipe(uglify())
.pipe(gulp.dest('./dest'));
});
// 监听文件变化
gulp.task('watch', function() {
// 当 SCSS 文件被编辑时执行 SCSS 任务
gulp.watch('./scss/*.scss', ['sass']);
gulp.watch('./js/*.js', ['scripts']);
});
|
Gulp 的优点:好用又不失灵活,既可以单独完成构建,也可以和其他工具搭配使用。
缺点:和Grunt 类似。集成度不高,要写很多配置后才可以用,无法做到开箱即用。
可以将Gulp 看做是 Grunt 的加强版。相对于 Grunt ,Gulp 增加了文件监听、读写文件、流式处理的功能。
FIS 3
Fis3是一个来自百度的优秀国产构建工具。相对于 Grunt、Gulp 这些只提供了基本功能的工具。Fis3集成了开发者常用的构建功能,如下所述。
- 读写文件:通过 fis.match 读文件,release 配置文件输出路径。
- 资源定位:解析文件之间的依赖关系和文件位置。
- 文件指纹:在通过 useHash 配置输出文件时为文件 URL加上 md5 戳,来优化浏览器的缓存。
- 文件编译:通过 parser 配置文件解析器做文件转换,例如将 ES6 编译成 ES5。
- 压缩资源:通过 optimizer 配置代码压缩方法。
- 图片合并:通过 spriter 配置合并 CSS 里导入的图片到一个文件中,来减少 HTTP 请求数。
大致使用如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// 加 md5
fis.match('*.{js,css,png}', {
useHash: true
});
// 通过fis3-parse-typescript插件可将 TypeScript 文件转换成 JavaScript 文件
fis.match('*.ts', {
parser: fis.plugin('typescript')
});
// 对CSS进行雪碧图合并
fis.match('*.css', {
// 为匹配到的文件分配属性 useSprite
useSprite: true
});
// 压缩 JavaScript
fis.match('*.js', {
optimizer: fis.plugin('uglify-js')
});
// 压缩CSS
fis.match('*.css', {
optimizer: fis.plugin('clean-css')
});
// 压缩图片
fis.match('*.png', {
optimizer: fis.plugin('png-compressor')
});
|
可以看出 Fis3 很强大,内置了许多功能,无需做太多配置就能完成大量工作。
Fis3的优点:集成了各种Web老发所需的构建功能,配置简单,开箱即用。其缺点是目前官方已经不再更新和维护,不支持最新版本的Node。
FIS3是一种专注于Web开发的完整解决方案,如果将Grunt、Gulp比作汽车的发动机,那么FIS3则就是一辆完整的汽车。
Webpack
Webpack 是一个打包模块化的JavaScript的工具,在Webpack里一切文件皆模块,通过 loader 转换文件,通过Plugin 注入钩子,最后输出由多个模块组合成的文件。Webpack 专注于构建模块化项目。
其官网的首页图很形象的展示了 Webpack 的定义,如下图:
一切文件,如JavaScript、CSS、SCSS、图片、模板,对于Webpack 来说都是一个个模块,这样的好处是能清晰地描绘各个模块之间的依赖关系,以方便Webpack进行组合和打包,经过Webpack的处理,最终会输出浏览器能使用的静态资源。
Webpack具有很大的灵活性,能配置处理文件的方式,使用方法大致如下:
1
2
3
4
5
6
7
8
|
module.exports = {
// 所有模块的入口,webpack从入口开始递归解析出所有依赖的模块
entry: './app.js',
output: {
// 将入口所依赖的所有模块打包成一个文件 bundle.js 输出
filename: 'bundle.js'
}
}
|
Webpack的优点是:
- 专注于处理模块化的项目,能做到开箱即用、一步到位;
- 可通过 Plugin 扩展,完整好用又不失灵活性;
- 使用场景不局限于Web开发;
- 社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展;
- 良好的开发体验;
Webpack的缺点是:只能用于采用模块化开发的项目。
Rollup
Rollup 是一个和 Webpack 很类似但专注于ES6的模块打包工具。它的亮点在于,针对ES6源码进行 Tree Shaking,以去除那些已经被定义但没被使用的代码并进行 Scope Hoisting,以减少输出文件的大小和提升运行性能。然而 Rollup 的这些亮点随后就被 Webpack 模仿和实现了。由于 Rollup 的使用方法和 Webpakc 差不多,所以这里就不详细介绍如何使用 Rollup 了,而是详细说明他们的差别:
- Rollup 是在Webpack 流行后出现的替代品;
- Rollup 生态链不完善,体验还不如Webpack;
- Rollup 的功能不如 Webpack 完善,但其配置和使用更简单;
- Rollup 不支持 Code Spliting, 但好处是在打包出来的代码中没有 Webpack 那段模块的加载、执行和缓存的代码。
Rollup 在用于打包JavaScript库时比 Webpack 更有优势,因为其打包出来的代码更小、更快。但他的功能不够完善,在很多场景下都找不到现成的解决方案。
Parcel
Parcel 是 最近新起的Web 应用打包工具,适用于经验不同的开发者。它利用多核处理提供了极快的速度,并且不需要任何配置。
Parcel的优点:
- 极速打包。Parcel 使用 worker 进程去启用多核编译。同时有文件系统缓存,即使在重启构建后也能快速再编译。
- 开箱即用。对 JS, CSS, HTML, 文件 及更多的支持,而且不需要插件。
- 自动转换。如若有需要,Babel, PostCSS, 和PostHTML甚至 node_modules 包会被用于自动转换代码.
- 热模块替换。Parcel 无需配置,在开发环境的时候会自动在浏览器内随着你的代码更改而去更新模块。
- 友好的错误日志。当遇到错误时,Parcel 会输出 语法高亮的代码片段,帮助你定位问题。
缺点:
- 不支持SourceMap:在开发模式下,Parcel也不会输出SourceMap,目前只能去调试可读性极低的代码;
- 不支持剔除无效代码(TreeShaking):很多时候我们只用到了库中的一个函数,结果Parcel把整个库都打包了进来;
- 一些依赖会让Parcel出错:当你的项目依赖了一些Npm上的模块时,有些Npm模块会让Parcel运行错误;
Parcel需要为零配置付出代价。零配置其实是把各种常见的场景做为默认值来实现的,这虽然能节省很多工作量,快速上手,但这同时会带来一些问题:
- 不守规矩的
node_module
:有些依赖的库在发布到Npm上时可能不小心把.babelrcpostcss.config.js
tsconfig.json
这些配置文件也一起发布上去了, - 不灵活的配置:零配置的Parcel关闭了很多配置项,在一些需要的配置的场景下无法改变。
Parcel使用场景受限。目前Parcel只能用来构建用于运行在浏览器中的网页,这也是他的出发点和专注点。在软件行业不可能存在即使用简单又可以适应各种场景的方案,就算所谓的人工智能也许能解决这个问题,但人工智能不能保证100%的正确性。
反观Webpack除了用于构建网页,还可以做:
与Webpack的详细对比请参看这里
二、为什么选择Webpack?
上面介绍的构建工具是按照他们的诞生时间排序的,他们是时代的产物,侧面反映出 Web 开发的发展趋势,如下所述:
- 在 Npm Scripts 和 Grunt 时代,Web 开发要做的事情变多,流程复杂,自动化思想被引入,用于简化流程;
- 在 Gulp 时代,开始出现一些新语言用于提高开发效率,流程处理思想的出现是为了简化文件转换的流程,例如将ES6转换为ES5;
- 在Webpack时代,由于单页应用的流行,网页的功能和实现代码变的复杂、庞大,Web开发向模块化改进。
这些构建工具都有各自的定位和专注点,它们之间既可以单独完成任务,也可以互相搭配起来弥补各自的不足。在了解这些常见的构建工具后,我们需要根据自己的需求去判断应该如何进行选择和搭配它们才能更好的满足自己的需求。
经过多年的额发展,Webpack 已经成为构建工具中的首选,这是因为:
- 大多数团队在开发新项目时会采用紧跟时代的技术,这些技术几乎都会采用“模块化+新语言+新框架”,Webpack可以为这些新项目提供一站式的解决方案;
- Webpack有良好的生态和维护团队,能提供良好的开发体验并保证质量;
- Webpack 被全世界大量的Web开发者使用和验证,能找到各个层面所需要的教程和经验分享。
三、Gulp与Webpack的区别
常有人拿gulp与webpack来比较,知道这两个构建工具功能上有重叠的地方,可单用,也可一起用,但本质的区别就没有那么清晰。
Gulp强调的是前端开发的工作流程,我们可以通过配置一系列的task,定义task处理的事务(例如文件压缩合并、雪碧图、启动server、版本控制等),然后定义执行顺序,来让gulp执行这些task,从而构建项目的整个前端开发流程。 简单说就一个Task Runner,就是用来跑一个一个任务的。
Gulp 没发解决的是 js module 的问题,是你写代码时候如何组织代码结构的问题。
Webpack是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。
相同点:文件合并与压缩(css,js),sass/less预编译,启动server,版本控制。
不同点,虽然都是前端自动化构建工具,但看他们的定位就知道不是对等的。
- gulp严格上讲,模块化不是他强调的东西,他旨在规范前端开发流程。
- webpack更是明显强调模块化开发,而那些文件压缩合并、预处理等功能,不过是他附带的功能。