专栏首页Super 前端10 分钟了解 webpack 核心内容
原创

10 分钟了解 webpack 核心内容

10 分钟了解 webpack 核心内容

直接上手稿了

Tapable 是 webpack 核心工具之一,提供了插件接口。webpack 中许多对象扩展自 Tapable 类(如,负责编译的 Compiler 和负责创建 bundles 的 Compilation)。这个类暴露 tap, tapAsynctapPromise 方法,可以使用这些方法,注入自定义的构建步骤,这些步骤将在整个编译过程中不同时机触发。

Compiler.js#L104 每一个事件钩子决定了它该如何应用插件的注册

class Compiler {
  this.hooks = Object.freeze({
    initialize: new SyncHook([]),
    shouldEmit: new SyncBailHook(["compilation"]),
    done: new AsyncSeriesHook(["stats"]),
    ...
  })
}

Tapable 的核心原理是发布订阅模式。基于 Tapable 使得 webpack 具有很好的扩展性,但对于调试来说比较痛苦(代码跳跃)。

const {
  SyncHook,
  SyncBailHook,
  SyncWaterfallHook,
  SyncLoopHook,
  AsyncParallelHook,
  AsyncParallelBailHook,
  AsyncSeriesHook,
  AsyncSeriesBailHook,
  AsyncSeriesWaterfallHook
} = require("tapable");

主线

hook 事件注册 ==> hook 触发 ==> 生成 hook 执行代码new Function() ==> 执行

更多动态执行脚本方式 ,请参照:https://blog.csdn.net/ligang2585116/article/details/105479340

注册&触发

注册

interface Hook {
  tap: (name: string | Tap, fn: (context?, ...args) => Result) => void,
  tapAsync: (name: string | Tap, fn: (context?, ...args, callback: (err, result: Result) => void) => void) => void,
  tapPromise: (name: string | Tap, fn: (context?, ...args) => Promise<Result>) => void
}

触发

interface Hook {
  call: (...args) => Result,
  callAsync: (...args, callback: (err, result: Result) => void) => void,
  promise: (...args) => Promise<Result>
}

Hook types

按时序方式分类

  • Sync: 同步,通过 myHook.tap() 调用
  • Async:异步
    • AsyncSeries: 连续调用每个异步方法,通过 myHook.tap()myHook.tapAsync()myHook.tapPromise() 调用
    • AsyncParallel:并行运行每个异步方法,调用方式同 AsyncSeries

按流程控制分类

  • 基础类型:名称中没有 Bail、Waterfall、Loop,在触发事件之后,会按照事件注册的先后顺序执行所有的事件处理函数,不关心返回值。SyncHook、AsyncParallelHook、AsyncSeriesHook
  • Waterfall:「返回结果具有流动性--瀑布」如果前一个 Hook 函数的结果 result !== undefined,则 result 会作为后一个 Hook 函数的第一个参数。-- 有点类似于 Array.prototype.reduce()SyncWaterfallHook、AsyncSeriesWaterfallHook 注意:没有 AsyncParallelWaterfallHook,并行操作无法确保返回顺序,值无法传递 类别Interface返回形式tapfn: (context?, ...args) => Resultreturn resulttapAsyncfn: (context?, ...args, callback: (err, result: Result) => void) => voidcallback(err, result)tapPromisefn: (context?, ...args) => Promise<Result>resolve(result)
  • Bail:顺序执行 Hook,遇到第一个结果 result !== undefined 则返回,不再继续执行。-- 有点类似于Promise.race(iterable)SyncBailHook、AsyncSeriseBailHook、AsyncParallelBailHook
  • Loop:不停的循环执行 Hook,直到所有函数结果 result === undefinedSyncLoopHook、AsyncSeriseLoopHook

示例

示例1:基础示例

const { SyncHook } = require('tapable')
​
class Test {
  constructor () {
    this.hooks = {
      compiler: new SyncHook(['name'])
    }
  }
  tap () {
    this.hooks.compiler.tap('consumer1', (name) => {
      console.log('consumer1', name)
      return 'consumer1'
    })
    this.hooks.compiler.tap('consumer2', (name) => {
      console.log('consumer2', name)
      return 'consumer2'
    })
  }
  call () {
    this.hooks.compiler.call('ligang')
  }
}
const t = new Test()
t.tap()
t.call()
// 输出结果
// consumer1 ligang
// consumer2 ligang

SyncHook 不会处理 result 值。

示例2:SyncWaterfallHook

const { SyncWaterfallHook } = require('tapable')
​
class Test {
  constructor () {
    this.hooks = {
      compiler: new SyncWaterfallHook(['name'])
    }
  }
  tap () {
    this.hooks.compiler.tap('consumer1', (name) => {
      console.log('consumer1', name)
      return 'consumer1'
    })
    this.hooks.compiler.tap('consumer2', (name) => {
      console.log('consumer2', name)
      return 'consumer2'
    })
  }
  call () {
    this.hooks.compiler.call('ligang')
  }
}
const t = new Test()
t.tap()
t.call()
// 输出结果
// consumer1 ligang
// consumer2 consumer1

return 值不是undefined,发生了传递。

示例3:SyncBailHook

const { SyncBailHook } = require('tapable')
​
class Test {
  constructor () {
    this.hooks = {
      compiler: new SyncBailHook(['name'])
    }
  }
  tap () {
    this.hooks.compiler.tap('consumer1', (name) => {
      console.log('consumer1', name)
      return 'consumer1'
    })
    this.hooks.compiler.tap('consumer2', (name) => {
      console.log('consumer2', name)
      return 'consumer2'
    })
  }
  call () {
    this.hooks.compiler.call('ligang')
  }
}
const t = new Test()
t.tap()
t.call()

