阅读 101

Webpack核心Tapable从应用到源码之二(拦截器)

前言

所有 Hook 都提供了额外的拦截器 API

本篇来看一下拦截器的用法。

Interception 拦截器

call

类型:(...args) => void

此方法会在定义的 Hook 被调用之前执行。也就是 callcallAsyncpromise 方法之前执行。

tap

类型:(tap: Tap) => void

此方法会在订阅函数执行之前执行。

loop

类型:(...args) => void

此方法会在 具有 loop 功能的 Hook 的订阅函数执行之前执行。

register

类型: (tap: Tap) => Tap | undefined

此方法会在每个订阅函数之前执行,并且可以用于修改订阅函数的 Tap 对象。

这个待分析源码时,就能清晰明白它的功能了。

示例

示例代码

用一个示例看一下拦截器功能:

const {
  AsyncSeriesLoopHook
} = require('tapable');

const hook = new AsyncSeriesLoopHook(['name', 'age']);
let count1 = 0;
let count2 = 0;

hook.intercept({
  call: () => {
    console.log('call');
  },
  tap: tapInfo => {
    console.log('tap', tapInfo);
  },
  register: tapInfo => {
    console.log('register', tapInfo);
    return tapInfo;
  },
  loop: () => {
    console.log('intercept loop');
  }
});

hook.tapAsync('js', (name, age, callback) => {
  if (++count1 >= 2) {
    console.log('js', name, age, count1);
    callback();
  } else {
    console.log('js', name, age, count1);
    callback(null, 'js1');
  }
});

hook.tapAsync('node', (name, age, callback) => {
  if (++count2 >= 2) {
    console.log('node', name, age, count2);
    callback();
  } else {
    console.log('node', name, age, count2);
    callback(null, 'node2');
  }
});

hook.callAsync('naonao', 2, err => {
  console.log('end', err);
});
复制代码

打印结果

register { type: 'async', fn: [Function (anonymous)], name: 'js' }
register { type: 'async', fn: [Function (anonymous)], name: 'node' }
call
intercept loop
tap { type: 'async', fn: [Function (anonymous)], name: 'js' }
js naonao 2 1
intercept loop
tap { type: 'async', fn: [Function (anonymous)], name: 'js' }
js naonao 2 2
tap { type: 'async', fn: [Function (anonymous)], name: 'node' }
node naonao 2 1
intercept loop
tap { type: 'async', fn: [Function (anonymous)], name: 'js' }
js naonao 2 3
tap { type: 'async', fn: [Function (anonymous)], name: 'node' }
node naonao 2 2
end undefined
复制代码

打印 hook.callAsync

打印此时的 hook.callAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  var _taps = this.taps;
  var _interceptors = this.interceptors;
  _interceptors[0].call(name, age);
  var _looper = (function () {
    var _loopAsync = false;
    var _loop;
    do {
      _loop = false;
      _interceptors[0].loop(name, age);
      function _next0() {
        var _tap1 = _taps[1];
        _interceptors[0].tap(_tap1);
        var _fn1 = _x[1];
        _fn1(name, age, (function (_err1, _result1) {
          if (_err1) {
            _callback(_err1);
          } else {
            if (_result1 !== undefined) {
              _loop = true;
              if (_loopAsync) _looper();
            } else {
              if (!_loop) {
                _callback();
              }
            }
          }
        }));
      }
      var _tap0 = _taps[0];
      _interceptors[0].tap(_tap0);
      var _fn0 = _x[0];
      _fn0(name, age, (function (_err0, _result0) {
        if (_err0) {
          _callback(_err0);
        } else {
          if (_result0 !== undefined) {
            _loop = true;
            if (_loopAsync) _looper();
          } else {
            _next0();
          }
        }
      }));
    } while (_loop);
    _loopAsync = true;
  });
  _looper();

}
复制代码

这里先忽略 register ,放在下篇源码分析的时候再看它的具体执行过程。

