Webpack 核心模块 tapable 解析

Docker安装并运行AnythingLLM的详细教程 通过本文的介绍,你已经掌握了如何在 CentOS 系统上使用 Docker 安装并运行 AnythingLLM。AnythingLLM 提供了强大的功能,支持本地知识库搭建和多模态输入,非常适合个人和企业使用。希望本文能帮助你快速上手并搭建自己的 AI 知识库。 阅读详情

在这里插入图片描述


阅读原文


前言

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器,是对前端项目实现自动化和优化必不可少的工具,Webpack 的 loader(加载器)和 plugin(插件)是由 Webpack 开发者和社区开发者共同贡献的,而目前又没有比较系统的开发文档,想写加载器和插件必须要懂 Webpack 的原理,即看懂 Webpack 的源码,tapable 则是 Webpack 依赖的核心库,可以说不懂 tapable 就看不懂 Webpack 源码,所以本篇会对 tapable 提供的类进行解析和模拟。


tapable 介绍

Webpack 本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 tapable,Webpack 中最核心的,负责编译的 Compiler 和负责创建 bundles 的 Compilation 都是 tapable 构造函数的实例。

打开 Webpack 4.0 的源码中一定会看到下面这些以 Sync、Async 开头,以 Hook 结尾的方法,这些都是 tapable 核心库的类,为我们提供不同的事件流执行机制,我们称为 “钩子”。

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

上面的实现事件流机制的 “钩子” 大方向可以分为两个类别,“同步” 和 “异步”,“异步” 又分为两个类别,“并行” 和 “串行”,而 “同步” 的钩子都是串行的。


Sync 类型的钩子

1、SyncHook

SyncHook 为串行同步执行,不关心事件处理函数的返回值,在触发事件之后,会按照事件注册的先后顺序执行所有的事件处理函数。

// SyncHook 钩子的使用
const { SyncHook } = require("tapable");

// 创建实例
let syncHook = new SyncHook(["name", "age"]);

// 注册事件
syncHook.tap("1", (name, age) => console.log("1", name, age));
syncHook.tap("2", (name, age) => console.log("2", name, age));
syncHook.tap("3", (name, age) => console.log("3", name, age));

// 触发事件,让监听函数执行
syncHook.call("panda", 18);

// 1 panda 18
// 2 panda 18
// 3 panda 18

在 tapable 解构的 SyncHook 是一个类,注册事件需先创建实例,创建实例时支持传入一个数组,数组内存储事件触发时传入的参数,实例的 tap 方法用于注册事件,支持传入两个参数,第一个参数为事件名称,在 Webpack 中一般用于存储事件对应的插件名称(名字随意,只是起到注释作用), 第二个参数为事件处理函数,函数参数为执行 call 方法触发事件时所传入的参数的形参。

// 模拟 SyncHook 类
class SyncHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tap(name, task) {
        this.tasks.push(task);
    }
    call(...args) {
        // 也可在参数不足时抛出异常
        if (args.length < this.args.length) throw new Error("参数不足");

        // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
        args = args.slice(0, this.args.length);

        // 依次执行事件处理函数
        this.tasks.forEach(task => task(...args));
    }
}

tasks 数组用于存储事件处理函数,call 方法调用时传入参数超过创建 SyncHook 实例传入的数组长度时,多余参数可处理为 undefined,也可在参数不足时抛出异常,不灵活,后面的例子中就不再这样写了。

2、SyncBailHook

SyncBailHook 同样为串行同步执行,如果事件处理函数执行时有一个返回值不为空(即返回值为 undefined),则跳过剩下未执行的事件处理函数(如类的名字,意义在于保险)。

// SyncBailHook 钩子的使用
const { SyncBailHook } = require("tapable");

// 创建实例
let syncBailHook = new SyncBailHook(["name", "age"]);

// 注册事件
syncBailHook.tap("1", (name, age) => console.log("1", name, age));

syncBailHook.tap("2", (name, age) => {
    console.log("2", name, age);
    return "2";
});

syncBailHook.tap("3", (name, age) => console.log("3", name, age));

// 触发事件,让监听函数执行
syncBailHook.call("panda", 18);

// 1 panda 18
// 2 panda 18

通过上面的用法可以看出,SyncHook 和 SyncBailHook 在逻辑上只是 call 方法不同,导致事件的执行机制不同,对于后面其他的 “钩子”,也是 call 的区别,接下来实现 SyncBailHook 类。

