本文前两部分摘自吴浩麟所著深入浅出 webpack1.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 字段定义任务:
- {
- "scripts":{
- "dev": "node dev.js",
- "pub": "node build.js"
- }
- }
里面的 scripts 字段是一个对象, 每个属性对应一个 Shell 脚本, 以上代码定义了两个任务, dev 和 pub. 其底层实现原理是通过调用 Shell 去运行脚本命令, 例如, 执行 npm run pub 命令等同于执行 node build.js 命令.
优点: npm scripts 的优点是内置, 无需安装其他依赖
缺点: 功能太简单, 虽然提供了 pre 和 post 两个钩子, 但不能方便的管理多个任务之间的依赖.
附: npm scripts 使用指南 http://www.ruanyifeng.com/blog/2016/10/npm_scripts.html
Grunt
Grunt
Grunt https://gruntjs.com 现在已基本不用了, 简单说一下. Grunt 和 Npm Scripts 类似, 也是一个任务执行者. Grunt 有大量现成的插件封装了常见任务, 也能管理任务之间的依赖关系, 自动化地执行依赖任务, 每个任务的具体执行代码和依赖关系写在配置文件 gruntfile.js 里.
Grunt 的优点是:
灵活, 它只负责执行我们定义好的任务;
大量可复用插件封装好了常见的构建任务.
Grunt 的缺点是集成度不高, 要写很多配置后才可以用, 无法做到开箱即用.
Grunt 相当于进化版的 Npm scripts, 它的诞生其实是为了弥补 Npm Scripts 的不足.
Gulp
Gulp
Gulp https://www.gulpjs.com.cn 是一个基于流的自动化构建工具. 除了可以管理任务和执行任务, 还支持监听文件, 读写文件. Gulp 被设计的非常简单, 只通过下面 5 个方法就可以支持几乎所有构建场景:
通过 gulp.task 注册一个任务;
通过 gulp.run 执行任务;
通过 gulp.watch 监听文件变化;
通过 gulp.src 读取文件;
通过 gulp.dest 写完文件.
Gulp 最大的特点是引入了流的概念, 同时提供了一系列常用插件去处理流, 流可以在插件之间传递, 大致使用如下:
- // 引入 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
- .piple(sass())
- // 输出文件
- .piple(guilp.dest('./css'));
- });
- // 合并压缩 JavaScript 文件
- gulp.task('scripts', function() {
- gulp.src('./js/*.js')
- .piple(concat('all.js'))
- .piple(uglify())
- .piple(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
FIS 3
Fis3 http://fis.baidu.com/ 是一个来自百度的优秀国产构建工具. 相对于 Grunt,Gulp 这些只提供了基本功能的工具. Fis3 集成了开发者常用的构建功能, 如下所述.
读写文件: 通过 fis.match 读文件, release 配置文件输出路径.
资源定位: 解析文件之间的依赖关系和文件位置.
文件指纹: 在通过 useHash 配置输出文件时为文件 URL 加上 md5 戳, 来优化浏览器的缓存.
文件编译: 通过 parser 配置文件解析器做文件转换, 例如将 ES6 编译成 ES5.
压缩资源: 通过 optimizer 配置代码压缩方法.
图片合并: 通过 spriter 配置合并 CSS 里导入的图片到一个文件中, 来减少 HTTP 请求数.
大致使用如下:
- // 加 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 具有很大的灵活性, 能配置处理文件的方式, 使用方法大致如下:
- module.exports = {
- // 所有模块的入口, webpack 从入口开始递归解析出所有依赖的模块
- entry: './app.js',
- output: {
- // 将入口所依赖的所有模块打包成一个文件 bundle.js 输出
- filename: 'bundle.js'
- }
- }
Webpack 的优点是:
专注于处理模块化的项目, 能做到开箱即用, 一步到位;
可通过 Plugin 扩展, 完整好用又不失灵活性;
使用场景不局限于 Web 开发;
社区庞大活跃, 经常引入紧跟时代发展的新特性, 能为大多数场景找到已有的开源扩展;
良好的开发体验;
Webpack 的缺点是: 只能用于采用模块化开发的项目.
Rollup
Rollup
Rollup http://www.rollupjs.com/ 是一个和 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 更有优势, 因为其打包出来的代码更小, 更快. 但他的功能不够完善, 在很多场景下都找不到现成的解决方案.
二, 为什么选择 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 更是明显强调模块化开发, 而那些文件压缩合并, 预处理等功能, 不过是他附带的功能.
来源: https://juejin.im/entry/5ae00f8ef265da0b9d77e1b9