阅读webpack4,核心内容精简手稿

对webpack核心文件进行了抽离,webpack并没有使用promise、await这样的东西,布满了各种callback回掉函数,部分内容我用await替换只是为了方便看懂逻辑。包括了启动webpack.js、Compiler、Compilation、ModuleFactory等最核心的流程。

webpack.js

function webpack(){
    compiler = new Compiler(options.context);
    compiler.options = options;
    // 往comiler中注册插件
    new NodeEnvironmentPlugin().apply(compiler);
    // 执行config中的插件
    for (const plugin of options.plugins) {
        if (typeof plugin === "function") {
            plugin.call(compiler, compiler);
        } else {
            plugin.apply(compiler);
        }
    }
    compiler.hooks.environment.call();
    compiler.hooks.afterEnvironment.call();
    // 执行webpack内置插件,EntryOptionPlugin、HarmonyModulesPlugin、LoaderPlugin
    compiler.options = new WebpackOptionsApply().process(options, compiler);
    // 调用run开始编译
    compiler.run(callback);
    return compiler;
}

Compiler

简化代码

class Compiler extends Tapable {
    run(){
        // 执行之前,添加一个钩子
        await this.hooks.beforeRun.callAsync(this);
        await this.hooks.run.callAsync(this);
        await this.compile(); // 触发beforeCompile、compile、make、afterCompile
        // 将最终的js输出到output的path中
        await this.emitAssets(compilation);
        // 编译(compilation)完成
        this.hooks.done.callAsync();
    }

    compile(callback) {
        // 拿到参数
        const params = this.newCompilationParams();
        // params 有 normalModuleFactory,contextModuleFactory 两个工厂和 compilationDependencies
        // 执行编译前钩子
        await this.hooks.beforeCompile.callAsync(params);
        // 告诉插件编编译即将启动
        this.hooks.compile.call(params);
        // 创建compilation
        const compilation = this.newCompilation(params);  // 触发thisCompilation、compilation
        // make 开始编译
        await this.hooks.make.callAsync(compilation);// 传入新建的Compilation
        // 触发compilation 的 finishModules 所有模块都完成构建
        await compilation.finish();
        // 编译(compilation)停止接收新模块时触发
        await compilation.seal();
        // 编译结束
        this.hooks.afterCompile.callAsync(compilation, () => {
            return callback(null, compilation);
        });
    }
    newCompilation(params) {
        const compilation = new Compilation(this);
        // 触发 compilation 事件之前执行
        this.hooks.thisCompilation.call(compilation, params);
        // 编译(compilation)创建之后,执行插件。
        this.hooks.compilation.call(compilation, params);
        return compilation;
    }
    emitAssets(compilation){
        // 生成资源到 output 目录之前
        await this.hooks.emit.callAsync(compilation);
        await this.outputFileSystem.mkdirp(compilation.getPath(this.outputPath));
        compilation.getAssets.forEach(item=>{
            /*
                写入文件
            */
            this.hooks.assetEmitted.callAsync(file, content);
        });
        // 生成资源到 output 目录之后
        this.hooks.afterEmit.callAsync(compilation)
    }
}

compiler生命周期

钩子 时间点
beforeRun 执行之前
run 启动一次新的编译
beforeCompile 执行编译前
compile 编译即将启动
创建Compilation对象
thisCompilation 触发 compilation 事件之前
compilation 编译(compilation)创建之后
make 开始编译
compilation.finish 所有模块都完成构建
compilation.seal 对所有module和chunk进行整理,生成编译后的源码,合并、拆分
afterCompile 编译结束
emit 生成资源到 output 目录之前
assetEmitted 每个文件完成写入
afterEmit 生成资源到 output 目录之后
done 完成

Compilation

简化代码