// 模拟 SyncBailHook 类
class SyncBailHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tap(name, task) {
        this.tasks.push(task);
    }
    call(...args) {
        // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
        args = args.slice(0, this.args.length);

        // 依次执行事件处理函数,如果返回值不为空,则停止向下执行
        let i = 0, ret;
        do {
            ret = this.tasks[i++](...args);
        } while (!ret);
    }
}

在上面代码的 call 方法中,我们设置返回值为 ret,第一次执行后没有返回值则继续循环执行,如果有返回值则立即停止循环,即实现 “保险” 的功能。

3、SyncWaterfallHook

SyncWaterfallHook 为串行同步执行,上一个事件处理函数的返回值作为参数传递给下一个事件处理函数,依次类推,正因如此,只有第一个事件处理函数的参数可以通过 call 传递,而 call 的返回值为最后一个事件处理函数的返回值。

// SyncWaterfallHook 钩子的使用
const { SyncWaterfallHook } = require("tapable");

// 创建实例
let syncWaterfallHook = new SyncWaterfallHook(["name", "age"]);

// 注册事件
syncWaterfallHook.tap("1", (name, age) => {
    console.log("1", name, age);
    return "1";
});

syncWaterfallHook.tap("2", data => {
    console.log("2", data);
    return "2";
});

syncWaterfallHook.tap("3", data => {
    console.log("3", data);
    return "3"
});

// 触发事件,让监听函数执行
let ret = syncWaterfallHook.call("panda", 18);
console.log("call", ret);

// 1 panda 18
// 2 1
// 3 2
// call 3

SyncWaterfallHook 名称中含有 “瀑布”,通过上面代码可以看出 “瀑布” 形象生动的描绘了事件处理函数执行的特点,与 SyncHook 和 SyncBailHook 的区别就在于事件处理函数返回结果的流动性,接下来看一下 SyncWaterfallHook 类的实现。

// 模拟 SyncWaterfallHook 类
class SyncWaterfallHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tap(name, task) {
        this.tasks.push(task);
    }
    call(...args) {
        // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
        args = args.slice(0, this.args.length);

        // 依次执行事件处理函数,事件处理函数的返回值作为下一个事件处理函数的参数
        let [first, ...others] = this.tasks;
        return reduce((ret, task) => task(ret), first(...args));
    }
}

上面代码中 call 的逻辑是将存储事件处理函数的 tasks 拆成两部分,分别为第一个事件处理函数,和存储其余事件处理函数的数组,使用 reduce 进行归并,将第一个事件处理函数执行后的返回值作为归并的初始值,依次调用其余事件处理函数并传递上一次归并的返回值。

4、SyncLoopHook

SyncLoopHook 为串行同步执行,事件处理函数返回 true 表示继续循环,即循环执行当前事件处理函数,返回 undefined 表示结束循环,SyncLoopHook 与 SyncBailHook 的循环不同,SyncBailHook 只决定是否继续向下执行后面的事件处理函数,而 SyncLoopHook 的循环是指循环执行每一个事件处理函数,直到返回 undefined 为止,才会继续向下执行其他事件处理函数,执行机制同理。

// SyncLoopHook 钩子的使用
const { SyncLoopHook } = require("tapable");

// 创建实例
let syncLoopHook = new SyncLoopHook(["name", "age"]);

// 定义辅助变量
let total1 = 0;
let total2 = 0;

// 注册事件
syncLoopHook.tap("1", (name, age) => {
    console.log("1", name, age, total1);
    return total1++ < 2 ? true : undefined;
});

syncLoopHook.tap("2", (name, age) => {
    console.log("2", name, age, total2);
    return total2++ < 2 ? true : undefined;
});

syncLoopHook.tap("3", (name, age) => console.log("3", name, age));

// 触发事件,让监听函数执行
syncLoopHook.call("panda", 18);

// 1 panda 18 0
// 1 panda 18 1
// 1 panda 18 2
// 2 panda 18 0
// 2 panda 18 1
// 2 panda 18 2
// 3 panda 18

通过上面的执行结果可以清楚的看到 SyncLoopHook 的执行机制,但有一点需要注意,返回值必须严格是 true 才会触发循环,多次执行当前事件处理函数,必须严格返回 undefined,才会结束循环,去执行后面的事件处理函数,如果事件处理函数的返回值不是 true 也不是 undefined,则会死循环。

