gulp学习(1)

Posted on By yuanfang

gulp基础使用

$ npm install --global gulp
$ npm install --save-dev gulp

// 在项目根目录下创建一个名为 gulpfile.js 的文件:
var gulp = require('gulp');

gulp.task('default', function() {
  // 将你的默认的任务代码放在这
});

// 运行 gulp
gulp

gulp api文档

gulp.src(globs[, options])

输出(Emits)符合所提供的匹配模式(glob)或者匹配模式的数组(array of globs)的文件。 将返回一个 Vinyl files 的 stream 它可以被 piped 到别的插件中。

gulp.src('client/templates/*.jade')
  .pipe(jade())
  .pipe(minify())
  .pipe(gulp.dest('build/minified_templates'));

gulp.dest(path[, options])

能被 pipe 进来,并且将会写文件。并且重新输出(emits)所有数据,因此你可以将它 pipe 到多个文件夹。如果某文件夹不存在,将会自动创建它。

gulp.src('./client/templates/*.jade')
  .pipe(jade())
  .pipe(gulp.dest('./build/templates'))
  .pipe(minify())
  .pipe(gulp.dest('./build/minified_templates'));

gulp.task(name[, deps], fn)

定义一个使用 Orchestrator 实现的任务(task)。

  • name 任务的名字,如果你需要在命令行中运行你的某些任务,那么,请不要在名字中使用空格。

  • deps –类型: Array 一个包含任务列表的数组,这些任务会在你当前任务运行之前完成。注意: 你的任务是否在这些前置依赖的任务完成之前运行了?请一定要确保你所依赖的任务列表中的任务都使用了正确的异步执行方式:使用一个 callback,或者返回一个 promise 或 stream。

  • fn 该函数定义任务所要执行的一些操作。通常来说,它会是这种形式:gulp.src().pipe(someplugin())
    注意: 默认的,task 将以最大的并发数执行,也就是说,gulp 会一次性运行所有的 task 并且不做任何等待。如果你想要创建一个序列化的 task 队列,并以特定的顺序执行,你需要做两件事:
    给出一个提示,来告知 task 什么时候执行完毕,
    并且再给出一个提示,来告知一个 task 依赖另一个 task 的完成。

var gulp = require('gulp');

// 返回一个 callback,因此系统可以知道它什么时候完成
gulp.task('one', function(cb) {
    // 做一些事 -- 异步的或者其他的
    cb(err); // 如果 err 不是 null 或 undefined,则会停止执行,且注意,这样代表执行失败了
});

// 定义一个所依赖的 task 必须在这个 task 执行之前完成
gulp.task('two', ['one'], function() {
    // 'one' 完成后
});

gulp.task('default', ['one', 'two']);

注意: 默认的,task 将以最大的并发数执行,也就是说,gulp 会一次性运行所有的 task 并且不做任何等待。如果你想要创建一个序列化的 task 队列,并以特定的顺序执行,你需要做两件事:
给出一个提示,来告知 task 什么时候执行完毕,
并且再给出一个提示,来告知一个 task 依赖另一个 task 的完成。
对于这个例子,让我们先假定你有两个 task,”one” 和 “two”,并且你希望它们按照这个顺序执行:
在 “one” 中,你加入一个提示,来告知什么时候它会完成:可以再完成时候返回一个 callback,或者返回一个 promise 或 stream,这样系统会去等待它完成。
在 “two” 中,你需要添加一个提示来告诉系统它需要依赖第一个 task 完成。

gulp.watch(glob [, opts], tasks) 或 gulp.watch(glob [, opts, cb])

监视文件,并且可以在文件发生改动时候做一些事情。它总会返回一个 EventEmitter 来发射(emit) change 事件。

gulp.watch('js/**/*.js', function(event) {
  console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});

以上gulp为4以下版本,新版需要查看新的api文档~~
gulp4.0 api

glob

官网https://github.com/isaacs/node-glob

“globs” 就是模型,比如当你在命令行里输入 ls .js, 又或者是你在 .gitignore 文件里写的 bulid/ 这些.
在解析路径模型的时候, 大括号里用多个逗号隔开的内容会被展开, 里面的部分也可以包含”/” ,比如 a{/b/c, bcd} 会被展开成 a/b/c 和 abcd;

var glob = require("glob");

// options 是可选的
glob("**/*.js", options, function (er, files) {
  // files 是匹配到的文件的数组.
  // 如果 `nonull` 选项被设置为true, 而且没有找到任何文件,那么files就是glob规则本身,而不是空数组
  // er是当寻找的过程中遇的错误
})

