上篇文章我们分别对 gulp 的 .src 和 .dest 两个主要接口做了分析,今天打算把剩下的面纱一起揭开 —— 解析 gulp.task 的源码,了解在 gulp4.0 中是如何管理、处理任务的。
在先前的版本,gulp 使用了 orchestrator 模块来指挥、排序任务,但到了 4.0 则替换为 undertaker 来做统一管理。先前的一些 task 写法会有所改变:
///////旧版写法 gulp.task('uglify', function(){ return gulp.src(['src/*.js']) .pipe(uglify()) .pipe(gulp.dest('dist')); }); gulp.task('default', ['uglify']); ///////新版写法1 gulp.task('uglify', function(){ return gulp.src(['src/*.js']) .pipe(uglify()) .pipe(gulp.dest('dist')); }); gulp.task('default', gulp.parallel('uglify')); ///////新版写法2 function uglify(){ return gulp.src(['src/*.js']) .pipe(uglify()) .pipe(gulp.dest('dist')); } gulp.task(uglify); gulp.task('default', gulp.parallel(uglify));
更多变化点,可以参考官方 changelog,或者在后文我们也将透过源码来介绍各 task API 用法。
从 gulp 的入口文件来看,任务相关的接口都是从 undertaker 继承:
var util = require('util'); var Undertaker = require('undertaker');function Gulp() { Undertaker.call(this); this.task = this.task.bind(this); this.series = this.series.bind(this); this.parallel = this.parallel.bind(this); this.registry = this.registry.bind(this); this.tree = this.tree.bind(this); this.lastRun = this.lastRun.bind(this); } util.inherits(Gulp, Undertaker);
接着看 undertaker 的入口文件,发现其代码粒化的很好,每个接口都是单独一个模块:
'use strict'; var inherits = require('util').inherits; var EventEmitter = require('events').EventEmitter; var DefaultRegistry = require('undertaker-registry'); var tree = require('./lib/tree'); var task = require('./lib/task'); var series = require('./lib/series'); var lastRun = require('./lib/last-run'); var parallel = require('./lib/parallel'); var registry = require('./lib/registry'); var _getTask = require('./lib/get-task'); var _setTask = require('./lib/set-task'); function Undertaker(customRegistry) { EventEmitter.call(this); this._registry = new DefaultRegistry(); if (customRegistry) { this.registry(customRegistry); } this._settle = (process.env.UNDERTAKER_SETTLE === 'true'); } inherits(Undertaker, EventEmitter); Undertaker.prototype.tree = tree; Undertaker.prototype.task = task; Undertaker.prototype.series = series; Undertaker.prototype.lastRun = lastRun; Undertaker.prototype.parallel = parallel; Undertaker.prototype.registry = registry; Undertaker.prototype._getTask = _getTask; Undertaker.prototype._setTask = _setTask; module.exports = Undertaker;
我们先从构造函数入手,可以知道 undertaker 其实是作为事件触发器(EventEmitter)的子类:
function Undertaker(customRegistry) { EventEmitter.call(this); //super() this._registry = new DefaultRegistry(); if (customRegistry) { this.registry(customRegistry); } this._settle = (process.env.UNDERTAKER_SETTLE === 'true'); } inherits(Undertaker, EventEmitter); //继承 EventEmitter
这意味着你可以在它的实例上做事件绑定(.on)和事件触发(.emit)处理。
另外在构造函数中,定义了一个内部属性 _registry 作为寄存器(注册/寄存器模式的实现,提供统一接口来存储和读取 tasks):
this._registry = new DefaultRegistry(); //undertaker-registry模块 if (customRegistry) { //支持自定义寄存器 this.registry(customRegistry); }
寄存器默认为 undertaker-registry 模块的实例,我们后续可以通过其对应接口来存储和获取任务:
// 存储任务(名称+任务方法) this._registry.set(taskName, taskFunction); // 通过任务名称获取对应任务方法 this._registry.get(taskName); // 获取存储的全部任务 this._registry.task(); // { taskA : function(){...}, taskB : function(){...} }
undertaker-registry 的源码也简略易懂:
function DefaultRegistry() { //对外免 new 处理 if (this instanceof DefaultRegistry === false) { return new DefaultRegistry(); } //初始化任务对象,用于存储任务 this._tasks = {}; } // 初始化方法(仅做占位使用) DefaultRegistry.prototype.init = function init(taker) {}; //返回指定任务方法 DefaultRegistry.prototype.get = function get(name) { return this._tasks[name]; }; //保存任务 DefaultRegistry.prototype.set = function set(name, fn) { return this._tasks[name] = fn; }; //获取任务对象 DefaultRegistry.prototype.tasks = function tasks() { var self = this; //克隆 this._tasks 对象,避免外部修改会对其有影响 return Object.keys(this._tasks).reduce(function(tasks, name) { tasks[name] = self.get(name); return tasks; }, {}); }; module.exports = DefaultRegistry;