在了解 SyncLoopHook 的执行机制以后,我们接下来看看 SyncLoopHook 的 call 方法是如何实现的。

// 模拟 SyncLoopHook 类
class SyncLoopHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tap(name, task) {
        this.tasks.push(task);
    }
    call(...args) {
        // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
        args = args.slice(0, this.args.length);

        // 依次执行事件处理函数,如果返回值为 true,则继续执行当前事件处理函数
        // 直到返回 undefined,则继续向下执行其他事件处理函数
        this.tasks.forEach(task => {
            let ret;
            do {
                ret = this.task(...args);
            } while (ret === true || !(ret === undefined));
        });
    }
}

在上面代码中可以看到 SyncLoopHook 类 call 方法的实现更像是 SyncHook 和 SyncBailHook 的 call 方法的结合版,外层循环整个 tasks 事件处理函数队列,内层通过返回值进行循环,控制每一个事件处理函数的执行次数。

注意:在 Sync 类型 “钩子” 下执行的插件都是顺序执行的,只能使用 tab 注册。


Async 类型的钩子

Async 类型可以使用 tap、tapAsync 和 tapPromise 注册不同类型的插件 “钩子”,分别通过 call、callAsync 和 promise 方法调用,我们下面会针对 AsyncParallelHook 和 AsyncSeriesHook 的 async 和 promise 两种方式分别介绍和模拟。

1、AsyncParallelHook

AsyncParallelHook 为异步并行执行,通过 tapAsync 注册的事件,通过 callAsync 触发,通过 tapPromise 注册的事件,通过 promise 触发(返回值可以调用 then 方法)。

(1) tapAsync/callAsync

callAsync 的最后一个参数为回调函数,在所有事件处理函数执行完毕后执行。

// AsyncParallelHook 钩子:tapAsync/callAsync 的使用
const { AsyncParallelHook } = require("tapable");

// 创建实例
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);

// 注册事件
console.time("time");
asyncParallelHook.tapAsync("1", (name, age, done) => {
    settimeout(() => {
        console.log("1", name, age, new Date());
        done();
    }, 1000);
});

asyncParallelHook.tapAsync("2", (name, age, done) => {
    settimeout(() => {
        console.log("2", name, age, new Date());
        done();
    }, 2000);
});

asyncParallelHook.tapAsync("3", (name, age, done) => {
    settimeout(() => {
        console.log("3", name, age, new Date());
        done();
        console.timeEnd("time");
    }, 3000);
});

// 触发事件,让监听函数执行
asyncParallelHook.callAsync("panda", 18, () => {
    console.log("complete");
});

// 1 panda 18 2018-08-07T10:38:32.675Z
// 2 panda 18 2018-08-07T10:38:33.674Z
// 3 panda 18 2018-08-07T10:38:34.674Z
// complete
// time: 3005.060ms

异步并行是指,事件处理函数内三个定时器的异步操作最长时间为 3s,而三个事件处理函数执行完成总共用时接近 3s,所以三个事件处理函数是几乎同时执行的,不需等待。

所有 tabAsync 注册的事件处理函数最后一个参数都为一个回调函数 done,每个事件处理函数在异步代码执行完毕后调用 done 函数,则可以保证 callAsync 会在所有异步函数都执行完毕后执行,接下来看一看 callAsync 是如何实现的。

// 模拟 AsyncParallelHook 类:tapAsync/callAsync
class AsyncParallelHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tabAsync(name, task) {
        this.tasks.push(task);
    }
    callAsync(...args) {
        // 先取出最后传入的回调函数
        let finalCallback = args.pop();

        // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
        args = args.slice(0, this.args.length);

        // 定义一个 i 变量和 done 函数,每次执行检测 i 值和队列长度,决定是否执行 callAsync 的回调函数
        let i = 0;
        let done = () => {
            if (++i === this.tasks.length) {
                finalCallback();
            }
        };

        // 依次执行事件处理函数
        this.tasks.forEach(task => task(...args, done));
    }
}

在 callAsync 中,将最后一个参数(所有事件处理函数执行完毕后执行的回调)取出,并定义 done 函数,通过比较 i 和存储事件处理函数的数组 tasks 的 length 来确定回调是否执行,循环执行每一个事件处理函数并将 done 作为最后一个参数传入,所以每个事件处理函数内部的异步操作完成时,执行 done 就是为了检测是不是该执行 callAsync 的回调,当所有事件处理函数均执行完毕满足 done 函数内部 i 和 length 相等的条件时,则调用 callAsync 的回调。