下列字符在路径部分使用时具有特殊的特殊意义

    1、* :匹配单个路径部分中的0个或多个字符。
    2、?:匹配路径中某部分1个字符。
    3、[...] :匹配一个字符的范围,类似于一个正则表达式的范围。如果范围的第一个字符是!或者,它匹配任何不在范围内的字符。
    4、!(模式1|模式2|模式3):匹配与所提供的任何模式不匹配的任何内容。和正则表达式的!一样。
    5、?(模式1|模式2|模式3):匹配所提供的模式的零或一个事件。
    6、+(模式1|模式2|模式3):匹配所提供的模式的一个或多个事件。
    7、*(a|b|c) :匹配所提供的模式的零个或多个事件。
    8、@(pattern|pat*|pat?erN):匹配所提供的模式之一。
    9、**:如果**在一个路径的部分,他会匹配零个或多个目录和子目录中搜索匹配。

gulp源码解析1–stream详解

注意attention: 转载学习来源https://www.cnblogs.com/vajoy/p/6349817.html

var server2 = http.createServer(function (req, res) {
    var stream = fs.createReadStream(__dirname + '/data.txt');
    stream.pipe(res);
});
server2.listen(4000);

对于创建的可读流,我们通过 .pipe() 接口来监听其 data 和 end 事件,并把 data.txt (的可读流)拆分成一小块一小块的数据(chunks),像流水一样源源不断地吐给客户端,而不再需要等待整个文件都加载到内存后才发送数据。
其中 .pipe 可以视为流的“管道/通道”方法,任何类型的流都会有这个 .pipe 方法去成对处理流的输入与输出。

内置接口 ._read() 可以用来处理这个问题,它是从系统底层开始读取数据流时才会不断调用自身,从而减少缓存冗余。我们是在 ._read 方法中才使用 readStream.push(data) 往可读流里注入数据供下游消耗(也会流经缓存),从而提升流处理的性能。

'use strict';
const Readable = require('stream').Readable;
const rs = Readable();
const s = 'VaJoy';
const l = s.length;
let i = 0;
rs._read = ()=>{
    if(i == l){
        rs.push(' is my name');
        return rs.push(null)
    }
    rs.push(s[i++])
};
rs.pipe(process.stdout);

这里也有个小问题 —— 上一句话所提到的“供下游消耗”,这个下游通常又会以怎样的形式来消耗可读流的呢?
首先,可以使用我们熟悉的 .pipe() 方法将可读流推送给一个消耗对象(writable、transform 或者 duplex流):

//ext1
const fs = require('fs');
const zlib = require('zlib');

const r = fs.createReadStream('data.txt');
const z = zlib.createGzip();
const w = fs.createWriteStream('data.txt.gz');
r.pipe(z).pipe(w);

其次,也可以通过监听可读流的“data”事件(别忘了文章前面提到的“所有的流都属于 EventEmitter 的实例”)来实现消耗处理 —— 在首次监听其 data 事件后,readStream 便会持续不断地调用 _read(),通过触发 data 事件将数据输出。当数据全部被消耗时,则触发 end 事件。

//demo3
const Readable = require('stream').Readable;

class ToReadable extends Readable {
    constructor(iterator) {
        super();
        this.iterator = iterator
    }
    _read() {
        const res = this.iterator.next();
        if (res.done) {
            // 迭代结束,顺便结束可读流
            this.push(null)
        }
        setTimeout(() => {
            // 将数据添加到流中
            this.push(res.value + '\n')
        }, 0)
    }
}

const gen = function *(a){
    let count = 5,
        res = a;
    while(count--){
        res = res*res;
        yield res
    }
};

const readable = new ToReadable(gen(2));

// 监听`data`事件,一次获取一个数据
readable.on('data', data => process.stdout.write(data));

// 可读流消耗完毕
readable.on('end', () => process.stdout.write('readable stream ends~'));

可写流有两个重要的方法:

writableStream.write(chunk[, encoding, callback]) —— 往可写流里写入数据; writableStream.end([chunk, encoding, callback]) —— 停止写入数据,结束可写流。在调用 .end() 后,再调用 .write() 方法会产生错误。 上方两方法的 encoding 参数表示编码字符串(chunk为String时才可以用)。
write 方法的 callback 回调参数会在 chunk 被消费后(从缓存中移除后)被触发;end 方法的 callback 回调参数则在 Stream 结束时触发。
另外,如同通过 readable._read() 方法可以处理可读流,我们可以通过 writable._write(chunk, enc, next) 方法在系统底层处理流写入的逻辑中,对数据进行处理。
其中参数 chunk 代表写进来的数据;enc 代表编码的字符串;next(err) 则是一个回调函数,调用它可以告知消费者进行下一轮的数据流写入。

