gulp的流与执行顺序
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'
如果在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的流与执行顺序的更多相关文章
- javascript运行机制之执行顺序详解(转)
转自http://www.admin10000.com/document/3385.html JavaScript是一种描述型脚本语言,它不同于java或C#等编译性语言,它不需要进行编译成中间语言, ...
- javascript 执行顺序详解
JavaScript是一种描述 型脚本语言,它不同于java或C#等编译性语言,它不需要进行编译成中间语言,而是由浏览器进行动态地解析与执行.如果你不能理解javaScript 语言的运行机制,或者简 ...
- JavaScript 运行机制之执行顺序详解
JavaScript是一种描述型脚本语言,它不同于 Java 或 C# 等编译性语言,它不需要进行编译成中间语言,而是由浏览器进行动态地解析与执行.如果你不能理解 JavaScript 语言的运行机制 ...
- JavaScript在页面中的执行顺序(理解声明式函数与赋值式函数) 转载
JavaScript在页面中的执行顺序 https://blog.csdn.net/superhoy/article/details/52946277 2016年10月27日 15:38:52 阅读数 ...
- JS中函数执行顺序的问题?
作者:知乎用户链接:https://www.zhihu.com/question/23564807/answer/82996422来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...
- Oracle sql语句执行顺序
sql语法的分析是从右到左 一.sql语句的执行步骤: 1)词法分析,词法分析阶段是编译过程的第一个阶段.这个阶段的任务是从左到右一个字符一个字符地读入源程序,即对构成源程序的字符流进行扫描然后根据构 ...
- javascript中的事件冒泡、事件捕获和事件执行顺序
谈起JavaScript的 事件,事件冒泡.事件捕获.阻止默认事件这三个话题,无论是面试还是在平时的工作中,都很难避免. DOM事件标准定义了两种事件流,这两种事件流有着显著的不同并且可能对你的应用有 ...
- 运行page页面时的事件执行顺序
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Secu ...
- 通过实验窥探javascript的解析执行顺序
简介 javascript是一种解释型语言,它的执行是自上而下的.但是各浏览器对于[自上而下]的理解是有细微差别的,而代码的上下游也就是程序流对于程序正确运行又是至关重要的.所以我们有必要深入理解js ...
随机推荐
- 云储存第三方--阿里云OSS VS 又拍云USS
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helvetica Neue"; color: #454545 } p. ...
- n年前,我没钱但年轻,我怕n年后我老时,还是一无所成——2017我的收获和反思
记得当年我刚从学校里出来时,应该和现在的95后差不多,当时还是很惶恐的,怕找不到工作,怕无法挣到足够的钱买房子支撑家庭,(当然还有其它的担心点),却唯独没意识到自己拥有着最宝贵的财富:年轻. 年轻意味 ...
- iView的使用【小白向】
首先看这篇:构建Vue本地开发环境(现阶段还不知道怎么用CDN的方式做...) 安装iView(WindowsPowershell或cmd下用cnpm) 编辑上一篇博客创建的Vue工程 先到main. ...
- 【WebGL】《WebGL编程指南》读书笔记——第5章
一.前言 终于到了第五章了,貌似开始越来越复杂了. 二.正文 Example1:使用一个缓冲区去赋值多个顶点数据(包含坐标及点大小) function initVerte ...
- crm踩坑记(三)
React 如何同步更新state 由于setState方法是异步的,而通常很多时候在一个生命周期里更新state后需要在另一个生命周期里使用这个state. 下面介绍几个方法 // 1 this.s ...
- SSH远程登录密码尝试
import threading #创建一个登陆日志,记录登陆信息 paramiko.util.log_to_file('paramiko.log') client = paramiko.SSHCli ...
- 如何使用vuex
一.何为vuex? vuex其实是一种状态管理模式,那么什么是状态管理模式呢?简单的理解就是把一些状态或者数据集中到一个地方管理,然后所有的组件共享这些数据,当数据改变时,用到这些数据的组件也会相应的 ...
- Golang 网络爬虫框架gocolly/colly 一
Golang 网络爬虫框架gocolly/colly 一 gocolly是用go实现的网络爬虫框架,目前在github上具有3400+星,名列go版爬虫程序榜首.gocolly快速优雅,在单核上每秒可 ...
- Regular expressions in lexing and parsing(翻译)
词法分析和语法分析中的正则表达式 (英文原文来自rob pike 的博客 https://commandcenter.blogspot.jp/2011/08/regular-expressions-i ...
- css3特效样式库
直接调用样式类即可: /* animation */ .a-bounce,.a-flip,.a-flash,.a-shake,.a-swing,.a-wobble,.a-ring{-webkit-an ...