(2) tapPromise/promise

要使用 tapPromise 注册事件,对事件处理函数有一个要求,必须返回一个 Promise 实例,而 promise 方法也返回一个 Promise 实例,callAsync 的回调函数在 promise 方法中用 then 的方式代替。

// AsyncParallelHook 钩子:tapPromise/promise 的使用
const { AsyncParallelHook } = require("tapable");

// 创建实例
let asyncParallelHook = new AsyncParallelHook(["name", "age"]);

// 注册事件
console.time("time");
asyncParallelHook.tapPromise("1", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("1", name, age, new Date());
            resolve("1");
        }, 1000);
    });
});

asyncParallelHook.tapPromise("2", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("2", name, age, new Date());
            resolve("2");
        }, 2000);
    });
});

asyncParallelHook.tapPromise("3", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("3", name, age, new Date());
            resolve("3");
            console.timeEnd("time");
        }, 3000);
    });
});

// 触发事件,让监听函数执行
asyncParallelHook.promise("panda", 18).then(ret => {
    console.log(ret);
});

// 1 panda 18 2018-08-07T12:17:21.741Z
// 2 panda 18 2018-08-07T12:17:22.736Z
// 3 panda 18 2018-08-07T12:17:23.739Z
// time: 3006.542ms
// [ '1', '2', '3' ]

上面每一个 tapPromise 注册事件的事件处理函数都返回一个 Promise 实例,并将返回值传入 resolve 方法,调用 promise 方法触发事件时,如果所有事件处理函数返回的 Promise 实例结果都成功,会将结果存储在数组中,并作为参数传递给 promise 的 then 方法中成功的回调,如果有一个失败就是将失败的结果返回作为参数传递给失败的回调。

// 模拟 AsyncParallelHook 类 tapPromise/promise
class AsyncParallelHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tapPromise(name, task) {
        this.tasks.push(task);
    }
    promise(...args) {
        // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
        args = args.slice(0, this.args.length);

        // 将所有事件处理函数转换成 Promise 实例,并发执行所有的 Promise
        return Promise.all(this.tasks.map(task => task(...args)));
    }
}

其实根据上面对于 tapPromise 和 promise 使用的描述就可以猜到,promise 方法的逻辑是通过 Promise.all 来实现的。

2、AsyncSeriesHook

AsyncSeriesHook 为异步串行执行,与 AsyncParallelHook 相同,通过 tapAsync 注册的事件,通过 callAsync 触发,通过 tapPromise 注册的事件,通过 promise 触发,可以调用 then 方法。

(1) tapAsync/callAsync

与 AsyncParallelHook 的 callAsync 方法类似,AsyncSeriesHook 的 callAsync 方法也是通过传入回调函数的方式,在所有事件处理函数执行完毕后执行 callAsync 的回调函数。

// AsyncSeriesHook 钩子:tapAsync/callAsync 的使用
const { AsyncSeriesHook } = require("tapable");

// 创建实例
let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);

// 注册事件
console.time("time");
asyncSeriesHook.tapAsync("1", (name, age, next) => {
    settimeout(() => {
        console.log("1", name, age, new Date());
        next();
    }, 1000);
});

asyncSeriesHook.tapAsync("2", (name, age, next) => {
    settimeout(() => {
        console.log("2", name, age, new Date());
        next();
    }, 2000);
});

asyncSeriesHook.tapAsync("3", (name, age, next) => {
    settimeout(() => {
        console.log("3", name, age, new Date());
        next();
        console.timeEnd("time");
    }, 3000);
});

// 触发事件,让监听函数执行
asyncSeriesHook.callAsync("panda", 18, () => {
    console.log("complete");
});

// 1 panda 18 2018-08-07T14:40:52.896Z
// 2 panda 18 2018-08-07T14:40:54.901Z
// 3 panda 18 2018-08-07T14:40:57.901Z
// complete
// time: 6008.790ms

异步串行是指,事件处理函数内三个定时器的异步执行时间分别为 1s、2s 和 3s,而三个事件处理函数执行完总共用时接近 6s,所以三个事件处理函数执行是需要排队的,必须一个一个执行,当前事件处理函数执行完才能执行下一个。

AsyncSeriesHook 类的 tabAsync 方法注册的事件处理函数参数中的 next 可以与 AsyncParallelHook 类中 tabAsync 方法参数的 done 进行类比,同为回调函数,不同点在于 AsyncSeriesHook 与 AsyncParallelHook 的 callAsync 方法的 “并行” 和 “串行” 的实现方式。

