第一个gulp程序
说起来惭愧,一直用公司内部的工具,没有用这些红得发紫的东西。今天东抄西拼终于搞出第一个gulp应用。gulp是做什么的,好处在哪儿我不废话了。直入主题吧。
先在D盘下建立一个xxxx目录,然后打开控制台,直接将npm install gulp。 里面多出一个node_modules目录安装成功。
然后xxxx目录下面建一个src目录,里面建一个index.html文件,内容如下或你自己乱写一点东西,我们这个例子主要测试压缩html。
<! DOCTYPE html> < html > < head > < title >TODO supply a title</ title > < meta charset="UTF-8"> < meta name="viewport" content="width=device-width"> </ head > < body > < h2 >写在前面</ h2 > < p >本来是想写个如何编写gulp插件的科普文的,突然探究欲又发作了,于是就有了这篇东西。。。翻了下源码看了下< code >gulp.src()</ code >的实现,不禁由衷感慨:肿么这么复杂。。。</ p > < h2 >进入正题</ h2 > < p >首先我们看下< code >gulpfile</ code >里面的内容是长什么样子的,很有express中间件的味道是不是~< br />我们知道< code >.pipe()</ code >是典型的流式操作的API。很自然的,我们会想到< code >gulp.src()</ code >这个API返回的应该是个Stream对象(也许经过层层封装)。本着一探究竟的目的,花了点时间把gulp的源码大致扫了下,终于找到了答案。</ p > < p >gulpfile.js</ p > < pre class="hljs-dark">< code class="hljs javascript">< span class="hljs-keyword">var gulp = < span class="hljs-built_in">require(< span class="hljs-string">'gulp'), preprocess = < span class="hljs-built_in">require(< span class="hljs-string">'gulp-preprocess'); gulp.task(< span class="hljs-string">'default', < span class="hljs-function">< span class="hljs-keyword">function< span class="hljs-params">() { gulp.src(< span class="hljs-string">'src/index.html') .pipe(preprocess({USERNAME:< span class="hljs-string">'程序猿小卡'})) .pipe(gulp.dest(< span class="hljs-string">'dest/')); }); </ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ code ></ pre > < h2 >提前剧透</ h2 > < p >此处有内容剧透,如有对剧透不适者,请自行跳过本段落。。。</ p > < blockquote > < p >gulp.src() 的确返回了定制化的Stream对象。可以在github上搜索< code >ordered-read-streams</ code >这个项目。</ p > < p >大致关系是:< br />ordered-read-streams --> glob-stream --> vinyl-fs --> gulp.src()</ p > </ blockquote > < h2 >探究之路</ h2 > < p >首先,我们看下< code >require('gulp')</ code >返回了什么。从gulp的源码来看,返回了< code >Gulp</ code >对象,该对象上有< code >src</ code >、< code >pipe</ code >、< code >dest</ code >等方法。很好,找到了我们想要的< code >src</ code >方法。接着往下看< br />参考:< a href="https://github.com/gulpjs/gulp/blob/master/index.js#L62" target="_blank">https://github.com/gulpjs/gulp/blob/master/index.js#L62</ a ></ p > < p >gulp/index.js</ p > < pre class="hljs-dark">< code class="hljs js">< span class="hljs-keyword">var inst = < span class="hljs-keyword">new Gulp(); < span class="hljs-built_in">module.exports = inst; </ span ></ span ></ span ></ code ></ pre > < p >从下面的代码可以看到,< code >gulp.src</ code >方法,实际上是< code >vfs.src</ code >。继续< br />参考:< a href="https://github.com/gulpjs/gulp/blob/master/index.js#L25" target="_blank">https://github.com/gulpjs/gulp/blob/master/index.js#L25</ a ></ p > < p >gulp/index.js</ p > < pre class="hljs-dark">< code class="hljs js">< span class="hljs-keyword">var vfs = < span class="hljs-built_in">require(< span class="hljs-string">'vinyl-fs'); < span class="hljs-comment">// 省略很多行代码 Gulp.prototype.src = vfs.src; </ span ></ span ></ span ></ span ></ code ></ pre > < p >接下来我们看下< code >vfs.src</ code >这个方法。从< code >vinyl-fs/index.js</ code >可以看到,< code >vfs.src</ code >实际是< code >vinyl-fs/lib/src/index.js</ code >。< br />参考:< a href="https://github.com/wearefractal/vinyl-fs/blob/master/index.js" target="_blank">https://github.com/wearefractal/vinyl-fs/blob/master/index.js</ a ></ p > < p >vinyl-fs/index.js</ p > < pre class="hljs-dark">< code class="hljs js">< span class="hljs-pi">'use strict'; < span class="hljs-built_in">module.exports = { src: < span class="hljs-built_in">require(< span class="hljs-string">'./lib/src'), dest: < span class="hljs-built_in">require(< span class="hljs-string">'./lib/dest'), watch: < span class="hljs-built_in">require(< span class="hljs-string">'glob-watcher') }; </ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ code ></ pre > < p >那么,我们看下< code >vinyl-fs/lib/src/index.js</ code >。可以看到,< code >gulp.src()</ code >返回的,实际是< code >outputStream</ code >这货,而< code >outputStream</ code >是< code >gs.create(glob, options).pipe()</ code >获得的,差不多接近真相了,还有几步而已。< br />参考:< a href="https://github.com/wearefractal/vinyl-fs/blob/master/lib/src/index.js#L37" target="_blank">https://github.com/wearefractal/vinyl-fs/blob/master/lib/src/index.js#L37</ a ></ p > < p >vinyl-fs/lib/src/index.js</ p > < pre class="hljs-dark">< code class="hljs js">< span class="hljs-keyword">var defaults = < span class="hljs-built_in">require(< span class="hljs-string">'lodash.defaults'); < span class="hljs-keyword">var through = < span class="hljs-built_in">require(< span class="hljs-string">'through2'); < span class="hljs-keyword">var gs = < span class="hljs-built_in">require(< span class="hljs-string">'glob-stream'); < span class="hljs-keyword">var File = < span class="hljs-built_in">require(< span class="hljs-string">'vinyl'); < span class="hljs-comment">// 省略非重要代码若干行 < span class="hljs-function">< span class="hljs-keyword">function < span class="hljs-title">src< span class="hljs-params">(glob, opt) { < span class="hljs-comment">// 继续省略代码 < span class="hljs-keyword">var globStream = gs.create(glob, options); < span class="hljs-comment">// when people write to use just pass it through < span class="hljs-keyword">var outputStream = globStream .pipe(through.obj(createFile)) .pipe(getStats(options)); < span class="hljs-keyword">if (options.read !== < span class="hljs-literal">false) { outputStream = outputStream .pipe(getContents(options)); } < span class="hljs-comment">// 就是这里了 < span class="hljs-keyword">return outputStream .pipe(through.obj()); } </ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ code ></ pre > < p >我们再看看< code >glob-stream/index.js</ code >里的< code >create</ code >方法,最后的< code >return aggregate.pipe(uniqueStream);</ code >。好的,下一步就是真相了,我们去< code >ordered-read-streams</ code >这个项目一探究竟。< br />参考:< a href="https://github.com/wearefractal/glob-stream/blob/master/index.js#L89" target="_blank">https://github.com/wearefractal/glob-stream/blob/master/index.js#L89</ a ></ p > < p >glob-stream/index.js</ p > < pre class="hljs-dark">< code class="hljs js">< span class="hljs-keyword">var through2 = < span class="hljs-built_in">require(< span class="hljs-string">'through2'); < span class="hljs-keyword">var Combine = < span class="hljs-built_in">require(< span class="hljs-string">'ordered-read-streams'); < span class="hljs-keyword">var unique = < span class="hljs-built_in">require(< span class="hljs-string">'unique-stream'); < span class="hljs-keyword">var glob = < span class="hljs-built_in">require(< span class="hljs-string">'glob'); < span class="hljs-keyword">var minimatch = < span class="hljs-built_in">require(< span class="hljs-string">'minimatch'); < span class="hljs-keyword">var glob2base = < span class="hljs-built_in">require(< span class="hljs-string">'glob2base'); < span class="hljs-keyword">var path = < span class="hljs-built_in">require(< span class="hljs-string">'path'); < span class="hljs-comment">// 必须省略很多代码 < span class="hljs-comment">// create 方法 create: < span class="hljs-function">< span class="hljs-keyword">function< span class="hljs-params">(globs, opt) { < span class="hljs-comment">// 继续省略代码 < span class="hljs-comment">// create all individual streams < span class="hljs-keyword">var streams = positives.map(< span class="hljs-function">< span class="hljs-keyword">function< span class="hljs-params">(glob){ < span class="hljs-keyword">return gs.createStream(glob, negatives, opt); }); < span class="hljs-comment">// then just pipe them to a single unique stream and return it < span class="hljs-keyword">var aggregate = < span class="hljs-keyword">new Combine(streams); < span class="hljs-keyword">var uniqueStream = unique(< span class="hljs-string">'path'); < span class="hljs-comment">// TODO: set up streaming queue so items come in order < span class="hljs-keyword">return aggregate.pipe(uniqueStream); </ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ code ></ pre > < p >真相来了,我们看下< code >ordered-read-streams</ code >的代码,可能刚开始看不是很懂,没关系,知道它实现了自己的< code >Stream</ code >就可以了(nodejs是有暴露相应的API让开发者对Stream进行定制的),具体可参考:< a href="http://www.nodejs.org/api/stream.html#stream_api_for_stream_implementors" target="_blank">http://www.nodejs.org/api/stream.html#stream_api_for_stream_implementors</ a ></ p > < p >代码来自:< a href="https://github.com/armed/ordered-read-streams/blob/master/index.js" target="_blank">https://github.com/armed/ordered-read-streams/blob/master/index.js</ a ></ p > < p >ordered-read-streams/index.js</ p > < pre class="hljs-dark">< code class="hljs js">< span class="hljs-function">< span class="hljs-keyword">function < span class="hljs-title">OrderedStreams< span class="hljs-params">(streams, options) { < span class="hljs-keyword">if (!(< span class="hljs-keyword">this < span class="hljs-keyword">instanceof(OrderedStreams))) { < span class="hljs-keyword">return < span class="hljs-keyword">new OrderedStreams(streams, options); } streams = streams || []; options = options || {}; < span class="hljs-keyword">if (!< span class="hljs-built_in">Array.isArray(streams)) { streams = [streams]; } options.objectMode = < span class="hljs-literal">true; Readable.call(< span class="hljs-keyword">this, options); < span class="hljs-comment">// stream data buffer < span class="hljs-keyword">this._buffs = []; < span class="hljs-keyword">if (streams.length === < span class="hljs-number">0) { < span class="hljs-keyword">this.push(< span class="hljs-literal">null); < span class="hljs-comment">// no streams, close < span class="hljs-keyword">return; } streams.forEach(< span class="hljs-function">< span class="hljs-keyword">function < span class="hljs-params">(s, i) { < span class="hljs-keyword">if (!s.readable) { < span class="hljs-keyword">throw < span class="hljs-keyword">new < span class="hljs-built_in">Error(< span class="hljs-string">'All input streams must be readable'); } s.on(< span class="hljs-string">'error', < span class="hljs-function">< span class="hljs-keyword">function < span class="hljs-params">(e) { < span class="hljs-keyword">this.emit(< span class="hljs-string">'error', e); }.bind(< span class="hljs-keyword">this)); < span class="hljs-keyword">var buff = []; < span class="hljs-keyword">this._buffs.push(buff); s.on(< span class="hljs-string">'data', buff.unshift.bind(buff)); s.on(< span class="hljs-string">'end', flushStreamAtIndex.bind(< span class="hljs-keyword">this, i)); }, < span class="hljs-keyword">this); } </ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ span ></ code ></ pre > < p >参考:< a href="https://github.com/armed/ordered-read-streams/blob/master/index.js" target="_blank">https://github.com/armed/ordered-read-streams/blob/master/index.js</ a ></ p > < h2 >写在后面</ h2 > < p >兜兜转转一大圈,终于找到了< code >gulp.src()</ code >的源头,大致流程如下,算是蛮深的层级。代码细节神马的,有兴趣的同学可以深究一下。</ p > < blockquote > < p >ordered-read-streams --> glob-stream --> vinyl-fs --> gulp.src()</ p > </ blockquote > </ body > </ html > |
好了,继续安装另一个插件gulp-htmlmin。照着readme安装就是,不过一下安装这么多依赖,黑压压一坨,着实吓人!
然后在xxxx目录下,建立一个gulpfile.js文件,内容直接抄gulp-htmlmin的readme:
var gulp = require( 'gulp' ); var htmlmin = require( 'gulp-htmlmin' ); gulp.task( 'minify' , function () { gulp.src( 'src/*.html' ) .pipe(htmlmin({collapseWhitespace: true })) .pipe(gulp.dest( 'dist' )) }); |
然后控制台运行gulp命令,报错,说什么“ Task 'default' is not in your gulpfile”。只好求助谷歌,发现这个东西
var gulp = require( 'gulp' ); var coffee = require( 'gulp-coffee' ); gulp.task( 'scripts' , function () { gulp.src( 'src/*.coffee' ) .pipe(coffee()) .pipe(gulp.dest( './' )); }); gulp.task( 'watch' , function () { gulp.watch( 'src/*.coffee' , [ 'scripts' ]); }); gulp.task( 'default' , [ 'scripts' , 'watch' ]); |
于是将原来的代码改装一下:
var gulp = require( 'gulp' ); var htmlmin = require( 'gulp-htmlmin' ); gulp.task( 'minify' , function () { gulp.src( 'src/*.html' ) .pipe(htmlmin({collapseWhitespace: true })) .pipe(gulp.dest( 'dist' )) }); gulp.task( 'watch' , function () { console.log( '继续压死你!' ) gulp.watch( 'src/*.html' , [ 'minify' ]); }); gulp.task( 'default' , [ 'minify' , 'watch' ]); |
运行gulp命令,生成dest目录,里面的index.html已经成功被压缩。并且有了watch任务,以后我们每次修改html,都会同步到dest中去。
估计default任务应该是类似C语言的main方法那样的东西,没有它是无法带动其他任务的。
接着我们好好学一下其基础吧。
gulp有5个基本方法:src、dest、task、run、watch
gulp.src()
gulp模块的src方法,用于产生数据流。它的参数表示所要处理的文件,一般有以下几种形式:
js/app.js
:指定确切的文件名js/*.js
:某个目录所有后缀名为js的文件js/**/*.js
:某个目录及其所有子目录中的所有后缀名为js的文件!js/app.js
:除了js/app.js以外的所有文件*.+(js|css)
:匹配项目根目录下,所有后缀名为js或css的文件
src方法的参数还可以是一个数组,用来指定多个成员:
gulp.src([ 'js/**/*.js' , '!js/**/*.min.js' ]); |
gulp.dest()
gulp模块的dest方法,可以用来传送文件,同时写入文件到指定目录。可以重复的发送传递给它的数据,因此可以将文件传送到多个目录中。简单的例子:
gulp.src( './client/templates/*.jade' ) .pipe(jade()) .pipe(gulp.dest( './build/templates' )) .pipe(minify()) .pipe(gulp.dest( './build/minified_templates' )); |
gulp.task()
gulp模块的task方法,用于定义具体的任务。它的第一个参数是任务名,第二个参数是任务函数。下面是一个非常简单的任务函数:
gulp.task( 'greet' , function () { console.log( 'Hello world!' ); }); |
task方法还可以指定按顺序运行的一组任务:
gulp.task( 'build' , [ 'css' , 'js' , 'imgs' ]); |
上面代码先指定build任务,它按次序由css、js、imgs三个任务所组成。注意:由于每个任务都是异步调用,所以没有办法保证js任务的开始运行的时间,正好是css任务运行结束时间。
如果希望各个任务严格按次序运行,可以把前一个任务写成后一个任务的依赖模块:
gulp.task( 'css' , [ 'greet' ], function () { // Deal with CSS here }); |
上面代码表明,css任务依赖greet任务,所以css一定会在greet运行完成后再运行。
如果一个任务的名字为default,就表明它是“默认任务”,在命令行直接输入gulp命令,就会运行该任务:
gulp.task( 'default' , function () { // Your default task }); |
gulp.run()
gulp模块的run方法,表示要执行的任务。可能会使用单个参数的形式传递多个任务。注意:任务是尽可能多的并行执行的,并且可能不会按照指定的顺序运行:
gulp.run( 'scripts' , 'copyfiles' , 'builddocs' ); gulp.run( 'scripts' , 'copyfiles' , 'builddocs' , function (err) { // 所有任务完成,或者触发错误而终止 }); |
可以使用gulp.run
在其他任务中运行任务。也可以在默认任务中使用gulp.run
组织多个更小的任务为一个大任务。
gulp.watch()
gulp模块的watch方法,用于指定需要监视的文件。一旦这些文件发生变动,就运行指定任务:
gulp.task( 'watch' , function () { gulp.watch( 'templates/*.tmpl.html' , [ 'build' ]); }); |
上面代码指定,一旦templates目录中的模板文件发生变化,就运行build任务。
watch方法也可以用回调函数,代替指定的任务:
gulp.watch( 'templates/*.tmpl.html' , function (event) { console.log( 'Event type: ' + event.type); console.log( 'Event path: ' + event.path); }); |
另一种写法是watch方法所监控的文件发生变化时(修改、增加、删除文件),会触发change事件,可以对change事件指定回调函数:
var watcher = gulp.watch( 'templates/*.tmpl.html' , [ 'build' ]); watcher.on( 'change' , function (event) { console.log( 'Event type: ' + event.type); console.log( 'Event path: ' + event.path); }); |
除了change事件,watch方法还可能触发以下事件:
- end:回调函数运行完毕时触发。
- error:发生错误时触发。
- ready:当开始监听文件时触发。
- nomatch:没有匹配的监听文件时触发。
watcher对象还包含其他一些方法:
watcher.end()
:停止watcher对象,不会再调用任务或回调函数。watcher.files()
:返回watcher对象监视的文件。watcher.add(glob)
:增加所要监视的文件,它还可以附件第二个参数,表示回调函数。watcher.remove(filepath)
:从watcher对象中移走一个监视的文件。
学完这些就可以到其官网上找插件了,毕竟插件才是王道。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
2011-02-11 splice方法在各浏览器的差异
2011-02-11 我的模块加载系统 v3
2010-02-11 为IE的javascript提速
2010-02-11 自动执行函数