gulp的关键在于流,这从它的logo就能看出来。

在node中,流是操作文件时一个重要的概念。流是指什么呢?它包含两个含义:“水流”和“流水”。 水流蕴含了源源不断或是一股一股那样流过的意味;而流水是“流水线”或是“流水作业”里那种让物件通过各个环节依次对其加工的意思。 我们经常接触到的“流媒体”主要是前者的含义,当你在线看一部电影时,影音数据从服务器源源不断地流入你的播放器, 再经过一些处理展现在你眼前;而gulp中的流我觉得含义偏重于后者,因为gulp的任务就是把源文件进行各种加工处理最终输出到指定位置。 我们说“源文件”而不是“原文件”,在gulp中,它还真是流的源头。

gulp是基于node的,但是它并没有直接使用node中fs模块里的文件系统和流,而是包装了一层vinyl。 vinyl是一个用来描述文件的简单的数据格式,通过vinyl-fs可以把node原生的文件系统封装成vinyl。 这个封装使得整个流的过程更加简单。从源头上,vinyl使用glob语法获取源,比如通过一个表达式 "src/**/*.js"就获得到了src目录下各级目录中的js文件,这要是用原生的fs恐怕得写个遍历树的算法程序了吧。 在gulp或vinyl-fs的api里,通过一个传入glob表达式的src方法就获得到了一个流的源。 很明显,在多数情况下这个源是由多个文件组成的,可以想象成这些文件构成了一个一股一股的文件流, 都将要通过一系列管道被加工处理。那么接下来就是管道,与原生的fs相同,vinyl使用管道也是用pipe方法。 pipe接受一个函数为参数,将当前流的内容传给这个函数让其加工,vinyl把流的内容封装得更加简明好用, 而且,对于调用一次pipe方法,其传入的函数会对这个流的所有文件作用,换句话说,传入pipe方法的函数实际上是针对一个文件的, 而流中所有的文件都会被这个函数加工一下。这么看,vinyl的流有些并行的感觉,但本质上说javascript是单线程的, 加工的过程还是一个接着一个进行的,所以说成让文件一个接一个地流过某个管道更确切。

既然是流,就应该有一种顺序进行的感觉。不过处理流的代码是异步的,比如下面的代码:

gulp = require('gulp')
through = require('through2') gulp.task 'test', ->
stream = gulp.src('src/js/*.js')
.pipe through.obj (file, enc, cb) ->
console.log 'processing...'
cb null, file
.pipe(gulp.dest('test'))
console.log 'end'
through.obj会创建一个让每个文件都通过的通道。

如果在src/js目录下有两个js文件。执行gulp task,结果是:

[20:12:32] Starting 'test'...
end
[20:12:32] Finished 'test' after 13 ms
processing...
processing...

很显然,pipe中的函数是异步执行的。不过对于流中的一个文件,各pipe中的函数一定会按照先后顺序执行。 再来看一段代码,为了方便,我把管道中的处理函数写成一般gulp插件的形式:

processor = (info) ->
through.obj (file, enc, cb) ->
console.log file.path, info
cb null, file gulp.task 'test', ->
stream = gulp.src('src/js/*.js')
.pipe processor("in pipe 1")
.pipe processor("in pipe 2")
.pipe processor("in pipe 3")
.pipe(gulp.dest('test'))
console.log 'end'

执行结果是:

[16:31:24] Starting 'test'...
end
[16:31:24] Finished 'test' after 9.02 ms
/src/js/city.js in pipe 1
/src/js/city.js in pipe 2
/src/js/city.js in pipe 3
/src/js/sysUtils.js in pipe 1
/src/js/sysUtils.js in pipe 2
/src/js/sysUtils.js in pipe 3

执行结果的确是像流那样一个文件挨着一个文件,一个过程接着一个过程处理完成的。尽管pipe中的函数会异步执行, 但它们严格按照先注册先执行的顺序进行。看来一个任务的执行顺序在一个流中是能够得以保证的,而且也只能在一个流中得以保证。

那么对于多个任务的情况呢?gulp.task方法可以接受一个任务数组,任务数组中的任务将会并行执行。 而传入gulp.task的函数将会在任务数组中所有任务执行完毕后开始执行。简单来说是这样的,实际要小心。

gulp的api中关于task方法有这么一项注意:“Are your tasks running before the dependencies are complete? Make sure your dependency tasks are correctly using the async run hints: take in a callback or return a promise or event stream.” 中文版本是:“你的任务是否在这些前置依赖的任务完成之前运行了?请一定要确保你所依赖的任务列表中的任务都使用了正确的异步执行方式:使用一个 callback,或者返回一个 promise 或 stream。”