//demo4
const Writable = require('stream').Writable;
const writable = Writable();

writable._write = (chunck, enc, next) => {
    // 输出打印
    process.stdout.write(chunck.toString().toUpperCase());
    // 写入完成时,调用`next()`方法通知流传入下一个数据
    process.nextTick(next)
};

// 所有数据均已写入底层
writable.on('finish', () => process.stdout.write('DONE'));

// 将一个数据写入流中
writable.write('a' + '\n');
writable.write('b' + '\n');
writable.write('c' + '\n');

// 再无数据写入流时,需要调用`end`方法
writable.end();

Duplex Streams
Duplex 是双工的意思,因此很容易猜到 Duplex 流就是既能读又能写的一类流,它继承了 Readable 和 Writable 的接口。

//demo5
const Duplex = require('stream').Duplex;
const duplex = Duplex();

duplex._read = function () {
    var date = new Date();
    this.push( date.getFullYear().toString() );
    this.push(null)
};

duplex._write = function (buf, enc, next) {
    console.log( buf.toString() + '\n' );
    next()
};

duplex.on('data', data => console.log( data.toString() ));

duplex.write('the year is');

duplex.end();

Transform Streams
Transform Stream 是在继承了 Duplex Streams 的基础上再进行了扩展,它可以把写入的数据和输出的数据,通过 ._transform 接口关联起来。

//demo6
const Transform = require('stream').Transform;
class SetName extends Transform {
    constructor(name, option) {
        super(option || {});
        this.name = name || ''
    }
    // .write接口写入的数据,处理后直接从 data 事件的回调中可取得
    _transform(buf, enc, next) {
        var res = buf.toString().toUpperCase();
        this.push(res + this.name + '\n');
        next()
    }
}

var transform = new SetName('VaJoy');
transform.on('data', data => process.stdout.write(data));

transform.write('my name is ');
transform.write('here is ');
transform.end();

through2

//demo9
const fs = require('fs');
const through2 = require('through2');
fs.createReadStream('data.txt')
    .pipe(through2(function (chunk, enc, callback) {
        for (var i = 0; i < chunk.length; i++)
            if (chunk[i] == 97)
                chunk[i] = 122; // 把 'a' 替换为 'z'

        this.push(chunk);

        callback()
    }))
    .pipe(fs.createWriteStream('out.txt'))
    .on('finish', ()=> {
        console.log('DONE')
    });

through2源码:

var Transform = require('readable-stream/transform'),
    inherits = require('util').inherits,
    xtend = require('xtend');

//构造方法,继承了Transform
function DestroyableTransform(opts) {
    Transform.call(this, opts);
    this._destroyed = false
}

inherits(DestroyableTransform, Transform);

//原型接口 destroy,用于关闭当前流
DestroyableTransform.prototype.destroy = function (err) {
    if (this._destroyed) return;
    this._destroyed = true;

    var self = this;
    process.nextTick(function () {
        if (err)
            self.emit('error', err);
        self.emit('close')
    })
};

// a noop _transform function
function noop(chunk, enc, callback) {
    callback(null, chunk)
}


// 闭包,用于返回对外接口方法
function through2(construct) {
    //最终返回此匿名函数
    return function (options, transform, flush) {
        if (typeof options == 'function') {
            flush = transform
            transform = options
            options = {}
        }

        if (typeof transform != 'function')
            transform = noop

        if (typeof flush != 'function')
            flush = null

        return construct(options, transform, flush)
    }
}


// 出口,执行 throuh2 闭包函数,返回一个 DestroyableTransform 的实例(t2)
module.exports = through2(function (options, transform, flush) {
    //t2 为 Transform Stream 对象
    var t2 = new DestroyableTransform(options);

    //Transform Streams 的内置接口 _transform(chunk, encoding, next) 方法
    t2._transform = transform;

    if (flush)
        t2._flush = flush;

    return t2
});


// 对外暴露一个可以直接 new (或者不加 new)来创建实例的的构造函数
module.exports.ctor = through2(function (options, transform, flush) {
    function Through2(override) {
        if (!(this instanceof Through2))
            return new Through2(override)

        this.options = xtend(options, override)

        DestroyableTransform.call(this, this.options)
    }

    inherits(Through2, DestroyableTransform)

    Through2.prototype._transform = transform

    if (flush)
        Through2.prototype._flush = flush

    return Through2
})

//Object Mode接口的简易封装
module.exports.obj = through2(function (options, transform, flush) {
    var t2 = new DestroyableTransform(xtend({objectMode: true, highWaterMark: 16}, options))

    t2._transform = transform

    if (flush)
        t2._flush = flush

    return t2
})