Webpack源码分析(3)--之 Webpack的灵魂强大的事件流Tapable(I)--同步 hook

Webpack源码分析(3)--之 Webpack的灵魂强大的事件流Tapable(I)--同步 hook

原本这篇是要说 resolve 实现的,但我发现讲 resolve 以前不把 Tapable 讲清楚,是搞不明白,这篇是最重要的,其余能够跳过,这个但愿正在看的读者,仔细看看,搞明白了,下次面试,人家问你什么事件流,发布订阅,观察者模式啊,你把这篇吹一波,确定加分很多(额,牛皮仍是要吹的)javascript

生命周期的概念

先拉远些,咱们看三大框架,都有个生命周期的概念,在生命周期暴露些hook,生命周期说白了就是对一个流程的抽象,就像一个小河里的水流,通过一个个大坝,规定有的大坝处理下垃圾,有的来个分流到小河里灌溉,有的来防洪,哪一个大坝能够捕鱼,定义好每一个大坝要干的事,那周围的人就知道本身干什么事的时候,到哪一个大坝去就行了vue

当你在写一个复杂的逻辑的,特别是暴露给不少人用的库时,必定要先抽象出个生命周期的流程出来,会方便不少,扩展也容易不少(我在这方面就不足,常常一顿乱写,每次升级别人依赖个人东西时候,都得手动去改代码,灵活性不足,正在慢慢学习使用这个思惟)java

事件流的概念

注意哈,这里的事件不是单纯的指 dom元素的事件,指的是程序的逻辑的关系,nodejs 有个 EventEmitter ,EventEmitter 的核心函数有两个,向事件集合中添加函数的event.on()以及用于触发的函数event.emit()。这个本质上是个发布订阅,先列几个常见的业务场景node

  • 你在写一套通用的cli的时候,每一个bu,小组有不一样模版要求,有的须要先建立远程git仓库,有的须要打包后发布到 cdn 等定制化需求,你怎么样作到可插拔,而不是一坨坨 if else
  • 商品组件加入购物车后,须要触发 头部购物车数字状态改变,购物车金额数字改变,优惠券组件展现,左侧其余商品组件初始化,这些触发相互不影响的
  • step by step,新功能引导流程, 组件间状态不依赖
  • 复杂些的审批流程,下一个组件的状态依赖上一个组件的返回值数据
  • 你须要写一个移动端用户认证的服务,这个服务总共分为2部分,资料审核上传,审核经过后展现信息,可是,游戏BU的资料审核只须要上传个身份证图片,信贷 BU 的要经历摄像头拍照,眨眼睛,说话,小说 BU 和在信贷 BU 的基础上还多了游戏部门的身份证环节

上面是一些具体的业务场景,读者能够先想一想这些你遇到了会怎么解决webpack

咱们回到Webpack,Webpack 扩展了 EventEmitter ,把全部的逻辑抽象成各类 plugins ,要 打包 TypeScipt,加个对应的 loader,这些loader都是对应的 plugin,而后挂载上去,须要 处理编译后的文件,自定义个 new Plugin 来挂载,上面说的几个业务场景,Webpack 构建过程当中都有遇到,并且比上面复杂的多流程都有,那咱们理解了 Webpack 实现思想,在去处理 咱们工做中遇到的具体业务逻辑,就很是方便了,这也是网上说的,多看源码,理解源码,为本身所用的缘由git

事件流最大的好处就是代码耦合性小,把逻辑独立到个个子模块,可插拔web

Webpack 80%的代码都是经过 plugins 的方式挂载的,外带第三方 plugins,loader,同时经过 同样挂载,我数了下 Webpack文档,compiler暴露了 24 个 hook,compilation 暴露了 65 个 hook ,要把这么复杂的打包流程组装起来,webpack 就是基于事件流开发了一个独立的骨架,这个骨架就是 Tapable,Tapable就是Webpack的大脑面试

总于说到Tapable了,下面就具体看下 Tapable 把事件流定义了那几个类型,Tapable的讲解,分为3部分框架

  • 同步事件流
  • 异步事件流
  • 组合事件流

tapable.png-774.3kB

先说说最简单的 sync 实现dom

SyncHook

SyncHook的特色是,每一个注入的方法不关注返回值,同步的串行的执行每一个方法

看段具体代码