既然任务中的那些处理函数一般都是异步执行的,那么怎么才能知道它们执行完了呢?只能是通过回调了, 可以是直接的回调,也可以是其它约定好的回调,也就是promise或者流的事件。 对于JavaScript异步编程来说这是很常见的事情,然而这也带来了相应的局限性。来看个例子:

gulp.task 'buildjs', (cb) ->
del.sync('prd/js')
gulp.src('src/js/**/*.js')
.pipe uglify({output:{ascii_only:true}})
.pipe gulp.dest('prd/js')
gulp.src(['src/js/**/*.*', '!src/js/**/*.js'])
.pipe gulp.dest('prd/js')

这是一个很常见的任务,就是把js代码混淆压缩,然后把非js代码原样拷贝出去。我写的是coffee版的gulpfile, coffeescript会默认把最后一个表达式作为返回值,所以这里实际上是返回了第二个流,也就是拷贝非js文件的那个流。 如果只执行这个任务倒无所谓,谁先谁后都能完成,但是如果它被作为前置任务呢?

gulp.task zip, ['buildjs'], ->
gulp.src('prd/js/*.js')
.pipe(zip('release.zip'))
.pipe(gulp.dest('prd'))

如果文件比较多的话,会发现压缩包里的文件不完整。原因就是buildjs这个任务返回的流是拷贝文件那个流, 而zip这个任务也只会等待拷贝文件完成时开始,此时混淆文件那个流还不一定能执行完。如果返回混淆文件那个流, 照常理说这个流会执行的慢一些,但仍不那么靠谱,毕竟没有逻辑保障,所以应该把它们都拆开,分别作为zip的前置任务:

gulp.task 'buildjs', ->
gulp.src('src/js/**/*.js')
.pipe uglify({output:{ascii_only:true}})
.pipe gulp.dest('prd/js') gulp.task 'copy', ->
gulp.src(['src/js/**/*.*', '!src/js/**/*.js'])
.pipe gulp.dest('prd/js') gulp.task 'zip', ['buildjs', 'copy'], ->
gulp.src('prd/js/*.js')
.pipe(zip('release.zip'))
.pipe(gulp.dest('prd'))

这样,zip一定会等buildjs和copy两个任务中各自的流全都执行完才会开始执行,从逻辑上也没问题了。 这样看好像是把本来可以在一个任务里完成的东西拆开了,不过gulp本身鼓励短小专一,所有的gulp插件都很小, 且只完成一件事情。这么说的话构建js文件和拷贝非js文件说是两件事也比较合理。

上例是一个两级的顺序保障,“分-总”的结构。你也许发现我偷偷地把删除目录的一句给去掉了。因为我的确不知道该把它放在哪里好。 buildjs和copy是并行的,不能确保谁先,如果放到buildjs里,万一copy先执行了,误删了已经拷贝过去的东西可不好。 所以,我需要多级的顺序保障,把删除目录的任务放在更高的一级,形成一个“总-分-总的结构”。 然而我并没找到形成这种结构的方法,貌似只能一个接着一个地进行:

gulp.task 'clean', ->
del.sync('prd/js') gulp.task 'copy', ['clean'], ->
gulp.src(['src/js/**/*.*', '!src/js/**/*.js'])
.pipe gulp.dest('prd/js') gulp.task 'buildjs', ['copy'], ->
gulp.src('src/js/**/*.js')
.pipe uglify({output:{ascii_only:true}})
.pipe gulp.dest('prd/js') gulp.task 'zip', ['buildjs'], ->
gulp.src('prd/js/*.js')
.pipe(zip('release.zip'))
.pipe(gulp.dest('prd'))

这样顺序是没啥问题了,就是看着挺别扭的,我需要一个构建js文件夹里面内容的任务,却需要一层又一层地依赖多个任务。 还有一种办法可以把所有步骤一股脑地放在一个任务里,就是利用流的事件,vinyl的流和原生fs流其实基本一样, 也有那些事件,所以可以利用end事件来控制顺序。我认为上面的copy和buildjs两个任务不应当拆开,就把他们写在一起:

gulp.task 'copy', ['clean'], (cb) ->
lastStream = null
gulp.src(['src/js/**/*.*', '!src/js/**/*.js'])
.pipe gulp.dest('prd/js').
.on 'end', ->
lastStream = gulp.src('src/js/**/*.js')
.pipe uglify({output:{ascii_only:true}})
.pipe gulp.dest('prd/js')
.on 'end', cb

要注意的是,下一个任务需要等待这个任务最有一个流执行完再开始,所以这里需要在最后执行的流上加上对end事件的处理,执行参数传入的回调。

