Webpack 进阶之源码分析(二)

本文主要讲解 webpacktapablenode

上一篇文章咱们分析当执行 webpack 命令的时候,会从 webpack 目录执行到 webpack-cli 目录,最终又回到 webpack 目录,这一篇文章就主要讲解回到 webpack 目录中又作了哪些事情?webpack

首先打开 webpack/lib/webpack.js 文件,找到 webpack 方法。方法内部首先会验证 options 是否合法,而后会根据传入的 options 是否为数组,生成不一样的 compiler 对象。web

若是为非数组则实例化 CompilerCompiler 对象继承 Tapable,若是是数组则实例化 MultiCompiler,数组每一项均实例化 Compiler。最终给生成 compiler 对象注入 plugins,再根据 target,选择性注入插件。好比 targetweb 时,注入 xxx 插件,最后视参数调用 run 方法仍是 watch 方法。数组

Tapable

因为笔者未用过 Tapable,因此先讲解用法,对使用已掌握的童鞋能够直接忽略,对 tapable 源码不熟的童鞋能够直接拉到最后。promise

Tapable 是一个相似于 Node.jsEventEmitter 的库,专一于事件的触发与处理,同时它也是 webpack 出的一个小型的库,它容许你建立钩子,为钩子挂载函数,最后调用挂载函数,其更像一个发布-订阅系统。异步

Tapable 共有 9 种钩子,共分为 2 大类,一类为 sync 同步钩子,一类为 async 异步钩子。sync 同步钩子分为 SyncHookSyncBailHookSyncWaterfallHookSyncLoopHook 4 种。async 异步钩子分为 AsyncParallelHookAsyncParallelBailHookAsyncSeriesHookAsyncSeriesBailHookAsync SeriesWaterfallHook 5 种。同步的钩子,只能经过 tap 调用,而异步的钩子则能够经过 tapPromisetapAsynctap 来调用。async

const { SyncHook } = require("tapable");
const hook = new SyncHook();
hook.tap("subscribe", () => {
    console.log("subscribe");
});

hook.call(); // subscribe
复制代码

使用

SyncHook

SyncHook 是最基础的钩子,它能够挂载多个函数,挂载的函数依次执行,没有返回值。函数

const syncHook = new SyncHook();
syncHook.tap("syncHook1",() => {
    console.log("syncHook1");
});
syncHook.tap("syncHook2",() => {
    console.log("syncHook2");
});
syncHook.call();
// syncHook1
// syncHook2
复制代码

SyncWaterfallHook

SyncWaterfallHook 能够挂载多个函数,函数依次执行,下一个挂载函数会利用上一个函数的返回值做为参数。oop

const syncWaterfallHook = new SyncWaterfallHook(["arg1"]);
syncWaterfallHook.tap("syncWaterfallHook1", arg1 => {
    console.log("syncWaterfallHook1", arg1);
    return arg1 + 1;
});
syncWaterfallHook.tap("syncWaterfallHook2", arg1 => {
    console.log("syncWaterfallHook2", arg1);
    return arg1 + 1;
});
syncWaterfallHook.call(1);
// syncWaterfallHook1 1
// syncWaterfallHook2 2
复制代码

syncBailHook

syncBailHook 能够挂载多个函数,函数依次执行,当有一个函数有返回值,则接下来的函数都不会执行。源码分析

const syncBailHook = new SyncBailHook();
syncBailHook.tap("syncBailHook1", () => {
    console.log("syncBailHook1");
    return false;
});
syncBailHook.tap("syncBailHook2", () => {
    console.log("syncBailHook2");
});
syncBailHook.call();
// syncBailHook1
复制代码

SyncLoopHook

当监听的函数返回 true,会一直执行此函数,直至返回 false

let syncLoopHook = new SyncLoopHook();
let count = 3;
syncLoopHook.tap("syncLoopHook1", () => {
    console.log(count);
    if (count) {
        count--;
        return true;
    }
    return;
});
syncLoopHook.call();
// 3
// 2
// 1
// 0
复制代码

AsyncSeriesHook

AsyncSeriesHook 异步钩子,能够挂载多个函数,每一个函数都会等待上一个函数执行完,再执行。

const asyncSeriesHook = new AsyncSeriesHook(["arg"]);
asyncSeriesHook.tapPromise("asyncSeriesHook1", arg => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("asyncSeriesHook1", arg);
            resolve(arg + 1);
        }, 1000);
    });
});
asyncSeriesHook.tapPromise("asyncSeriesHook2", arg => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("asyncSeriesHook2", arg);
        }, 1000);
    });
});
asyncSeriesHook.callAsync(1);
// asyncSeriesHook1 1
// asyncSeriesHook2 1
复制代码

AsyncSeriesBailHook

AsyncSeriesBailHook 异步钩子,能够挂载多个函数,只要一个异步函数 reject 或异步函数有返回值,则直接进入回调函数执,且后续异步再也不执行。若是异步执行结果 undefined,则执行下一个函数。