consumer1 返回的 result 值不是 undefined,因此后续路程被终止,consumer2 未被执行!

示例4:其他

异步方式类似,只要注意返回的 result 形式不同。

  • tapreturn result
  • tapAsynccallback(err, result)
  • tapPromiseresolve(result)

拦截 intercept

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

interface HookInterceptor {
  call: (context?, ...args) => void,
  loop: (context?, ...args) => void,
  tap: (context?, tap: Tap) => void,
  register: (tap: Tap) => Tap
}
  • register: (tap: Tap) => Tap | undefined 插件用 tap* 方法注册时触发;
  • call: (...args) => void 当被call 调用时触发,并可以访问到 hooks 参数(call 之前触发);
  • tap: (tap: Tap) => void 当插件被调用触发,提供的是Tap对象,但不可修改(在call 之后,回调之前触发);
  • loop: (...args) => void loop hook 的插件被调用时触发(每个循环触发);
const { SyncHook } = require('tapable')
​
class Test {
  constructor () {
    this.hooks = {
      compiler: new SyncHook(['name'])
    }
  }
  interceptor () {
    this.hooks.compiler.intercept({
      register: (tap) => {
        console.log('register!!!', tap)
        return tap
      },
      call: (args => {
        console.log('call!!!', args)
      }),
      tap: (tap => {
        console.log('tap!!!', tap)
      }) 
    }) 
  }
  tap () {
    this.hooks.compiler.tap('consumer1', (name) => {
      console.log('consumer1', name)
      return 'consumer1'
    })
    this.hooks.compiler.tap('consumer2', (name) => {
      console.log('consumer2', name)
      return 'consumer2'
    })
  }
  call () {
    this.hooks.compiler.call('ligang')
  }
}
const t = new Test()
t.interceptor()
t.tap()
t.call()
// 输出结果
// register!!! { type: 'sync', fn: [Function], name: 'consumer1' }
// register!!! { type: 'sync', fn: [Function], name: 'consumer2' }
// call!!! ligang
// tap!!! { type: 'sync', fn: [Function], name: 'consumer1' }
// consumer1 ligang
// tap!!! { type: 'sync', fn: [Function], name: 'consumer2' }
// consumer2 ligang

上下文 Context

插件和拦截器都可以往里面传一个上下文对象的参数,该对象可用于向后续插件和拦截器传递任意值。通过此,可以对拦截或后续插件做灵活控制!

const { SyncHook } = require('tapable')
​
class Test {
  constructor () {
    this.hooks = {
      compiler: new SyncHook(['name'])
    }
  }
  interceptor () {
    this.hooks.compiler.intercept({
      context: true,
      call: (context, args) => {
        context.params = {a: 1, b: 2}
        console.log('call!!!', args)
      }
    }) 
  }
  tap () {
    this.hooks.compiler.tap({
      name: 'consumer',
      context: true
    }, (context, name) => {
      console.log('consumer1', name, context)
      return 'consumer1'
    })
  }
  call () {
    this.hooks.compiler.call('ligang')
  }
}
const t = new Test()
t.interceptor()
t.tap()
t.call()
// 输出结果
// call!!! ligang
// consumer1 ligang { params: { a: 1, b: 2 } }

参考地址

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 变量、作用域和内存问题

    确定一个值是哪种基本类型可以使用typeof操作符,而确定一个值是哪种引用类型可以使用instanceof操作符。 4. ECMAScript中所有函数的参...

    奋飛
  • JavaScript面向对象精要(一)

    JavaScript虽然没有类的概念,但依然存在两种类型:原始类型和引用类型。 原始类型保存为简单数据值;引用类型则保存为对象,其本质是指向内存位置的引用。

    奋飛
  • 解决PKIX问题:unable to find valid certification path to requested target

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • 从源码中分析 Hadoop 的 RPC 机制

    RPC是Remote Procedure Call(远程过程调用)的简称,这一机制都要面对两个问题:对象调用方式余与序列/反序列化机制。本文给大家介绍从源码中分...

    Star先生
  • Windows启动HDFS报错 - 系统找不到文件 hadoop。

    Windows 7 环境下启动 HDFS,执行 start-dfs.cmd 出现 系统找不到文件 hadoop。报错信息如下

    夹胡碰
  • ROS编程(ETH)2018更新版习题说明(一)

    1. 依据网页链接,完成Husky仿真环境配置,并启动。(ROS版本为Kinetic),可能需要源码编译。

    zhangrelay
  • 几个django 2.2和mysql使用

    可能是由于Django使用的MySQLdb库对Python3不支持,我们用采用了PyMySQL库来代替,导致出现各种坑,特别是执行以下2条命令的是时候:

    py3study
  • RPA(机器人流程自动化)能在什么环境下运行?

    RPA是一种机器人(软件),可以利用AI等技术在计算机上再现人类智能。当然也包括机械学习。

    爱博特iBotRPA
  • Tesla 两年前车祸致死案,事故报告终发布

    场景描述:近日,特斯拉 2018 年的一场严重车祸,迎来了一场公开听证会上的调查结果。这一次,对车祸具体原因进行了说明,阐释了自动特斯拉自动驾驶系统存在的弊端,...

    HyperAI超神经
  • python测试开发django-37.外键(ForeignKey)查询

    前面在admin后台页面通过设置外键,可以选择下拉框的选项,本篇主要讲解关于外键(ForeignKey)的查询

    上海-悠悠

扫码关注云+社区

领取腾讯云代金券