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. 【深度学习系列】用PaddlePaddle和Tensorflow实现GoogLeNet InceptionV2/V3/V4

    上一篇文章我们引出了GoogLeNet InceptionV1的网络结构,这篇文章中我们会详细讲到Inception V2/V3/V4的发展历程以及它们的网络结构和亮点. GoogLeNet Ince ...

  2. mysql版本升级

    环境 mysql安装在centos上,需要升级. mysql的版本是 mysql> select version(); +-----------+ | version() | +-------- ...

  3. JDBC结果集rs.next()注意事项

    写在前面: 用JDBC从数据库中查询数据要用到结果集ResultSet,其中我们在获取结果的时候经常用到rs.next()方法来判断是否查询到了数据. 但是要特别注意,next()方法用一次,游标就往 ...

  4. sqlserver 存储过程 增加

    CREATE PROCEDURE [dbo].[InsertMessage]( @strTable varchar(), --表名 @strValues nvarchar(), --要插入的数据(用英 ...

  5. iOS 获取当前应用的信息以及用户信息:版本号手机号手机型号

    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; CFShow(infoDictionary); // ap ...

  6. Smart line Panel和S7-200的MPI通信

    1.系统组成 2.一个简单任务 3.设置S7-200的通信参数 1)新建工程,设置CPU类型 2)设置端口1的通讯参数PLC地址为2,波特率187.5kbps 组态 3)保存完成配置 4.组态Smar ...

  7. 学习时用的软件最新 开发环境为Visual Studio 2010,数据库为SQLServer2005,使用.net 4.0开发。 超市管理系统

    一.源码特点 1.采用典型的三层架构进行开发.模板分离,支持生成静态 伪静态..购物车.登陆验证.div+css.js等技术二.功能介绍 1.本源码是一个超市在线购物商城源码,该网上商城是给超市便利店 ...

  8. windows 命令行打开浏览器

    在命令行打开百度 start chrome www.baidu.com

  9. linux防火墙之 ufw

    Usage: ufw COMMAND Commands: enable enables the firewall 开启ufw防火墙 disable disables the firewall 禁用防火 ...

  10. Apache Avro# 1.8.2 Specification (Avro 1.8.2规范)一

    h4 { text-indent: 0.71cm; margin-top: 0.49cm; margin-bottom: 0.51cm; direction: ltr; color: #000000; ...