从代码中可以看到:

  • 拦截器的 call (代码中的 _interceptors[0].call ) 是最先执行的,而且只执行了一次,它接收的参数就是 Hook 调用时传递的参数
  • 拦截器的 loop (代码中的 _interceptors[0].loop) 是在每次循环时执行的,它接收的参数就是 Hook 调用时传递的参数
  • 拦截器的 tap (代码中的 _interceptors[0].tap),是在每个订阅函数执行前执行的,它接收的参数是 Tap 对象。

Context 对象

插件和拦截器可以选择访问可选的 context 对象,该对象可用于将任意值传递给后续插件和拦截器。

示例

示例代码

const {
  AsyncSeriesLoopHook
} = require('tapable');

const hook = new AsyncSeriesLoopHook(['name', 'age']);

hook.intercept({
  context: true,
  tap: (context, tapInfo) => {
    console.log('tap', tapInfo);
    if (context) {
      context.show = true;
    }
  }
});

hook.tapAsync('js', (name, age, callback) => {
  console.log('js', name, age);
  callback();
});

hook.tapAsync({
  context: true,
  name: 'css'
}, (context, name, age, callback) => {
  if (context && context.show) {
    console.log('css context', name, age);
    callback();
  } else {
    console.log('css', name, age);
    callback();
  }
});

hook.tapAsync('node', (name, age, callback) => {
  console.log('node', name, age);
  callback();
});

hook.callAsync('naonao', 2, err => {
  console.log('end', err);
});
复制代码

打印结果

tap { type: 'async', fn: [Function (anonymous)], name: 'js' }
js naonao 2
tap {
  type: 'async',
  fn: [Function (anonymous)],
  context: true,
  name: 'css'
}
css context naonao 2
tap { type: 'async', fn: [Function (anonymous)], name: 'node' }
node naonao 2
end undefined
复制代码

从打印结果看出,第二个订阅函数使用了 context,它能获取到 context.show 属性,所以打印出了 css context naonao 2

这样就可以通过 context 向下传递一些值。

但是在最新版本的 Tapable 已经开始提示 DeprecationWarning: Hook.context is deprecated and will be removed。 所以后面的版本会移除 context

打印 hook.tapAsync

打印此时的 hook.tapAsync 方法:

function anonymous(name, age, _callback) {
  "use strict";
  var _context = {};
  var _x = this._x;
  var _taps = this.taps;
  var _interceptors = this.interceptors;
  var _looper = (function () {
    var _loopAsync = false;
    var _loop;
    do {
      _loop = false;
      function _next1() {
        var _tap2 = _taps[2];
        _interceptors[0].tap(_context, _tap2);
        var _fn2 = _x[2];
        _fn2(name, age, (function (_err2, _result2) {
          if (_err2) {
            _callback(_err2);
          } else {
            if (_result2 !== undefined) {
              _loop = true;
              if (_loopAsync) _looper();
            } else {
              if (!_loop) {
                _callback();
              }
            }
          }
        }));
      }
      function _next0() {
        var _tap1 = _taps[1];
        _interceptors[0].tap(_context, _tap1);
        var _fn1 = _x[1];
        _fn1(_context, name, age, (function (_err1, _result1) {
          if (_err1) {
            _callback(_err1);
          } else {
            if (_result1 !== undefined) {
              _loop = true;
              if (_loopAsync) _looper();
            } else {
              _next1();
            }
          }
        }));
      }
      var _tap0 = _taps[0];
      _interceptors[0].tap(_context, _tap0);
      var _fn0 = _x[0];
      _fn0(name, age, (function (_err0, _result0) {
        if (_err0) {
          _callback(_err0);
        } else {
          if (_result0 !== undefined) {
            _loop = true;
            if (_loopAsync) _looper();
          } else {
            _next0();
          }
        }
      }));
    } while (_loop);
    _loopAsync = true;
  });
  _looper();

}
复制代码

从代码中可以看出,首先定义了 var _context = {} ,然后在拦截器执行 tap 方法时,会把这个 _context 当作第一个参数传递进去。

对于订阅函数,如果设置了 context: true,在调用时就会把 _context 当作第一个参数传递进去;否则就不传递。

结语

本篇涉及的是拦截器和 Context 对象。

下篇我们将从源码说起,揭开 Tapable 的面纱。

更多精彩,请关注微信公众号:闹闹前端

文章分类
前端
文章标签