gulp的流与执行顺序的更多相关文章

  1. javascript运行机制之执行顺序详解(转)

    转自http://www.admin10000.com/document/3385.html JavaScript是一种描述型脚本语言,它不同于java或C#等编译性语言,它不需要进行编译成中间语言, ...

  2. javascript 执行顺序详解

    JavaScript是一种描述 型脚本语言,它不同于java或C#等编译性语言,它不需要进行编译成中间语言,而是由浏览器进行动态地解析与执行.如果你不能理解javaScript 语言的运行机制,或者简 ...

  3. JavaScript 运行机制之执行顺序详解

    JavaScript是一种描述型脚本语言,它不同于 Java 或 C# 等编译性语言,它不需要进行编译成中间语言,而是由浏览器进行动态地解析与执行.如果你不能理解 JavaScript 语言的运行机制 ...

  4. JavaScript在页面中的执行顺序(理解声明式函数与赋值式函数) 转载

    JavaScript在页面中的执行顺序 https://blog.csdn.net/superhoy/article/details/52946277 2016年10月27日 15:38:52 阅读数 ...

  5. JS中函数执行顺序的问题?

    作者:知乎用户链接:https://www.zhihu.com/question/23564807/answer/82996422来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...

  6. Oracle sql语句执行顺序

    sql语法的分析是从右到左 一.sql语句的执行步骤: 1)词法分析,词法分析阶段是编译过程的第一个阶段.这个阶段的任务是从左到右一个字符一个字符地读入源程序,即对构成源程序的字符流进行扫描然后根据构 ...

  7. javascript中的事件冒泡、事件捕获和事件执行顺序

    谈起JavaScript的 事件,事件冒泡.事件捕获.阻止默认事件这三个话题,无论是面试还是在平时的工作中,都很难避免. DOM事件标准定义了两种事件流,这两种事件流有着显著的不同并且可能对你的应用有 ...

  8. 运行page页面时的事件执行顺序

    using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Secu ...

  9. 通过实验窥探javascript的解析执行顺序

    简介 javascript是一种解释型语言,它的执行是自上而下的.但是各浏览器对于[自上而下]的理解是有细微差别的,而代码的上下游也就是程序流对于程序正确运行又是至关重要的.所以我们有必要深入理解js ...

随机推荐

  1. React学习总结(一)

    React学习总结 一.什么是React? 是Facebook公司开发的一套JS库 React的详细介绍https://www.jianshu.com/p/ae482813b791 二.老版本Reac ...

  2. Struts2学习---result结果集

    这一章节主要介绍如何配置结果集,分为以下几个知识点: 结果集类型(result type) 全局结果集(global types) 动态结果集(dynamic type) 带有参数的结果集(type ...

  3. sed使用范例

    本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/32 记录下sed编译器的常见使用方法. sed编辑器基于输入到命 ...

  4. 大赛获奖选手专访 | 冷燕冰:最佳设计奖TIMING里的时机和时序

    Mockplus三周年原型设计大赛,从筹备到11月21日完美落幕,50余天的时光,已成为过去.这场近千人参赛的原型设计大赛,我想,无论是于主办方,于参赛选手,于专家评委,还是于每一个关注和参与的人,都 ...

  5. ELK开机启动 service文件内容

    为了实现ELK的3部分开机启动,可以添加各项服务对应的service文件,再通过systemctl enable XXX实现ELK所有服务开机启动. Elasticsearch elasticsear ...

  6. NodeJS初介

    之前很多环境搭建中都使用到了Nodejs,所以这边对Nodejs做一个简单总结. 1.什么是Nodejs Node.js是一个Javascript运行环境(runtime),发布于2009年5月,由R ...

  7. COMPUTE子句和Group By

                        首先声明一下,这个COMPUTE语法在SQLServer 2012之后,就废弃使用了,详情请看https://msdn.microsoft.com/librar ...

  8. 教你如何安装配置Windows7系统 IIS IIS7.5本地浏览测试网站 完整版介绍

    大家都知道网站建设前期测试于浏览网站都喜欢用iis本地浏览来操作 那么为了方便大家自己来安装和配置Internet信息服务 相信大家,对于Windows 7有了相应的了解,从操作上,使用上,内置功能上 ...

  9. SpringMVC 如何在页面中获取到ModelAndView绑定的值

    springMVC中通过ModelAndView进行后台与页面的数据交互,那么如何在页面中获取ModelAndView绑定的值呢? 1.在JSP中通过EL表达式进行获取(比较常用) 后台:ModelA ...

  10. 这是要逆天么,看我控制台程序玩Microsoft XPS Document 打印

    主要是想试试Microsoft XPS Document 打印时怎样去掉那个“将打印输出另存为”对话框 using System; using System.Drawing; using System ...