webpack源码阅读之主流程分析

webpack源码阅读之主流程分析

Comipler是其webpack的支柱模块,其继承于Tapable类,在compiler上定义了很多钩子函数,贯穿其整个编译流程,这些钩子上注册了很多插件,用于在特定的时机执行特定的操作,同时,用户也可以在这些钩子上注册自定义的插件来进行功能拓展,接下来将围绕这些钩子函数来分析webpack的主流程。

1. compiler生成

​ compiler对象的生成过程大致可以简化为如下过程,首先对我们传入的配置进行格式验证,接着调用Compiler构造函数生成compiler实例,自定义的plugins注册,最后调用new WebpackOptionsApply().process(options, compiler)进行默认插件的注册,comailer初始化等。

const webpack = (options,callback)=>{
    //options格式验证
  const webpackOptionsValidationErrors = validateSchema(
        webpackOptionsSchema,
        options
    );
  ...
  //生成compiler对象
    let compiler = new Compiler(options.context);
  
  //自定义插件注册
  if (options.plugins && Array.isArray(options.plugins)) {
            for (const plugin of options.plugins) {
                if (typeof plugin === "function") {
                    plugin.call(compiler, compiler);
                } else {
                    plugin.apply(compiler);
                }
            }
        }
  
  //默认插件注册,默认配置等
  compiler.options = new WebpackOptionsApply().process(options, compiler);
}

2. compiler.run

​ 生成compler实例后,cli.js中就会调用compiler.run方法了,compiler.run的流程大致可以简写如下(去掉错误处理等逻辑),其囊括了整个打包过程,首先依次触发beforeRun、run等钩子,接下来调用compiler.compile()进行编译过程,在回调中取得编译后的compilation对象,调用compiler.emitAssets()输出打包好的文件,最后触发done钩子。

run(){
  const onCompiled = (err, compilation) => {
    //打包输出
            this.emitAssets(compilation, err => {
                this.hooks.done.callAsync(stats)
        };
    // beforeRun => run => this.compile()                 
        this.hooks.beforeRun.callAsync(this, err => {
            this.hooks.run.callAsync(this, err => {
                this.readRecords(err => {
                    this.compile(onCompiled);
                });
            });
        });
}

3. compiler.compile

​ 在这个方法中主要也是通过回调触发钩子进行流程控制,通过newCompilation=>make=>finsih=>seal流程来完成一次编译过程,compiler将具体一次编译过程放在了compilation实例上,可以将主流程与编译过程分割开来,当处于watch模式时,可以进行多次编译。

compile(callback) {
        const params = this.newCompilationParams();
        this.hooks.beforeCompile.callAsync(params, err => {
            this.hooks.compile.call(params);
            const compilation = this.newCompilation(params);
            this.hooks.make.callAsync(compilation, err => {
                compilation.finish(err => {
                    compilation.seal(err => {
                        this.hooks.afterCompile.callAsync(compilation, err => {
                            return callback(null, compilation);
                        });
                    });
                });
            });
        });
    }

​ 从图中可以看到make钩子上注册了singleEntryPlugin(单入口配置时),compilation作为参数传入该插件,接着在插件中调用compilation.addEntry方法开始编译过程。

compiler.hooks.make.tapAsync(
            "SingleEntryPlugin",
            (compilation, callback) => {
                const { entry, name, context } = this;

                const dep = SingleEntryPlugin.createDependency(entry, name);
                compilation.addEntry(context, dep, name, callback);
            }
        );

4. compilation编译过程

​ 编译过程的入口在compilation._addModuleChain函数,传入entry,context参数,在回调中得到编译生成的module。编译的过程包括文件和loader路径的resolve,loader对源文件的处理,递归的进行依赖处理等等,这里不进行详述。

addEntry(context, entry, name, callback) {
        this.hooks.addEntry.call(entry, name);
        this._addModuleChain(
            context,
            entry,
            module => {
                this.entries.push(module);
            },
            (err, module) => {
                this.hooks.succeedEntry.call(entry, name, module);
                return callback(null, module);
            }
        );
    }

5. compilation.seal

在 webpack 的工作流程当中,在上一步中得到编译好的所有模块后,会调用回调函数,回调向上传递了好几层,最后调用的是compiler.compile中的compilation.finish以及compilation.seal,主要操作在compilation.seal中。

主要步骤为:构建 module graph,构建chunk graph 、生成 moduleId,生成 chunkId,生成 hash,然后生成最终输出文件的内容,同时每一步之间都会暴露 hook , 提供给插件修改的机会。

6. compiler.emitAssets

经历了上面所有的阶段之后,所有的最终代码信息已经保存在了 Compilation 的 assets 中,当 assets 资源相关的优化工作结束后,seal 阶段也就结束了。这时候执行 seal 函数接受到 callback,callback回溯到compiler.run中,执行compiler.emitAssets.

在这个方法当中首先触发 hooks.emit 钩子函数,即将进行写文件的流程。接下来开始创建目标输出文件夹,并执行 emitFiles 方法,将内存当中保存的 assets 资源输出到目标文件夹当中,这样就完成了内存中保存的 chunk 代码写入至最终的文件

参考资料:

https://juejin.im/post/5d4d08...

相关推荐