zhangzheng-zz / blog

1 stars 0 forks source link

webpack源码 #14

Open zhangzheng-zz opened 3 years ago

zhangzheng-zz commented 3 years ago

https://juejin.cn/post/6844903987129352206

webpack4 流程写得较好的: https://github.com/6fedcom/fe-blog

zhangzheng-zz commented 3 years ago

compile 核心 compiler.run 启动编译

webpack.js

如果传入了 callback 会在 compiler.run执行 之后执行 callback,否则是返回 compiler 最后会开启 compiler.run 进行编译。 compiler 做了什么? 注册插件,开启钩子。https://webpack.docschina.org/api/compiler-hooks/ 钩子是什么?run 是什么类型的钩子?

webpack 插件核心:tapable

tapable

Hook 用法:https://juejin.cn/post/6844903895584473096

GitHub: https://github.com/webpack/tapable

tapable翻译:https://segmentfault.com/a/1190000017420937

zhangzheng-zz commented 3 years ago

tapable 暴露了很多钩子函数,这些钩子函数的基类都是 Hook

SyncHook

钩子类的编写基本一致,基于Hook创造自己的类,修饰类并实现自己的compile方法,compile是为了生成call方法,理解流水线。

const hook = new Hook(args, name);
// 修饰 hook 构造函数为类 SyncHook
hook.constructor = SyncHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
// compile
hook.compile = COMPILE;

Hook

发布订阅模式:tap注册 call触发 Hook分为同步异步:SyncHook AsyncSeriesHook Async Hook 三种注册方式: sync、async、promise

tap 如何保证顺序?

let sh = new SyncHook(["name"]);
// 注册
sh.tap("A", name => {
    console.log("A:", name);
});
sh.tap("B", name => {
    console.log("B:", name);
});
sh.tap(
    {
        name: "C",
        before: "B"
    },
    name => {
        console.log("C:", name);
    }
);

执行结果: taps: [A,C,B] A -> C -> B 源码在 Hook.js 134行的 _insert 方法,思路:

zhangzheng-zz commented 3 years ago

webpack 脚本执行

输入webpck 之后:

webpack.js -> require webpack-cli -> runCLI -> (new WebpackCLI() -> require('webpack')) cli.run(args) 

-> program.action -> webpack
webpack(config) -> compile.run()

compile.run() 之前做了什么?

// 实例化 compiler 
const compiler = new Compiler(options.context)

// 处理后的 options 
compiler.options = options

// 为 compiler 封装并注入了文件存取的 api   涉及钩子 beforeRun
new NodeEnvironmentPlugin({
    infrastructureLogging: options.infrastructureLogging
}).apply(compiler)

// 注册 用户配置的 plugins 
if (typeof plugin === "function") {
    plugin.call(compiler, compiler);
} else {
    plugin.apply(compiler);
}

// 在编译器准备环境时调用,时机就在配置文件中初始化插件之后。
compiler.hooks.environment.call()

// 当编译器环境设置完成后,在 environment hook 后直接调用。
compiler.hooks.afterEnvironment.call()

// 根据配置注册内部 plugins
// 涉及到入口的处理
// 注册了 compilation make 两个钩子
// new EntryOptionPlugin().apply(compiler);
new WebpackOptionsApply().process(options, compiler)

compiler.hooks.initialize.call()

....

开始 run

const run = () => {
                // 这里就会调用  NodeEnvironmentPlugin
        this.hooks.beforeRun.callAsync(this, err => {
                ......
                       // 重点 onCompiled 先记下,会在 compile 完成后调用
            this.compile(onCompiled);
        });
};

compile

    compile(callback) {
        const params = this.newCompilationParams();

        this.hooks.beforeCompile.callAsync(params, err => {

            this.hooks.compile.call(params);

            // 实例化 compilation
                       // 调用了 compile 的 thisCompilation和compilation两个钩子
            const compilation = this.newCompilation(params);

            this.hooks.make.callAsync(compilation, err => {

                this.hooks.finishMake.callAsync(compilation, err => {

                    process.nextTick(() => {

                        compilation.finish(err => {

                            compilation.seal(err => {

                                this.hooks.afterCompile.callAsync(compilation, err => {
                                });
                            });
                        });
                    });
                });
            });
        });
    }

compile 的调用链:beforeCompile -> compilation 实例化 -> thisCompilation 和 compilation -> make -> finishMake -> finish -> seal -> afterCompile 最后再是回调 onCompiled

onCompiled 回调

onCompiled 文件输出

compilation 开始编译

compile run 到 make 之前完成了 compile 的流程,调用 make 会开始 compilation 阶段开始编译文件的过程,从入口看起:addEntry

// EntryPlugin.js
compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
    const { entry, options, context } = this;

    const dep = EntryPlugin.createDependency(entry, options);
       // 开始 addEntry
    compilation.addEntry(context, dep, options, err => {
        callback(err);
    });
});