1. 什么是 Gulp?
简单的讲,Gulp 是一个构建工具,一个 streaming 构建工具,一个 nodejs 写的构建工具
总之,它是一个构建工具
那么什么是构建工具呢?
构建工具本质就是为了自动化构建,解放程序员、提供程序员效率的工具
我们来举个例子,最早的 make,因为每次都 cc 编译,太恶心了,而且当文件特别多的时候,编译速度又慢下来,能不能按需编译,增量编译?
make 是通过 makefile 文件来描述源程序之间的相互关系并自动维护编译工作的。
例子就不举了,写过 c 的人多少都知道点。
其实编译在每个语言世界里,都是痛,骨子里的风湿一样,于是产生了 make 类似的东西:
比如 java 里的 ant,c# 里的 NAn;
比如 ruby 里 rake;
比如 coffeescript 里有 cake。
它们的共同特点:
基于 task,定义简单;
task 有串行,并行,作业依赖等控制方式;
通过 xxxfile 来定义 task。
如此看来,nodejs 的构建系统也应该是这样的,可以说gulp是node世界里和上面几个构建工具最像的一个,它们太像了,以至于学习起来特别简单。
其实上面还提了一个 streaming,是流式的意思,后面讲原理的时候会深入讲解。
- 入门指南
2.1. 安装 Gulp
建议全局安装:
$ npm install —global gulp
但最多的是作为项目的开发依赖(devDependencies)安装:
$ npm install —save-dev gulp
2.2. 创建gulpfile
在项目根目录下创建一个名为 gulpfile.js
的文件:
gulpfile 和 makefile、build.xml 等是类似的,定义 task 的地方。
定义好 task,下面运行一下。
2.3. 运行 Gulp
$ gulp
默认的名为 default 的任务(task)将会被运行,在这里,这个任务并未做任何事情。
想要单独执行特定的任务(task),请输入:
gulp <task> <othertask>
是不是很简单?
- Gulp vs Grunt
下面比较一下 Grunt 和 Gulp:
Gulp 和 Grunt 类似。但相比于 Grunt 的频繁的 IO 操作,Gulp 的流操作,能更快地完成构建。
Grunt 是基 于dsl 声明式写法,出来的早,但写起来比较恶心,可读性非常差。
nodejs 里 sails 的因为基于 Grunt 被鄙视,ionic 里也是,好在 ionic 现在也是 Gulp 的。
鄙视完 Grunt,就该吹吹 Gulp 了。
话说 2014 年 Gulp 火了之后,很快风头就盖过了 Grunt,好不好,试了才知道。 - why Gulp?
前端本身就对 js 非常了解,Gulp 可以说是 plain js,语法都再简单不过,所以大家比较容易接受。
4.1. 基于nodejs
基于 nodejs 的小而美哲学,写 gulp task 是件很愉快的事儿。
而且还可以结合 nodejs 世界里最好的包管理器 npm,写 cli 或者模块特别简单。
而且可以写 c/c++ addon,给你无限可能。
而且还有 shelljs 这样的库,结合 shell,给你无限可能。
4.2. 易于使用
通过代码优于配置的策略,Gulp 让简单的任务简单,复杂的任务可管理。
而且对 ant,rake,make 等了解的人非常容易上手。
4.3. 构建快速
利用 Node.js stream 的威力,你可以快速构建项目并减少频繁的 IO 操作。
而且 Gulp 的核心代码可以高效利用你所有核的 cpu,高效运行,尤其是大文件处理,后面会讲到。
4.4. 插件机制
Gulp 严格的插件指南确保插件如你期望的那样简洁高质得工作。
4.5. 易于学习
通过最少的 API,掌握 Gulp 毫不费力。
这得益于 Gulp 的设计,api 简单,调用关系和依赖清晰,当然如果理解 linux 管道或者其他类似的构建工具会更容易。
而且每个任务都拆分成一个 task,结构清晰,利用 stream 和 pipe 写法,组成 task,也是非常容易学习的。
4.6. 总结
这里说了 Gulp 的各种好处,它通用,高效,使用简单,学习成本低,总之,我就是要告诉你:
它是非常好的构建工具,而且是长久看好的。
它也有缺点,这样说也不太合适,更准确的说,它是一个通用构建工具,所以在构建实践上,需要写的人有好的实践。
像 fib3 这样的,只是基于比较好的构建实践而已,其他方面是不能和 Gulp 相提并论的。 -
Gulp快速入门
5.1. hello world
都说得来个 hello world。
创建 gulpfile.js:
然后需执行命令:
gulp
定义作业就是这么简单:
default 是名字
后面的匿名函数是它的实现
5.2 自定义作业
将作业名称自定义为 stuq:
然后运行:
gulp stuq
这里的 stuq 是作业名称。
5.3. 依赖作业
这里的 task 有 3 个参数。
default 是方法名称,只有 default 比较奇怪,会默认调用。
相当于 c 里的 main 方法。
[‘watch’] 这是依赖的作业列表,它们是由顺序的,按数组顺序依次执行。
第三个参数是成功执行完上面的依赖作业后执行的回调函数。
这里要强调,依赖作业数组里的都执行完了,才会执行第三个参数,即当前作业具体内容。
我们不妨改一下,看看多个依赖如何定义:
是不是很简单?放心,这只是入门,下面看一下流式处理。
5.4. 流式处理
比如混淆压缩 js,使用 gulp-uglify 插件:
src 是输入
dest 是输出
pipe 是管道的意思,也是 stream 里核心概念,也就是说:上一个的输出,是下一个的输入。
src 里所有 js,经过处理 1,处理 2,然后压缩变成 min.js,中间的处理 pipe 可以 1 步,也可以是 n 步。
反正第一步处理的结果是第二步的输入,以此类推,就像生产线一样,每一步都是一个 task 是不是很好理解呢?
每个独立操作单元都是一个 task,使用 pipe 来组装 tasks。
于是 Gulp 就变成了基于 task 的积木式的工具。
5.5. 总结
好吧,是时候总结一下了:
每一个作业都是独立,定义为 task;
通过 stream 的机制,上一个的输出,作为下一个的输入;
通过 pipe 方法组装 task,完成我们想要的业务逻辑。
至此,task 相关的概念都讲完了,你已经会使用 Gulp 了,找几个插件就足以工作了。
如果想高级玩法,可以自己写插件,可以尝试 Gulp 4 的一些 API,比如并行等。。。
下面看以项目实战。
- 项目实践
以目前最火的微信前端组的 WeUI 项目为例,看看gulp是如何被使用的(其实是如何使用 Gulp 构建前端项目)。
先说一下 WeUI 是一个什么项目:
WeUI 为微信 Web 服务量身设计的h5框架。
WeUI 是一套同微信原生视觉体验一致的基础样式库,为微信 Web 开发量身设计,可以令用户的使用感知更加统一。包含 button、cell、dialog、toast、article、icon 等各式元素。
它是使用 less 编写,最终编译成 css,压缩成 weui.min.css 的,当然这里面还使用了一下比较好的开发实践:
web server
livereload
watch 实时监控
less:css 预处理器
minify 压缩
sourcemap 生成
等等~~
下面我们来扒光它。
6.1. 准备工作
首先看目录结构。
就几个目录:
有 package.json,说明它是一个 node 模块或者说它依赖了 node 模块
有 gulpfile.js,说明它是使用 Gulp 作为构建工具的。
还有 src 和 dist 目录,一般 src 是源码,dist 是构建后生成的目录,至此,我们似乎明白了点什么。
首先 clone 代码:
git clone https://github.com/weui/weui.git
上面说了,有 package.json,此时需要安装依赖模块:
npm install
至此就准备玩了,下面看一下 gulpfile.js。
6.2. 看一下它有哪些 tasks
看一下它有哪些 tasks,查看命令是: gulp -T
看这个的目的,其实就为了了解当前 gulpfile 里 tasks,以便让大家有一个概况了解。
大概有 7 个 task,其中 styles 和 release 是有依赖作业的。
也就是说,整个项目目前的 task 比较少,比较适合讲解,而且是腾讯公司的项目,大家应该会比较认可一些。
ok,下面我们分别看一下每个 task。
上面讲过,所有的 task 定义在 gulpfile.js 里,那么我们就结合源码,看一下 gulpfile.js 的 task 是如何定义,以及如何应用的。
6.3. default
这是默认作业,也就是在根目录,执行:
gulp
相当于:
gulp default
其实 make,rake,ant 等都有类似约定。
这里面值得说明的是:
这是最简单的 task 定义,无任何依赖作业;
作业里面使用 nodejs 写的 yargs 模块,用户处理 cli 参数。
比如此时可以执行 gulp -s
后者 gulp default -s
然后它就会打开网页,跳转到 http://localhost:8080/example/
从task定义里可知:
if (yargs.s) { gulp.start(‘server’); }
说明 server 是一个 task,这里的 start 就相当于 call 或者 invoke 某个 task 的方法。
注:yargs 是一个 nodejs 模块,目前最好的解析 cli 参数的库。
当然,如果这样,是不是太简单了呢?而且亲,你还有 2 个参数没说呢。
是的
-w: 实时监听;
-p: 服务器启动端口,默认8080。
这里要说的就是一个开发惯例,
-p
很好理解,就是 httpserver 的端口 port,如果指定是 7070 那就是 7070,如果没指定就是 8080。给程序员自己定制的空间,谁还没有个端口自定义权利呢?
-w
比较特殊,这里的 w 是 watch 的意思,就是监控某个文件或目录,只要有变化就触发 xx 动作,一般用于编译,比如 coffee,typescript,less,sass 等。
看一下定义:
if (yargs.w) { gulp.start(‘release’); gulp.start(‘watch’); }
这里的意思的如果有 w 参数,就先调 release task,然后 watch 作业。
这里牵连出 3 个 task,有 server,watch,release,容我慢慢道来。
btw:这里的 if (yargs.w) {
怎么看逻辑都怪怪的,既然有 无w 都执行 release task,这样写法还是有待商榷的。
6.4. server
server task 一看就知道是启动服务器,一般前端开发,都是起一个服务器在浏览器里测试。
所以还是比较容易理解。
看代码:
代码里的几个关键词:
browserSync
server
port
startPath
weinre
browserSync 是一个 nodejs 模块,专门做的是 livereload 的事儿,也就是说,我们在编辑器里写代码,保存了,文件就会变动,文件变动了就会触发操作系统的监控事件,这时让浏览器刷新。
于是,代码变了,不用刷新浏览器就能看到效果。。。
这其实就是传说中得 livereload…
又可以偷懒了,祭奠 f5 吧!!!
其他(server,port,startPath)是 browserSync 的配置项,有兴趣自己扒文件吧。
这里稍稍提一下 weinre,因为 WeUI 这个项目是移动端 HTML5 页面,所以才会用到 weinre 调试的,是远程调试 HTML5 的利器
总结一下。
整个 server 就是 browserSync 提供的 3 个功能:
起了一个 server
支持 livereload
自动打开网页
还不错吧,下面看一下更实用的一个 task: watch 监控
6.5. wat
watch 其实就干了 2 件事儿:
如果 ‘src/*/.less’ 变动,执行 styles task;
如果 ‘src/example/*/.{html,js}’ 变动,先执行 ‘source’ task,然后livereload通知浏览器。
大家伙只要了解文件变动能干坏事即可,其他可自由发挥。
如果 Gulp 内置的 watch 无法满足,你还可以使用 gulp-watch 这个单独模块,哈哈,如果有兴趣还可以研究一下操作系统底层监控文件变动接口,有点意思。
6.6. release
release 是发布最终 css 的 task:
gulp.task(‘release’, [‘styles’]);
release 只是依赖 styles task,相当于 styles 的别名。
值得说明的是,WeUI 是 less 写的,需要编译成 css,然后最终发布的是 css 文件。
那么,
如果 js 是用 coffeescript,typescript 写的呢?
如果 css 是用 less,sass,stylus 写的呢?
其实都是一样的思路,编译成 js 或 css,然后发布
这些预处理器,让开发方便,高效的同时,也增加了前端的复杂度,真是老子那句话:
福兮祸所伏,祸兮福所倚…
阿门。。。阿弥托佛。。。
下面一个 source task。
6.7. source
上面的都比较简单,只是作业定义和作业依赖定义而已,下面看一下真实的流式处理:
回故一下,上面讲的流式内容:
src是输入
dest是输出
pipe是管道的意思,也是stream里核心概念,也就是说上一个的输出,是下一个的输入。
这代码里的 src 里的,所有不是 less 的文件,都丢到 dist 目录:
简单吧?
然后,它又 pipe 一个,仅仅是为了表示顺序,无上下文件传递关系(偷懒做法而已,不可取)。
这样写起来是不是非常简单?
我知道你会回答是,下面我们来讲个不简单的。
6.8. styles
下面是关于样式处理的 task:
这是整个 gulpfile 里最长的一个 task。
下面拆成 2 部分分析一下:
依赖 source,执行完 source,然后编译 less
编译的 less 有例子和具体要发布的 weui.css
part1
[图片上传中。。。(15)]
less() 来编译less文件,src 和 dest 大家要看清楚
最后 pipe 了一个 livereload 触发
和上面的 source task 类似,只有 less 编译不一样,这里就不详细讲解了。
下面看一下 part2
src是src/style/weui.less
sourcemaps.init()是初始化sourcemap
less编译
.pipe(sourcemaps.write())是写入sourcemap
.pipe(autoprefixer())自动增加前缀
.pipe(gulp.dest(dist)) 输出到dist目录
.pipe(minify()) 是压缩
.pipe(rename(function (path) 重命名,因为文件后面要加min
.pipe(gulp.dest(dist)) 压缩后的文件进行保存
.pipe(browserSync.reload({stream: true}));是livereload触发
整体是分了 3 个阶段:
编译 less 和生成 sourcemap
压缩 minify
触发 livereload
6.9. 实战总结
至此,我们就讲完了所有 gulpfile 里的内容,以及每个 task 的细节。
结论是:这是一个比较典型的 Gulp 项目,还不错。
当然它也不是非常完美,比如作业依赖可以优化、代码校验检测、release 没有 reversion 处理等。
6.10. package.json
下面简单看一下 package.json。
这里有 2 个
是 gulpfile 里引用的模块:
“scripts”: { “test”: “gulp release” },
执行 npm test 即可发布最终 css。
有人说 Gulp 只是一些命令,没啥技术含量,大家先笑笑,一会我们就来讲讲。
下面看一下 Gulp 的核心原理:nodejs stream
- 核心:stream
官方文档解释
A stream is an abstract interface implemented by various objects in Node.js. For example a request to an HTTP server is a stream, as is stdout. Streams are readable, writable, or both. All streams are instances of EventEmitter
Stream 是 nodejs 各种对象实现的抽象接口。比如一个 http server 的请求是一个 stream,stdout 也是一个。Streams 可读、可写,或者兼有的。所有的 stream 对象都是 EventEmitter 的实例。
好理解么?
不好理解,还是从流式理解吧~
7.1. 什么是流式
上一个的输出,是下一个的输入
上一个的输出,是下一个的输入
上一个的输出,是下一个的输入
7.2. linux pipe
流式和 linux pipe 是一样的(也可能是最早的起源)。
举例:
ps -ef|grep boot|awk ‘{print $2}’|xargs kill -9
ps -ef 查看进程
grep boot 是过率进程里的和boot相关的所有进程
awk ‘{print $2}’ 取出进程号
xargs kill -9 杀掉该进程