class Compilation extends Tapable {
    constructor(compiler) {
        super();
        ...
        //初始化配置
        this.compiler = compiler;
        this.resolverFactory = compiler.resolverFactory;
        this.inputFileSystem = compiler.inputFileSystem;
        this.requestShortener = compiler.requestShortener;
        //初始化模版
        this.mainTemplate = new MainTemplate(this.outputOptions);
        this.chunkTemplate = new ChunkTemplate(this.outputOptions);
        this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(
            this.outputOptions
        );
        this.dependencyFactories = new Map(); // entryPlugin插件 会负责插入值
    }
    // SingleEntryPlugin 插件的 apply 回调用 addEntry 方法
    addEntry(context, entry, name, callback) {
        ...
        this._addModuleChain(context, entry,
            module => {
                this.entries.push(module);
            },
            (err, module) => { // 不能放到后面,因为中间某一步回调
            ...
            }
        );
    }
    _addModuleChain(context, dependency, onModule, callback) {
        ...
        // 获取模块工厂
        // entryPlugin时候Dep 是 SingleEntryDependency 实例
        const moduleFactory = this.dependencyFactories.get(Dep);
        // normalModuleFactory
        ...
        //创建模块
        let module = await moduleFactory.create({
            contextInfo: {
                issuer: "",
                compiler: this.compiler.name
            },
            context: context,
            dependencies: [dependency]
        })
        const addModuleResult = this.addModule(module);
        module = addModuleResult.module;
        onModule(module);
        dependency.module = module;
        module.addReason(null, dependency);
        const afterBuild = () => {
            if (addModuleResult.dependencies) {
                // 递归处理依赖
                this.processModuleDependencies(module, err => {
                    if (err) return callback(err);
                    callback(null, module);
                });
            } else {
                return callback(null, module);
            }
        };
        if (addModuleResult.build) {
            // 构造模块
            this.buildModule(module, false, null, null, err => {
                this.semaphore.release();
                afterBuild();
            });
        } else {
            this.semaphore.release();
            this.waitForBuildingFinished(module, afterBuild);
        }
    }
    buildModule(module, optional, origin, dependencies, thisCallback) {
        // _buildingModules是一个锁,如果一个module已经在构建中,则把callback添加到 _buildingModules 中,末尾统一执行
        let callbackList = this._buildingModules.get(module);
        if (callbackList) {
            callbackList.push(thisCallback);
            return;
        }
        this._buildingModules.set(module, (callbackList = [thisCallback]));
        // buildModule钩子
        this.hooks.buildModule.call(module);
        // 构建模块
        await module.build(
            this.options,
            this,
            this.resolverFactory.get("normal", module.resolveOptions),
            this.inputFileSystem
        );
        // 把 dependencies 进行代码中出现顺序排序
        const originalMap = module.dependencies.reduce((map, v, i) => {
            map.set(v, i);
            return map;
        }, new Map());

        module.dependencies.sort((a, b) => {
            const cmp = compareLocations(a.loc, b.loc); // 代码靠前
            if (cmp) return cmp;
            return originalMap.get(a) - originalMap.get(b);
        });

        this.hooks.succeedModule.call(module);
        this._buildingModules.delete(module);
        for (const cb of callbackList) {
            cb();
        }
    }
}
WechatIMG7.png

从addModuleDependencies到buildModule这条线是1:n,一对多的。将循环接收到的参数dependencies,循环每一个的时候,使用了semaphore.acquire,形成了并发执行。

NormalModuleFactory

生命周期

钩子 事件
create
beforeResolve
factory
resolver
createParser
parser 构造出了NormalModule将使用的解析器
afterResolve
createModule
module

NormalModuleFactory

constructor(){
    this.hooks.factory.tap("NormalModuleFactory",()=>(result,callback)=>{
        let resolver = this.hooks.resolver.call(null);// 解析路径
        resolver(result,(data)=>{
            this.hooks.afterResulve.callAsync(data,()=>{
                let createdModule = this.hooks.createModule.call(result);
                createdModule = this.hooks.module.call(createdModule, result);
                callback(createdModule);
            })
        })
    });
    this.hooks.resolver.tap("NormalModuleFactory",()=>{
    })
}
create(data, callback){
    this.hooks.beforeResolve.callAsync({
    },()=>{
        this.hooks.factory.call(null)(result, (err, module) => {
            if(module && this.cachePredicate(module)) {
                for (const d of dependencies) {
                    dependencyCache.set(d, module);
                }
            }
            callback(null, module);
        })
    })
}
// 创建构造器 type的值可以是 json、javascript/auto、javascript/dynamic 等
getParser(type, parserOptions) {
    /* 根据参数 type 和 parserOptions 进行缓存 */
    return this.createParser(type, parserOptions);
}
createParser(type, parserOptions = {}) {
    const parser = this.hooks.createParser.for(type).call(parserOptions);
    this.hooks.parser.for(type).call(parser, parserOptions);
    return parser;
}
getGenerator(type, generatorOptions) {
    /* 根据参数 type 和 generatorOptions 进行缓存 */
    return this.createGenerator(type,generatorOptions);
}
createGenerator(type, generatorOptions = {}) {
    const generator = this.hooks.createGenerator
        .for(type)
        .call(generatorOptions);
    this.hooks.generator.for(type).call(generator, generatorOptions);
    return generator;
}

上面工厂函数代码中,有一个钩子createParser暴露给外部,不同类型的文件会在NormalModuleFactory的createParser返回对应的构造器。
例如jsonModulesPlugin会指定JsonParser构造器、会指定JsonGenerator

const JsonParser = require("./JsonParser");
const JsonGenerator = require("./JsonGenerator");

class JsonModulesPlugin {
    apply(compiler) {
        compiler.hooks.compilation.tap(
            "JsonModulesPlugin",
            (compilation, { normalModuleFactory }) => {
                normalModuleFactory.hooks.createParser
                    .for("json")
                    .tap("JsonModulesPlugin", () => {
                        return new JsonParser();
                    });
                normalModuleFactory.hooks.createGenerator
                    .for("json")
                    .tap("JsonModulesPlugin", () => {
                        return new JsonGenerator();
                    });
            }
        );
    }
}

module.exports = JsonModulesPlugin;

