边学边想webpack5源码一起读(4)

457 阅读4分钟

引言

本文章基于webpack5源码进行解读,编写本文仅为方便作者学习以及记忆,如果有什么说的不对或者瞎猜的不对的地方,请大家指出,请勿乱喷,大家都是自己人感谢大家~

地址

从本章节开始会开始加上之前的一些跳转地址方便大家跟我一起回忆

边学边想webpack5源码一起读(1)

边学边想webpack5源码一起读(2)

边学边想webpack5源码一起读(3)

接上回

首先我们基于上回的一些内容,我们继续开始画我们图~ webpack流程图成长版本.png 那么在上一章节中我们主要其实关注的是在webpack/lib/webpack.js这个文件下的compiler的一个构建过程,但是我们知道实际上真正开始构建过程的是watch(...)run(...)

//webpack/lib/webpack.js webpack(...) 中的部分代码片段
    const { compiler, watch, watchOptions } = create();
    if (watch) {
        compiler.watch(watchOptions, callback);
    } else {
        compiler.run((err, stats) => {
            compiler.close(err2 => {
                    callback(err || err2, stats);
            });
        });
    }
    return compiler;

那么由于我们当前并没有要求webpackwatch

webpack官网配置图 那么这里我们进入compiler.run(...)函数文件中查看一下具体情况(这里为了方便阅读,删减部分代码)

// node_modules/webpack/lib/Compiler.js
    run(callback) {
            // 如果对正在运行状态下的compiler,再次执行run或者watch函数
        if (this.running) {
                return callback(new ConcurrentCompilationError());
        }

        let logger;

        const finalCallback = (err, stats) => {
            ...
        };

        const startTime = Date.now();

        this.running = true;

        const onCompiled = (err, compilation) => {
            ...
        };
        // 这个函数就是compiler的运行核心函数
        const run = () => {
            /*
            * 快速看到这个代码 我们可以发现的是首先大部分你的错误处理是由finalCallback(...),显然
            * 目前来说这个还不是我们关注的重点,那么我们要关注的重点就是this.compile(...),在run钩子执行后,立马调用的主流程函数。我们跟进去看看
            */
            this.hooks.beforeRun.callAsync(this, err => {
                if (err) return finalCallback(err);

                this.hooks.run.callAsync(this, err => {
                    if (err) return finalCallback(err);

                    this.readRecords(err => {
                        if (err) return finalCallback(err);

                        this.compile(onCompiled);
                    });
                });
            });
        };
        // this.idle 在complier初始化的时候 默认为false
        if (this.idle) {
            this.cache.endIdle(err => {
                if (err) return finalCallback(err);

                this.idle = false;
                run();
            });
        } else {
            // 所以我们就直接关注到这里即可
            run();
        }
    }

这里可能会有朋友问为什么不看this.readRecords(...),因为作者跟进去看了以后发现这个函数中的内容主要是跟官方配置中的recordspath相关,由于不是我们关注的编译过程的主路径。我们就继续往下过到compile(...)函数中