// 模拟 AsyncSeriesHook 类:tapAsync/callAsync
class AsyncSeriesHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tabAsync(name, task) {
        this.tasks.push(task);
    }
    callAsync(...args) {
        // 先取出最后传入的回调函数
        let finalCallback = args.pop();

        // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
        args = args.slice(0, this.args.length);

        // 定义一个 i 变量和 next 函数,每次取出一个事件处理函数执行,并维护 i 的值
        // 直到所有事件处理函数都执行完,调用 callAsync 的回调
        // 如果事件处理函数中没有调用 next,则无法继续
        let i = 0;
        let next = () => {
            let task = this.tasks[i++];
            task ? task(...args, next) : finalCallback();
        };
        next();
    }
}

AsyncParallelHook 是通过循环依次执行了所有的事件处理函数,done 方法只为了检测是否已经满足条件执行 callAsync 的回调,如果中间某个事件处理函数没有调用 done,只是不会调用 callAsync 的回调,但是所有的事件处理函数都执行了。

而 AsyncSeriesHook 的 next 执行机制更像 Express 和 Koa 中的中间件,在注册事件的回调中如果不调用 next,则在触发事件时会在没有调用 next 的事件处理函数的位置 “卡死”,即不会继续执行后面的事件处理函数,只有都调用 next 才能继续,而最后一个事件处理函数中调用 next 决定是否调用 callAsync 的回调。

(2) tapPromise/promise

与 AsyncParallelHook 类似,tapPromise 注册事件的事件处理函数需要返回一个 Promise 实例,promise 方法最后也返回一个 Promise 实例。

// AsyncSeriesHook 钩子:tapPromise/promise 的使用
const { AsyncSeriesHook } = require("tapable");

// 创建实例
let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]);

// 注册事件
console.time("time");
asyncSeriesHook.tapPromise("1", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("1", name, age, new Date());
            resolve("1");
        }, 1000);
    });
});

asyncSeriesHook.tapPromise("2", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("2", name, age, new Date());
            resolve("2");
        }, 2000);
    });
});

asyncParallelHook.tapPromise("3", (name, age) => {
    return new Promise((resolve, reject) => {
        settimeout(() => {
            console.log("3", name, age, new Date());
            resolve("3");
            console.timeEnd("time");
        }, 3000);
    });
});

// 触发事件,让监听函数执行
asyncSeriesHook.promise("panda", 18).then(ret => {
    console.log(ret);
});

// 1 panda 18 2018-08-07T14:45:52.896Z
// 2 panda 18 2018-08-07T14:45:54.901Z
// 3 panda 18 2018-08-07T14:45:57.901Z
// time: 6014.291ms
// [ '1', '2', '3' ]

分析上面的执行过程,所有的事件处理函数都返回了 Promise 的实例,如果想实现 “串行”,则需要让每一个返回的 Promise 实例都调用 then,并在 then 中执行下一个事件处理函数,这样就保证了只有上一个事件处理函数执行完后才会执行下一个。

// 模拟 AsyncSeriesHook 类 tapPromise/promise
class AsyncSeriesHook {
    constructor(args) {
        this.args = args;
        this.tasks = [];
    }
    tapPromise(name, task) {
        this.tasks.push(task);
    }
    promise(...args) {
        // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined
        args = args.slice(0, this.args.length);

        // 将每个事件处理函数执行并调用返回 Promise 实例的 then 方法
        // 让下一个事件处理函数在 then 方法成功的回调中执行
        let [first, ...others] = this.tasks;
        return others.reduce((promise, task) => {
            return promise.then(() => task(...args));
        }, first(...args));
    }
}

上面代码中的 “串行” 是使用 reduce 归并来实现的,首先将存储所有事件处理函数的数组 tasks 解构成两部分,第一个事件处理函数和存储其他事件处理函数的数组 others,对 others 进行归并,将第一个事件处理函数执行后返回的 Promise 实例作为归并的初始值,这样在归并的过程中上一个值始终是上一个事件处理函数返回的 Promise 实例,可以直接调用 then 方法,并在 then 的回调中执行下一个事件处理函数,直到归并完成,将 reduce 最后返回的 Promise 实例作为 promise 方法的返回值,则实现 promise 方法执行后继续调用 then 来实现后续逻辑。


对其他异步钩子补充