又例如JavascriptModulesPlugin 也会指定NormalModuleFactory的createParser和createGenerator钩子,用于绑定构造器

"use strict";

const Parser = require("./Parser");
const Template = require("./Template");
const { ConcatSource } = require("webpack-sources");
const JavascriptGenerator = require("./JavascriptGenerator");
const createHash = require("./util/createHash");

class JavascriptModulesPlugin {
    apply(compiler) {
        compiler.hooks.compilation.tap(
            "JavascriptModulesPlugin",
            (compilation, { normalModuleFactory }) => {
                normalModuleFactory.hooks.createParser
                    .for("javascript/auto")
                    .tap("JavascriptModulesPlugin", options => {
                        return new Parser(options, "auto");
                    });
                normalModuleFactory.hooks.createParser
                    .for("javascript/dynamic")
                    .tap("JavascriptModulesPlugin", options => {
                        return new Parser(options, "script");
                    });
                normalModuleFactory.hooks.createGenerator
                    .for("javascript/auto")
                    .tap("JavascriptModulesPlugin", () => {
                        return new JavascriptGenerator();
                    });
                normalModuleFactory.hooks.createGenerator
                    .for("javascript/dynamic")
                    .tap("JavascriptModulesPlugin", () => {
                        return new JavascriptGenerator();
                    });             
            }
        );
    }

}

module.exports = JavascriptModulesPlugin;

NormalModuleFactory流程图,和参数流转过程

WechatIMG6.png

重要插件

entryPlugin.js

每个插件一半都会修改若干个生命周期,比如入口解析插件,就修改了 compilation 和 make 。

class SingleEntryPlugin {
    constructor(context, entry, name) {
        this.context = context;
        this.entry = entry;
        this.name = name;
    }

    apply(compiler) {
        compiler.hooks.compilation.tap(
            "SingleEntryPlugin",
            (compilation, { normalModuleFactory }) => {
                // 在Compilation里配置,SingleEntryDependency 需要使用的 Factory 是 normalModuleFactory
                compilation.dependencyFactories.set(
                    SingleEntryDependency,
                    normalModuleFactory
                );
            }
        );
        compiler.hooks.make.tapAsync(
            "SingleEntryPlugin",
            (compilation, callback) => {
                const { entry, name, context } = this;
                //返回SingleEntryDependency实例
                const dep = SingleEntryPlugin.createDependency(entry, name);
                compilation.addEntry(context, dep, name, callback);
                // 间接调用 _addModuleChain,构造的出的Factory,从compilation.dependencyFactories取出,也就是normalModuleFactory
            }
        );
    }
}

模块Module和Dependecy

Module 类往上还会继承于 DependenciesBlock,这个是所有模块的基类,它包含了处理依赖所需要的属性和方法。上面所说的 variables, dependencies, blocks 也是这个基类拥有的三个属性。它们分别是:

钩子 时间点
variables 对应需要对应的外部变量,比如 __filename, __dirname, process 等node环境下特有的变量
dependencies 对应需要解析的其他普通模块,比如 require("./a") 中的 a 模块会先生成一个 CommonJSRequireDependency
blocks 对应需要解析的代码块(最终会对应成一个 chunk),比如 require.ensure("./b"),这里的 b 会生成一个 DependenciesBlock 对象

source

class Source {
    source() {
        throw new Error("Abstract");
    }
    size() {
        if(Buffer.from.length === 1) return new Buffer(this.source()).length;
        return Buffer.byteLength(this.source())
    }
    map(options) {
        return null;
    }
    sourceAndMap(options) {
        return {
            source: this.source(),
            map: this.map()
        };
    }
    node() {
        throw new Error("Abstract");
    }

    listNode() {
        throw new Error("Abstract");
    }
    updateHash(hash) {
        var source = this.source();
        hash.update(source || "");
    }
}
module.exports = Source;

NormalModule

parser

HarmonyModulesPlugin

class HarmonyModulesPlugin {
    apply(compiler) {
        compiler.hooks.compilation.tap(
            "HarmonyModulesPlugin",
            (compilation, { normalModuleFactory }) => {
                // 省略Factory绑定
                const handler = (parser, parserOptions) => {
                    // 此插件的作用,是在parser.hooks.program钩子 添加依赖
                    new HarmonyDetectionParserPlugin().apply(parser);
                    new HarmonyImportDependencyParserPlugin(this.options).apply(parser);
                    new HarmonyExportDependencyParserPlugin(this.options).apply(parser);
                    new HarmonyTopLevelThisParserPlugin().apply(parser);
                };

                normalModuleFactory.hooks.parser
                    .for("javascript/auto")
                    .tap("HarmonyModulesPlugin", handler);
                normalModuleFactory.hooks.parser
                    .for("javascript/esm")
                    .tap("HarmonyModulesPlugin", handler);
            }
        );
    }
}

HarmonyModulesPlugin插件,添加了normalModuleFactory各个事件节点应该做的主要事情

你可能感兴趣的:(阅读webpack4,核心内容精简手稿)