// node_modules/webpack/lib/Compiler.js
compile(callback) {
    /*
    * 首先我们看到这里为接下来要创建的Compilation类筹备的参数,注意:这里的内部构建工厂比较重要
    * 我把它贴在当前函数的下方方便大家查看理解
    */ 
    const params = this.newCompilationParams();
    /*
    * 触发beforeCompile钩子
    */
    this.hooks.beforeCompile.callAsync(params, err => {
        if (err) return callback(err);

        this.hooks.compile.call(params);
        /*
        * 这里通过newCompilation(...)构建新的compilation对象并且进行返回,为了方便查看,放在了下方
        */
        const compilation = this.newCompilation(params);

        const logger = compilation.getLogger("webpack.Compiler");

        logger.time("make hook");
        /*
        * 根据this.hooks.make.callAsync查到EntryPlugin并且看了进去看了进去,钩子涉及回调涉及到compilation的多个函数如下
        * 添加入口依赖
        * addEntry(...)
        * _addEntryItem(...)
        * 根据入口模块增加模块树
        * addModuleTree(...)
        * 整体的模块体系构建的入口
        * handleModuleCreation(...)
        * 根据翻译因该是因式分解模块
        * factorizeModule(...)
        * 创建当前模块
        * addModule(..)
        * 在这里调用 NormalModule 中的 build方法 进行解析
        * 其中经过 loader-run 进行 pitch 跟 normal 调用,最终通过module类传入的回调函数 processResource 进行 文件二进流读取
        * 针对二进制进行RawSource的构建,最后通过Acorn进行JS Parse并且基于node_modules/webpack/lib/javascript/JavascriptParser.js 的规则进行解析节点,触发import钩子收集相关依赖模块
        * buildModule (...)
        * 处理模块依赖的模块关系递归调用 handleModuleCreation 来完成整个模块构建
        * processModuleDependencies (...)
        */
        this.hooks.make.callAsync(compilation, err => {
            logger.timeEnd("make hook");
            if (err) return callback(err);

            logger.time("finish make hook");
            // 因为这里看到taps数组为空,所以默认无调用逻辑
            this.hooks.finishMake.callAsync(compilation, err => {
                logger.timeEnd("finish make hook");
                if (err) return callback(err);

                process.nextTick(() => {
                    logger.time("finish compilation");
                    /*
                    *  边学边想webpack5源码一起读(5) 再来解析
                    */
                    compilation.finish(err => {
                        logger.timeEnd("finish compilation");
                        if (err) return callback(err);

                        logger.time("seal compilation");
                        compilation.seal(err => {
                            logger.timeEnd("seal compilation");
                            if (err) return callback(err);

                            logger.time("afterCompile hook");
                            this.hooks.afterCompile.callAsync(compilation, err => {
                                logger.timeEnd("afterCompile hook");
                                if (err) return callback(err);

                                return callback(null, compilation);
                            });
                        });
                    });
                });
            });
        });
    });
}
/*
* NormalModuleFactory和ContextModuleFactory都继承于基类ModuleFactory需要重写方法create(...)
*/ 
newCompilationParams() {
    const params = {
        normalModuleFactory: this.createNormalModuleFactory(),
        contextModuleFactory: this.createContextModuleFactory()
    };
    return params;
}
/*
* 对于ContextModuleFactory内部构造过程总结:
* 1. hooks的初始化
* 2. resolverFactory 的内部存储
*/ 
createContextModuleFactory() {
    const contextModuleFactory = new ContextModuleFactory(this.resolverFactory);
    this.hooks.contextModuleFactory.call(contextModuleFactory);
    return contextModuleFactory;
}
/*
* 对于NormalModuleFactory内部构造过程总结:
* 1. hooks的初始化
* 2. 格式化Module.rule属性,并且构建对应的匹配方法,详细配合NormalModuleFactory.js, BasicMatcherRulePlugin.js, RuleSetCompiler.js 进行查看学习
* 3. 构建整体rules的执行方法
* 4. 将我们在webpack.js中执行的NodeEnvironmentPlugin中所构建的文件读取工具给到模块类(缓存必要性)
* 5. 资源解析缓存构建
* 6. 实例对象的factorize和resolve钩子的钩子函数挂载
*/ 
createNormalModuleFactory() {
    const normalModuleFactory = new NormalModuleFactory({
        context: this.options.context,
        fs: this.inputFileSystem,
        resolverFactory: this.resolverFactory,
        options: this.options.module,
        associatedObjectForCache: this.root,
        layers: this.options.experiments.layers
    });
    this.hooks.normalModuleFactory.call(normalModuleFactory);
    return normalModuleFactory;
}
/*
* 1. _cleanupLastCompilation(...)如果在compilation有缓存的情况,清空缓存的过往compilation数据,并释放相应内存
* 2. 根据当前compiler上下文,构建一个新的compilation对象
*/
createCompilation() {
    this._cleanupLastCompilation();
    return (this._lastCompilation = new Compilation(this));
}
newCompilation(params) {
    const compilation = this.createCompilation();
    compilation.name = this.name;
    compilation.records = this.records;
    this.hooks.thisCompilation.call(compilation, params);
    /*
    * EntryPlugin 在 compilation 钩子中将 normalModuleFactory 设置为 compilation.dependencyFactories 集合中的 EntryDependency key的数据
    */
    this.hooks.compilation.call(compilation, params);
    return compilation;
}

本次就看到模块整体依赖建立,其中很多细节内容需要大家运用到debug的能力,希望大家有什么问题可以告诉我,我与大家共同进步