webpack 的核心模块:Tapable 的核心 同步方法 模拟实现

255 阅读2分钟

# 前言

Webpack本质上是一种事件流的机制,他的工作流程就是将各个插件串联起来,而实现这一切的核心就是TapableTapable的核心原理是依赖于发布订阅模式,它有几个核心的方法,我们简单的来模拟实现下。

# SyncHook: 同步钩子

SyncHook.js 文件

// 同步的钩子
class SyncHook {
    constructor(args) {
        this.tasks = [];
    };
    /**
     * 注册监听函数
     * @param {String} name : 名称
     * @param {Function} task : 任务函数
     */
    tap(name, task) {
        // 这里相当于订阅
        this.tasks.push(task);
    };
    /**
     * 执行注册的函数
     * @param  {...any} args
     */
    call(...args) {
        this.tasks.forEach(task => task(...args))
    }
}

module.exports = { SyncHook };

写一个调用该钩子的模拟学习的模块文件:Lesson.js

Lesson.js 文件

//引入 同步钩子函数
const { SyncHook } = require('./Tapable/SyncHook');

// 模拟学习的课程
class Lesson {
    constructor() {
        this.hooks = {
            // 通过 new 创建实例后会拿到 SyncHook 中的两个实例方法:tap 和 call
            test: new SyncHook(['name'])
        }
    };
    // 注册钩子
    tap() {
        this.hooks.test.tap('Vue', function(name) {
            console.log('Vue', name)
        })
        this.hooks.test.tap('Html', function(name) {
            console.log('Html', name)
        })
        
        // 这里可以按需写多个
    };
    // 启动钩子
    start() {
        this.hooks.test.call('zp');
    };
}

let lesson = new Lesson();

lesson.tap(); // 注册钩子 / 事件
lesson.start(); // 启动钩子,会分别输出 Vue zp 和 Html zp

# SyncBailHook:保险钩子

SyncBailHook.js 文件

/**
 * 同步的保险钩子
 * 任何一个监听函数返回了非undefined的结果就会停止执行
 */
class SyncBailHook {
    constructor(args) {
        this.tasks = [];
    };
    /**
     * 注册监听函数
     * @param {String} name : 名称
     * @param {Function} task : 任务函数
     */
    tap(name, task) {
        // 这里相当于订阅
        this.tasks.push(task);
    };
    /**
     * 执行注册的函数
     * @param  {...any} args
     */
    call(...args) {
        let ret; // 当前函数的返回值
        let index = 0; // 默认先执行第一个
        do {
            ret = this.tasks[index++](...args);
        } while (ret === undefined && index < this.tasks.length)
    }
}

module.exports = { SyncBailHook };

同样的Lesson.js文件,引用的是SyncBailHook.js文件,如下:

Lesson.js 文件

const { SyncBailHook } = require('./Tapable/SyncBailHook');

// 模拟学习的课程
class Lesson {
    constructor() {
        this.hooks = {
            test: new SyncBailHook(['name'])
        }
    };
    // 注册钩子
    tap() {
        this.hooks.test.tap('Vue', function(name) {
            console.log('Vue', name)
            return '芭比Q了'
        })
        this.hooks.test.tap('Html', function(name) {
            console.log('Html', name)
        })
    };
    // 启动钩子
    start() {
        this.hooks.test.call('zp');
    };
}

let lesson = new Lesson();

lesson.tap(); // 注册钩子/事件
lesson.start(); // 启动钩子 

因第一个返回了非undefined,所只会输出 Vue zp,且不会执行第二个

# SyncWaterfallHook:瀑布方法

SyncWaterfallHook.js 文件

// 瀑布流式的同步钩子
class SyncWaterfallHook {
    constructor(args) {
        this.tasks = [];
    };
    /**
     * 注册监听函数
     * @param {String} name : 名称
     * @param {Function} task : 任务函数
     */
    tap(name, task) {
        // 这里相当于订阅
        this.tasks.push(task);
    };
    /**
     * 执行注册的函数
     * @param  {...any} args
     */
    call(...args) {
        let [first, ...others] = this.tasks;
        let red = first(...args);
        // 迭代执行
        others.reduce((pre, curren) => {
            return curren(pre);
        }, red)
    }
}

module.exports = { SyncWaterfallHook };

同样的Lesson.js文件,引用的是SyncWaterfallHook.js文件,如下:

Lesson.js 文件

const { SyncWaterfallHook } = require('./Tapable/SyncWaterfallHook');

// 模拟学习的课程
class Lesson {
    constructor() {
        this.hooks = {
            test: new SyncWaterfallHook(['name'])
        }
    };
    // 注册钩子
    tap() {
        this.hooks.test.tap('Vue', function(name) {
            console.log('Vue', name)
            return '开始下一个'
        })
        this.hooks.test.tap('Html', function(data) {
            console.log('Html', data)

        })
    };
    // 启动钩子
    start() {
        this.hooks.test.call('zp');
    };
}

let lesson = new Lesson();

lesson.tap(); // 注册钩子/事件
lesson.start(); // 启动钩子

会输出 Vue zp 开始下一个 Html

# SyncLoopHook:循环执行的钩子

SyncLoopHook.js 文件

/**
 * 循环执行的钩子
 * 遇到某个不返回undefined的监听函数会多次执行
 */
class SyncLoopHook {
    constructor(args) {
        this.tasks = [];
    };
    /**
     * 注册监听函数
     * @param {String} name : 名称
     * @param {Function} task : 任务函数
     */
    tap(name, task) {
        // 这里相当于订阅
        this.tasks.push(task);
    };
    /**
     * 执行注册的函数
     * @param  {...any} args
     */
    call(...args) {
        this.tasks.forEach(task => {
            let ret;
            do {
                ret = task(...args);
            } while (ret != undefined)
        })
    }
}

module.exports = { SyncLoopHook };

同样的Lesson.js文件,引用的是SyncLoopHook.js文件,如下:

Lesson.js 文件

const { SyncLoopHook } = require('./Tapable/SyncLoopHook');

// 模拟学习的课程
class Lesson {
    constructor() {
        this.hooks = {
            test: new SyncLoopHook(['name'])
        }
    };
    // 注册钩子
    tap() {
        let index = 0;
        this.hooks.test.tap('Vue', function(name) {
            console.log('Vue', name);
            return ++index === 5 ? undefined : 'go on';
        })
        this.hooks.test.tap('Html', function(data) {
            console.log('Html', data)

        })
    };
    // 启动钩子
    start() {
        this.hooks.test.call('zp');
    };
}

let lesson = new Lesson();

lesson.tap(); // 注册钩子/事件
lesson.start(); // 启动钩子

会输出 vue zp vue zp vue zp vue zp vue zp Html zp

几个核心模块的方法到这里就模拟实现完成了,nice ~~