在上面 Async 异步类型的 “钩子中”,我们只着重介绍了 “串行” 和 “并行”(AsyncParallelHook 和 AsyncSeriesHook)以及回调和 Promise 的两种注册和触发事件的方式,还有一些其他的具有一定特点的异步 “钩子” 我们并没有进行分析,因为他们的机制与同步对应的 “钩子” 非常的相似。

AsyncParallelBailHook 和 AsyncSeriesBailHook 分别为异步 “并行” 和 “串行” 执行的 “钩子”,返回值不为 undefined,即有返回值,则立即停止向下执行其他事件处理函数,实现逻辑可结合 AsyncParallelHook 、AsyncSeriesHook 和 SyncBailHook。

AsyncSeriesWaterfallHook 为异步 “串行” 执行的 “钩子”,上一个事件处理函数的返回值作为参数传递给下一个事件处理函数,实现逻辑可结合 AsyncSeriesHook 和 SyncWaterfallHook。


总结

在 tapable 源码中,注册事件的方法 tab、tapAsync、tapPromise 和触发事件的方法 call、callAsync、promise 都是通过 compile 方法快速编译出来的,我们本文中这些方法的实现只是遵照了 tapable 库这些 “钩子” 的事件处理机制进行了模拟,以方便我们了解 tapable,为学习 Webpack 原理做了一个铺垫,在 Webpack 中,这些 “钩子” 的真正作用就是将通过配置文件读取的插件与插件、加载器与加载器之间进行连接,“并行” 或 “串行” 执行,相信在我们对 tapable 中这些 “钩子” 的事件机制有所了解之后,再重新学习 Webpack 的源码应该会有所头绪。


C++和OpenGL实现3D游戏编程【连载7】——文字和汉字的显示(附源码) 上一节我们讨论了纹理在二维平面内不规则图形贴图的相关基础操作,本节我们开始了解游戏里文字以及汉字的显示方法。本节课我们将从基本的ASCII字符显示,拓展到中文字符的显示,最后再讲到纹理字符的显示,并对各种文字显示方法的优缺点和使用场景进行分析,这节课将使用到显示列表、兼容视图、屏幕图形转纹理等操作。 阅读详情

相关推荐

独立游戏开发者的新武器:实测腾讯混元3D自动绑骨,搭配Mixamo让角色动画制作效率翻倍

本文实测腾讯混元3D自动绑骨功能与Mixamo动画库的协同使用,显著提升独立游戏开发者的角色动画制作效率。通过解决卡通角色绑骨偏差、优化骨骼权重及动画状态机配置,使原本两周的流程缩短至三天,并分享Unity中的性能优化与版权合规要点。

weixin_42662605的博客 489

tapable详解