const { SyncHook } = require("tapable");
class MyVue {
    constructor() {
        this.hooks = {
            beforeCreate: new SyncHook(["beforeCreateHook"]),
            created: new SyncHook(["createdHook"]),
            mounted: new SyncHook(),
            destroyed: new SyncHook(),
        };
    }
    defaultBeforeCreateHook(){
        this.hooks.beforeCreate.tap('1', (name) => {
        console.log('default', name);
        })
    }
    beforeCreate() {
        console.log('准备初始化MyVue实例了')
        //....这里框架干了一堆事 ,就经过 hook 把使用者注入代码执行完成了
        this.hooks.beforeCreate.call('MyVue')
    }
    created() {
        console.log('干点其余事,唤起hook created ')
        this.hooks.created.call(this)
    }
    init() {
        //..... 干一堆事
        this.beforeCreate()
        //..... 再干一堆事
        this.created()
    }
}
const vueInstance = new MyVue()
vueInstance.hooks.beforeCreate.tap('1', (name) => {
    console.log('hello', name);
})
vueInstance.hooks.beforeCreate.tap('2', (name) => {
    console.log('Wellocome', name);
})
vueInstance.init()

输出以下:
准备初始化MyVue实例了
hello MyVue
Wellocome MyVue
干点其余事,唤起hook created 
复制代码

这里模仿 Vue 定义了几个生命周期,这样是否是就把代码解藕了,在本身项目中就能够经过这种方式,把外部传入的逻辑和本身框架的逻辑剥离出来, defaultBeforeCreateHook 方法就能够作到可插拔,在你框架逻辑变化的时候,就改变 default* 这个方法就好 咱们日常有写 Webpack 插件的话,都是这种方式挂载上去的

SyncHook 的特色是,每一个注入的方法不关注上个方法返回值,同步的串行的执行每一个方法,下面简单实现 SyncHook

class Hook{
    constructor(args){
        this.taps = []
        this.interceptors = [] // 这个放在后面用
        this._args = args 
    }
    tap(name,fn){
        this.taps.push({name,fn})
    }
}
class SyncHook extends Hook{
    call(name,fn){
        try {
            this.taps.forEach(tap => tap.fn(name))
            fn(null,name)
        } catch (error) {
            fn(error)
        }
        
    }
}
复制代码

看到 this.taps.forEach(tap => tap.fn(name)),这里不关注上个方法返回值

SyncWaterfallHook

SyncWaterfallHook 和 SyncHook 的差异是 SyncWaterfallHook 注入的方法,上一个方法的返回值会作为下个方法的参数传入,这个和咱们最上面说的那个业务场景

复杂些的审批流程,下一个组件的状态依赖上一个组件的返回值数据

是否是就很合适的套上了,SyncWaterfallHook 实现以下

class SyncWaterfallHook extends Hook{
    call(name,fn){
        try {
           let result =  this.taps.reduce((result,tap) => tap.fn(name),null)
            fn(null,result)
        } catch (error) {
            fn(error)
        }
        
    }
}
复制代码

SyncBailHook

假设有一方法返回非 undefined,跳过剩下的 tap,当即执行回调,实现以下

class SyncBailHook extends Hook{
    call(name,fn){
        try {
            let result = this.taps.reduce((result,tap) => {
                if(result != undefined){
                    fn(null,result)
                }else{
                    return  tap.fn(name)
                }
            },null)
             fn(null,result)
        } catch (error) {
            fn(error)
        }
    }
}
复制代码

这个Hook比较适合在 知足某些条件的状况下,执行某个逻辑,而这些条件是当下不能肯定的,须要使用者来定义,传入框架的,说的有点绕,我不知道怎么去描述这个场景,能够简单的想成,多条件判断,只要一个返回 true,就执行具体逻辑

SyncLoopHook

若是当前方法返回 非undefined,一直执行这个方法,只有当返回 undefined 跳出循环,执行下一个方法,这个我目前没有想到使用场景,实现方式以下

class SyncLoopHook extends Hook {
    call(name, fn) {
        try {
            this.taps.forEach(tap => {
                let result;
                do {
                    result = tap.fn(name)
                } while (result !== undefined);
            })
            fn(null)
        } catch (error) {
            fn(error)
        }
    }
}
复制代码

同步 hook 写完,理解学会这几种用法,在日常工做中能够大大的减小代码耦合,值得深刻理解学习 下篇咱们讲下异步Hook

相关文章
相关标签/搜索