const asyncSeriesBailHook = new AsyncSeriesBailHook(["arg1"]);
asyncSeriesBailHook.tapPromise("asyncSeriesBailHook1", arg1 => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("asyncSeriesBailHook1", arg1);
            resolve();
        }, 1000);
    });
});
asyncSeriesBailHook.tapPromise("asyncSeriesBailHook2", arg1 => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("asyncSeriesBailHook2", arg1);
            resolve(arg1 + 1);
        }, 1000);
    });
});
asyncSeriesBailHook.tapPromise("asyncSeriesBailHook3", arg1 => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("asyncSeriesBailHook3", arg1);
        }, 1000);
    });
});
asyncSeriesBailHook.callAsync(1, err => {
    console.log("err", err);
});
// asyncSeriesBailHook1 1
// asyncSeriesBailHook2 1
// err null
复制代码

AsyncSeriesWaterfallHook

AsyncSeriesWaterfallHook 异步钩子,能够挂载多个函数,只要有一个异步函数 reject ,则直接进入回调函数执行,且后续异步再也不执行。若是异步函数有返回值,则会把返回值传入到下一个函数。

const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook(["arg1"]);
asyncSeriesWaterfallHook.tapPromise("AsyncSeriesWaterfallHook1", arg1 => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("AsyncSeriesWaterfallHook1", arg1);
            resolve(arg1 + 1);
        }, 2000);
    });
});
asyncSeriesWaterfallHook.tapPromise("AsyncSeriesWaterfallHook2", arg1 => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log("AsyncSeriesWaterfallHook2", arg1);
            resolve();
        }, 2000);
    });
});
asyncSeriesWaterfallHook.callAsync(1, err => {
    console.log(err);
});
// AsyncSeriesWaterfallHook1 1
// AsyncSeriesWaterfallHook2 2
// null
复制代码

AsyncParallelHook

AsyncParallelHook 异步钩子,能够挂载多个函数,同时触发异步函数执行,和 Promise.all 相似,只要有一个异步函数错误,总体结果就错误。

const asyncParallelHook = new AsyncParallelHook(["arg1"]);
asyncParallelHook.tapPromise("asyncParallelHook1", arg1 => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("asyncParallelHook1", arg1);
            resolve(arg1 + 1);
        }, 1000);
    });
});
asyncParallelHook.tapPromise("asyncParallelHook2", arg1 => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("asyncParallelHook2", arg1);
            reject(arg1 + 1);
        }, 1000);
    });
});
asyncParallelHook.callAsync(1, err => {
    console.log(err);
});
//asyncParallelHook1 1
//asyncParallelHook2 1
// 2
复制代码

AsyncParallelBailHook

AsyncParallelBailHookAsyncParallelHook 特性同样。惟一不同的就是,若是绑定的第一个异步函数出错,那总体状态就出错,成功则成功,并立马执行回调函数。不以其他绑定的异步函数成功与否做为依据。

const asyncParallelBailHook = new AsyncParallelBailHook(["arg"]);
asyncParallelBailHook.tapPromise("asyncParallelBailHook1", arg => {
    return new Promise((resolve, reject) => {
        console.log("asyncParallelBailHook1", arg);
        reject(arg + 1);
    });
});
asyncParallelBailHook.tapPromise("asyncParallelBailHook2", arg => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("asyncParallelBailHook2", arg);
            resolve(arg + 1);
        }, 100);
    });
});
asyncParallelBailHook.tapPromise("asyncParallelBailHook3", arg => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("asyncParallelBailHook3", arg);
            resolve(arg + 1);
        }, 1000);
    });
});
asyncParallelBailHook.promise(1).then(
    () => {
        console.log("successful");
    },
    () => {
        console.log("error");
    }
);
//asyncParallelBailHook1 1
// error
// asyncParallelBailHook2 1
// asyncParallelBailHook3 1
复制代码

源码分析

打开 node_modules/lib/index.js 文件,能够看到 Tapable 向外界暴露的不止 9 个钩子,还有 TapableHookMapMultiHook。这三个不是重点,咱们忽略它。

全部钩子都继承 Hook 类,每一个钩子的方法基本上彻底同样,都有 compile 方法,只有两点比较例外。

  1. AsyncSeriesWaterfallHookSyncWaterfallHook 重写了 Hook 的构造函数
  2. 同步钩子全都重写了 tapAsynctapPromise 那当咱们绑定一个钩子时候,Tapable 作了哪些呢?Tapable 会根据传入的参数生成 options,并把生成的 options 对象插入到 taps 属性上。

当经过 callcallAsyncpromise 调用钩子时候,最终会调用各自类上重写后的 compile 方法。

compile(options) {
	factory.setup(this, options);
	return factory.create(options);
}
复制代码

factory 值从何而来,它是每个钩子对应的 xxxHookCodeFactory 类的实例,全部的 xxxHookCodeFactory 类均继承 HookCodeFactory。当咱们调用的时候,HookCodeFactory 内会组装执行代码,组装完成后便执行此段代码。

具体每个钩子的原理,我以为webpack4.0 源码分析之 Tapable就写得十分不错,这里就不赘述了。

总结

参考文献

最后

笔者建议按照顺序食用,效果更佳哦。若是本文对您有所帮助,还请不要吝啬您的点赞,每个点赞都是对笔者最大的鼓励。

相关文章
相关标签/搜索