tapable详解 tapable是webpack内部使用的一个流程管理工具,主要用来串联插件,完善事件流执行。 1.安装tapable yarn add tapable 2. 常用hooks import { SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook, AsyncParallelHook, AsyncParallelBailHook, AsyncSeriesHook, A

风火一回,一生不毁 4079

JS实例(三)图片的缓慢放大和缩小

  我们经常能看到在一些网站中可以通过按钮实现对图片的放大或者缩小,也有可能是鼠标经过时图片放大。在本例中就是通过JavaScript实现图片的放大和缩小。   首先是进行简单的页面布局 1 <div class="imagedispose"> 2 <img src="1.jpeg" id="imaged" width="400px" height="3...

weixin_34235105的博客 313

一文介绍 Tapable 的特性和使用,助力理解 webpack 插件架构!

Tapable 提供的这些钩子,支持同步、异步、熔断、循环、waterfall 等功能特性,以此支撑起 webpack 复杂的编译功能,在理解这些内容之后,我们对 webpack 插件架构的设计会有进一步的理解和使用。

梦是太匆 1483

合成孔径雷达(SAR)的常见工作模式

随着星载SAR技术研究的深入发展,单一的条带模式已经无法满足当今时代的需求,因此许多新的工作模式相继被提出。这些新模式在分辨率和测绘宽度等方面都有所提高,接下来将简要介绍几种常用的工作模式。

m0_51390495的博客 2314

结构光三维重建2——多频外差解包裹

本文深入解析了结构光三维重建中的多频外差解包裹技术。针对四步相移法产生的相位包裹问题,该方法通过投影多组不同频率的条纹光,利用外差原理逐级解算,最终获得精确、连续的绝对相位值,是实现高精度、稳健三维重建的关键步骤。

weixin_29056101的博客 350

tapable最详细源码分析及手写实现

tapable整体介绍 tapable 是一个类似于 Node.js 中的 EventEmitter的库,但更专注于自定义事件的触发和处理。 const { SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook, AsyncParallelHook, AsyncParallelBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook, Asyn

qq_35385241的博客 1439

QGIS中高效处理CAD数据的两种实用技巧

本文详细解析了在QGIS中高效处理CAD数据的两种核心方法。针对DWG/DXF文件在QGIS中直接打开失败的问题,介绍了使用QGIS内置数据导入管理器进行标准化转换的流程,并重点推荐了功能更强大的Another DXF Importer插件,以更好地保留文字、块属性等复杂信息。文章对比了两种方法的优劣与适用场景,并提供了坐标系设置、中文乱码等常见问题的避坑指南,帮助用户根据数据需求选择最佳导入策略。

weixin_28363123的博客 117

基于数据结构从源码解析webpack核心模块tapable

webpack作为前端打包的重要工具之一,我们有必要深入掌握webpack的底层数据结构。官方文档上直接给出了webpack的核心模块tapable,但是并没有给出特别详细的介绍其实现原理和内部实现,本文从源码触发,来解析下tapable的数据结构和方法属性,这对于掌握webpack打包机制的理解是很有帮助的。 总览: hook.js数据结构: constructor(args) ...

前端361 412

txt经纬度批量转面要素(附自定义ArcGIS工具箱方法)

当涉及大量txt坐标文件需要转换成面要素的时候,逐步操作工作量较大。本文由需求出发,以ArcGIS10.2为例,采用python脚本自定义ArcGIS工具,实现批量操作的目的。

m0_59216200的博客 1818

基于POD和神经网络的多工况温度场重构反演系统

本文提出了一种基于本征正交分解(POD)和神经网络的多工况温度场重构反演系统。该系统通过POD方法从多工况温度场数据中提取主要特征模态,构建神经网络模型建立工况参数与POD模态系数的映射关系,实现温度场的快速重构。系统采用Python实现,包含数据预处理、POD分解、神经网络训练和温度场重构等模块。实验结果表明,该方法能够有效降低计算复杂度,实现高精度的温度场预测。

huanghm88的专栏 274

Webpack 核心模块 tapable 解析(转)

原文出自:https://www.pandashen.com 前言 Webpack 是一个现代 JavaScript 应用程序的静态模块打包器,是对前端项目实现自动化和优化必不可少的工具,Webpack 的 loader(加载器)和 plugin(插件)是由 Webpack 开发者和社区开发者共同贡献的,而目前又没有比较系统的开发文档,想写加载器和...

121

【CT/电镜 重建教程 1.1】层析成像的精确实空间迭代重建算法(RESIRE)

在此,我们报告了RESIRE算法的发展用于准确的断层重建。与实空间迭代算法相比,RESIRE使用傅里叶切片定理或Radon变换作为正向投影,使用线性变换作为反向投影,两者都具有高精度。与EST和GENFIRE相比,RESIRE是一种实空间迭代算法,可以在并行计算中实现快速运行时间。

2401_87064292的博客 1060

webpack工作原理

抛开 webpack 复杂的 loader 和 plugin 机制,webpack 本质上就是一个 JS 模块 Bundler,用于将多个代码模块进行打包,所以我们先撇开 webpack 错综复杂的整体实现,来看一下一个相对简单的 JS 模块 Bunlder 的基础工作流程是怎么样的,在了解了 bundler 如何工作的基础上,再进一步去整理 webpack 整个流程,将 loader 和 plugin 的机制弄明白。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。

江拥羡橙的博客 631

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

webpack详解

webpack是现代前端开发中最火的模块打包工具,只需要通过简单的配置,便可以完成模块的加载和打包。那它是怎么做到通过对一些插件的配置,便可以轻松实现对代码的构建呢? webpack的配置 const path = require('path'); module.exports = { entry: "./app/entry", // string | object | array // Webpack打包的入口 output: { // 定义webpack如何输出的选项 pat

包磊磊的博客 1560

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Tapable之index.js 源码分析

package.json "main":"lib/index.js", "version":"1.1.3" 路径:/node_modules/webapck/node_modules/tapable index.js 主要是向外暴露了一些东西 "use strict"; exports.__esModule = true; exports.Tapable = require("./Tapable"); exports.SyncHook = require("./SyncHoo..

lzz的博客 224

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

webpack的运行机制详解

webpack是现代前端开发中最火的模块打包工具,只需要通过简单的配置,便可以完成模块的加载和打包。那它是怎么做到通过对一些插件的配置,便可以轻松实现对代码的构建呢?we...

frontend_frank的博客 1142

Webpack是如何实现打包的?

Webpack作为现代前端构建工具,通过模块整合、类型转换和兼容处理三大核心能力,解决浏览器原生不支持模块化及非JS文件解析的问题。其打包流程包含初始化配置、依赖解析、模块转换、Chunk生成、优化和输出六个关键步骤,借助Tapable插件系统、Loader机制和自定义模块加载实现高效构建。

1067

Webpack Loader 与 Plugin 实现原理深度解析

维度LoaderPlugin作用阶段模块转换阶段整个构建生命周期输入输出处理文件内容访问/修改编译对象执行方式链式管道处理事件驱动编程模型典型应用文件转译、编译资源优化、环境注入复杂度相对简单需要理解编译过程。

Yue 1194

webpack源码

打包后源码分析 path: path.resolve(‘dist’) 为输出目录的路径 对输出文件进行分析 1.模块定义 “./src/index.js”: //路径模块id 视为键值对 (function (module, exports) { console.log(‘index.js内容’) module.exports = ‘入口文件导出内容’ //模块定义 }) //值是一个函数的调用 //传递模块的参数 打包后的文件就是一个函数自调用,当前函数调用时传入一个对象 这个对象我们为了方便将

weixin_38255079的博客 533

webpack——从基础使用到手动实现

写到前面:故虽然我事前也查阅了许许多多的博客专栏书籍,但是由于很多教程都是几年以前的,其不少插件或loader均进行了升级迭代不再是以前的用法,其新的使用方法均需一个一个手动查询。如有误,望指教。感恩! ❞ 写在开头 我想这些你应该事先知道 1. webpack是干什么的,它如今在前端是一个什么样的地位 这个其实不用多说了,如今的前端项目哪里还有用不到打包工具的呢,而webpack又作为打包工具中的王者我们又有什么理由不去搞明白它呢。 2. commonjs与ES6 Module不应该了解吗..

大灰狼的小绵羊哥哥的博客 771

Webpack面试核心解析与高频考点深度剖析

模块化打包是现代前端工程化的核心技术之一,其核心原理是通过依赖图(dependency graph)分析资源间的引用关系,最终输出优化后的静态资源。Webpack作为主流打包工具,通过Loader机制实现文件转换,利用Plugin系统扩展构建流程,支持Tree Shaking等高级优化功能。在工程实践中,Webpack的HMR热更新能显著提升开发效率,而代码分割和持久化缓存则解决了大型应用的性能瓶颈。本文以面试高频考点为线索,系统讲解从基础配置到模块联邦(Module Federation)等高级特性的完整

weixin_30367873的博客 401

Webpack生命周期机制全解析:Tapable、Compiler与Compilation详解

前端构建工具正在从单向流水线进化为高度可扩展的平台,Webpack正是其中的代表。它通过一套完善的生命周期机制,将打包过程拆解为多个可插拔的钩子节点,让开发者能在关键阶段注入自定义逻辑。这套机制的底层是Tapable事件流库,它提供同步/异步、串行/并行等多种Hook,精确控制回调执行顺序。在生命周期中,Compiler管理一次构建的宏观流程,Compilation则负责具体模块的解析与优化,二者协同完成打包全链路。理解这一运行脉络,不仅能帮助开发者编写健壮的插件,还能精准定位构建性能瓶颈,从模块解析、资源

weixin_28829903的博客 236

Webpack 生命周期原理深度解析

插件化架构:基于 Tapable 的事件订阅/发布模式阶段化处理:初始化→编译→优化→输出的清晰流程高度可扩展:200+ 个钩子覆盖构建的每个细节性能优化:支持增量构建、缓存、并行处理开发更高效的 Webpack 插件优化构建性能和打包结果实现定制化的构建流程调试复杂的构建问题掌握生命周期原理是成为 Webpack 高级开发者的关键一步,也是理解现代前端工程化架构的基础。原文:https://juejin.cn/post/7593702885881200676。

W1391517383的博客 688
上一篇: NodeJS 多进程和集群
下一篇: MySQL —— 数据库基础
Panda Shen
博客等级 码龄10年 18粉丝 50原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值