Skip to content

tapable介绍-同步钩子 SyncHook

webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来。

而实现这一切的核心就是Tapable。

Tapable有点类似于nodejs的events库,核心原理也是依赖发布订阅模式。

查看webpack的源码,找到tapable

新建空白项目 webpack-tapable

初始化package.json

shell
yarn init -y

安装一下webpack、webpack-cli

shell
yarn add webpack@^4.32.2 webpack-cli@^3.3.2 -D

查看webpack-tapable\node_modules\webpack\lib\Compiler.js,编译模块中的代码,就会发现tapable,这是它的主核心模块

js
// webpack-tapable\node_modules\webpack\lib\Compiler.js
const {
	Tapable,
	SyncHook,
	SyncBailHook,
	AsyncParallelHook,
	AsyncSeriesHook
} = require("tapable");
// ...
class Compiler extends Tapable {
	constructor(context) {
		super();
		this.hooks = {
			/** @type {SyncBailHook<Compilation>} */
			shouldEmit: new SyncBailHook(["compilation"]),
			/** @type {AsyncSeriesHook<Stats>} */
			done: new AsyncSeriesHook(["stats"]),
			/** @type {AsyncSeriesHook<>} */
			additionalPass: new AsyncSeriesHook([]),
			/** @type {AsyncSeriesHook<Compiler>} */
			beforeRun: new AsyncSeriesHook(["compiler"]),
			/** @type {AsyncSeriesHook<Compiler>} */
			run: new AsyncSeriesHook(["compiler"]),
			/** @type {AsyncSeriesHook<Compilation>} */
			emit: new AsyncSeriesHook(["compilation"]),
			/** @type {AsyncSeriesHook<string, Buffer>} */
			assetEmitted: new AsyncSeriesHook(["file", "content"]),
			/** @type {AsyncSeriesHook<Compilation>} */
			afterEmit: new AsyncSeriesHook(["compilation"]),
		}
    }
    // ...
}

里面有各种钩子。

插件里包含着异步钩子方法和同步钩子方法。

image-20220128153343326

一个小例子,学习tapable

安装一下tapable

shell
yarn add tapable@^1.1.3

同步钩子 SyncHook

新建文件

js
// webpack\webpack-tapable\1.start.js

// 同步钩子SyncHook tapable
let { SyncHook } = require('tapable')

class Lesson {
    // new 对象时 构造函数会被执行
    constructor() {
        // new对象的时候 注册一些钩子
        // 一旦new一个新的实例时,这些钩子就被启用了
        this.hook = {
            // 假如有个架构课
            arch: new SyncHook(['name']), // 可能会传一个参数,参数是可选的
        }
    }

    // 用来注册的方法
    tap() { // 注册监听函数
        this.hook.arch.tap('vue', function (name) {
            console.log('vue 111', name);
        }) // 拿到钩子实例,实例上就有一个tap方法

        this.hook.arch.tap('react', function (name) {
            console.log('react 111', name);
        })

        // 001 调用这个方法时,会将上面两个事件注册到数组中
    }

    // 启动钩子的方法
    start() {
        // 执行之前注册的事件
        this.hook.arch.call('cheny') // 实例上有一个call 方法,执行注册的事件


        // 002 调用这个方法时,会让数组中注册的方法依次执行

    }
}

// 测试
let l = new Lesson() // 声明一个实例

// 注册两个事件
l.tap()

// 调用start方法 启动钩子
l.start()

执行结果

image-20220128160448618

实际就是一个发布订阅,例子中模拟的是,假如有一个架构课,上架构课之前先把课程准备好。也即先注册事件。

然后等到时机成熟了,然后再依次执行之前注册好的事件。

自己手动实现一下同步钩子 SyncHook

新建文件

js
// webpack\webpack-tapable\1.case.js

class SyncHook { // 同步钩子

    constructor(args) { // args -> ['name']
        this.tasks = [] // 保存注册的事件
    }

    // 同步注册事件
    tap(name, task) {
        this.tasks.push(task)
    }


    // 按顺序依次执行注册过的事件
    call(...args) {
        // console.log(this.tasks);
        this.tasks.forEach((task) => {
            task(...args)
        })
    }
}


let hook = new SyncHook(['name']) // 参数可以传可以不传

// 注册事件
hook.tap('vue', function (name) {
    console.log('vue 1111', name);
})
hook.tap('react', function (name) {
    console.log('react 1111', name);
})

// 依次执行
hook.call('cheny')

总结

webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来。

而实现这一切的核心就是Tapable。

核心原理依赖发布订阅模式。

注册事件,然后在合适的时机执行事件,各个插件就是通过这样的机制执行的。

  1. 本节学习了tapable的同步钩子 SyncHook,同步注册事件,然后依次顺序执行

    js
    class Lesson {
        // new 对象时 构造函数会被执行
        constructor() {
            // new对象的时候 注册一些钩子
            // 一旦new一个新的实例时,这些钩子就被启用了
            this.hook = {
                // 假如有个架构课
                arch: new SyncHook(['name']), // 可能会传一个参数,参数是可选的
            }
        }
    
        // 用来注册的方法
        tap() { // 注册监听函数
            this.hook.arch.tap('vue', function (name) {
                console.log('vue 111', name);
            }) // 拿到钩子实例,实例上就有一个tap方法
    
            this.hook.arch.tap('react', function (name) {
                console.log('react 111', name);
            })
            // 001 调用这个方法时,会将上面两个事件注册到数组中
        }
    
        // 启动钩子的方法
        start() {
            // 执行之前注册的事件
            this.hook.arch.call('cheny') // 实例上有一个call 方法,执行注册的事件
            // 002 调用这个方法时,会让数组中注册的方法依次执行
        }
    }
    
    // 测试
    let l = new Lesson() // 声明一个实例
    // 注册两个事件
    l.tap()
    // 调用start方法 启动钩子
    l.start()
  2. 然后自己仿写了一下,核心就是将事件保存到一个数组中,然后再依次执行。

    js
    class SyncHook { // 同步钩子
    
        constructor(args) { // args -> ['name']
            this.tasks = [] // 保存注册的事件
        }
    
        // 同步注册事件
        tap(name, task) {
            this.tasks.push(task)
        }
    
    
        // 按顺序依次执行注册过的事件
        call(...args) {
            // console.log(this.tasks);
            this.tasks.forEach((task) => {
                task(...args)
            })
        }
    }
    
    
    let hook = new SyncHook(['name']) // 参数可以传可以不传
    
    // 注册事件
    hook.tap('vue', function (name) {
        console.log('vue 1111', name);
    })
    hook.tap('react', function (name) {
        console.log('react 1111', name);
    })
    
    // 依次执行
    hook.call('cheny')

参考

https://www.bilibili.com/video/BV1a4411e7Bz?p=28&spm_id_from=pageDriver