# 源码解析四

上章说到 执行 compiler.run,那么这章就来看看 run 到底干了啥

# compiler.run

run 方法定义在 Compiler 类里面

run(callback){
    // ...
    const onCompiled = (err, compilation) =>{}
    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);
            });
        });
    });
}

上面 run 方法只要就是执行了 hooks.run.beforeRun.callAsync,如果未出现 异常 就执行 hooks.run.callAsync, 里面执行了 readRecords 方法,然后在回调里面执行 compile,我们再去这个函数看看具体做了什么

# compiler.compile

compile 是真正进行编译的过程,最终会把所有的原始资源编译为目标资源.实例化了一个 compilation,并将 compilation 传给 make 钩子上的方法,注册在这些钩子上的方法会调用 compilation 上的 addEntry,进行构建,代码如下:

	compile(callback) {
		const params = this.newCompilationParams();
		this.hooks.beforeCompile.callAsync(params, err => {
			if (err) return callback(err);

			this.hooks.compile.call(params);

			const compilation = this.newCompilation(params);
			this.hooks.make.callAsync(compilation, err => {
				if (err) return callback(err);

				compilation.finish(err => {
					if (err) return callback(err);

					compilation.seal(err => {
						if (err) return callback(err);

						this.hooks.afterCompile.callAsync(compilation, err => {
							if (err) return callback(err);

							return callback(null, compilation);
						});
					});
				});
			});
		});
	}

首先获取 compilation 所需的 params,也就是上面 看到的

const params = this.newCompilationParams();

具体就是如下:

newCompilationParams() {
    const params = {
        normalModuleFactory: this.createNormalModuleFactory(),
        contextModuleFactory: this.createContextModuleFactory(),
        compilationDependencies: new Set()
    };
    return params;
}

该方法里面分别返回一个 NormalModuleFactory,ContextModuleFactory 实例化的类,点进去发现都是继承 tapable,工厂对象顾名思义就是用来创建实例的,它们后续用来创建 module 实例的,包括 NormalModule 以及 ContextModule 实例。

# compilation

上面创建完 params 后继续调用 newCompilation(params) 创建 compilation 对象,代码如下:

newCompilation(params) {
    const compilation = new Compilation(this);
    compilation.fileTimestamps = this.fileTimestamps;
    compilation.contextTimestamps = this.contextTimestamps;
    compilation.name = this.name;
    compilation.records = this.records;
    compilation.compilationDependencies = params.compilationDependencies;
    this.hooks.thisCompilation.call(compilation, params);
    this.hooks.compilation.call(compilation, params);
    return compilation;
}

Compilation 对象是后续构建流程中最核心最重要的对象,它包含了一次构建过程中所有的数据。也就是说一次构建过程对应一个 Compilation 实例。在创建 Compilation 实例时会触发钩子 compilaiion 和 thisCompilation。

compilaiion

compilaiion 对面里面有很多属性,我们拿几个重要的属性来看看

  • modules 记录了所有解析后的模块
  • chunks 记录了所有的chunk
  • assets 记录了所有要生成的文件
  • entries 记录了打包文件的入口

通过断点我们可以清晰的看到 assets 记录的就是我们生成的代码资源,对应形式 为 chunk:code,但是 modules 中每个模块是啥对象,真不清楚.先不纠结,反正该步骤就是生成 Compilation 对象

# make

上面代码可以看到生成 compilation 实例后, this.hooks.make.callAsync 执行订阅了 make 钩子的插件的回调函数. 说实话,这个订阅让我一顿好找,最终在 webpack.js 初始化默认插件中找到了

// webpack.js 135 行
SingleEntryPlugin: () => require("./SingleEntryPlugin"),

SingleEntryPlugin.js


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

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

可以看到 这里订阅了 make,最后后执行了 compilation.addEntry 方法

# addEntry

好了,现在又回到了 compilation 里面,我们去找发现果然有个 addEntry 方法如下:

addEntry(context, entry, name, callback) {
    this.hooks.addEntry.call(entry, name);
    // ...
    this._addModuleChain()
}

可以看到,addEntry 调用了 _addModuleChain,这里面的逻辑就稍微多一些了

...
// 根据依赖查找对应的工厂函数
const Dep = /** @type {DepConstructor} */ (dependency.constructor);
const moduleFactory = this.dependencyFactories.get(Dep);

this.semaphore.acquire(() => {
    moduleFactory.create(
    {
        contextInfo: {
            issuer: "",
            compiler: this.compiler.name
        },
        context: context,
        dependencies: [dependency]
    },
    (err, module) => {
        ...
        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();
            });
        } 
    }
);
});

上面的代码进行了部分节选,_addModuleChain 中接收参数 dependency 传入的入口依赖,然后在this.semaphore.acquire

this.semaphore 这个类是一个编译队列控制,对执行进行了并发控制,默认并发为 100,在实例化的时候可以看到,超过存入 semaphore.waiters,带入如下:

acquire(callback) {
    if (this.available > 0) {
        this.available--;
        callback();
    } else {
        this.waiters.push(callback);
    }
}

然后在其回调里面执行了 moduleFactory.create, 于是来到了 NormalModuleFactory.js

create(data, callback) {
    const dependencies = data.dependencies;
    // ...
    this.hooks.beforeResolve.callAsync(
        {
            contextInfo,
            resolveOptions,
            context,
            request,
            dependencies
        },
        (err, result) => {
            if (err) return callback(err);

            // Ignored
            if (!result) return callback();

            const factory = this.hooks.factory.call(null);

            // Ignored
            if (!factory) return callback();

            factory(result, (err, module) => {
                // ...
                callback(null, module);
            });
        }
    );
}

create 先触发了 hooks:beforeResolve.callAsync,然后在回调里又触发 hooks.factory,该事件返回一个 factory 函数,然后执行 factory 函数

this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {
    let resolver = this.hooks.resolver.call(null);
    resolver(result, (err, data) => {
        // ... 

        this.hooks.afterResolve.callAsync(data, (err, result) => {
            // ...
           createdModule = new NormalModule(result);
	        return callback(null, createdModule);
        });
    });
});

在 resolver 里面,先是执行了 hooks.resolver.call,进入内部注册hooks钩子的函数,最后得到这个一个对象:

callback(null, {
    context: context,
    request: loaders
        .map(loaderToIdent)
        .concat([resource])
        .join("!"),
    dependencies: data.dependencies,
    userRequest,
    rawRequest: request,
    loaders,
    resource,
    matchResource,
    resourceResolveData,
    settings,
    type,
    parser: this.getParser(type, settings.parser),
    generator: this.getGenerator(type, settings.generator),
    resolveOptions
});

之后再执行创建 module 并返回

let createdModule = this.hooks.createModule.call(result);
createdModule = this.hooks.module.call(createdModule, result);
return callback(null, createdModule);

# getParser

主要作用是为该 module 提供 parser,用于解析模块为 ast。

this.getParser(type, settings.parser) 创建 parser 并缓存。

执行 createParser,方法里触发 NormalModuleFactory.hooks:createParser for (type),该事件注册在 JavascriptModulesPlugin 插件,根据 type 不同返回不同的 parser 实例。

实例化之后,触发 NormalModuleFactory.hooks:parser for (type),会去注册一些在 parser 阶段(遍历解析 ast 的时候)被触发的 hooks。

# getGenerator

主要作用是为该 module 提供 generator,用于模版生成时提供方法。

与 parser 类似,this.getGenerator(type, settings.generator) 创建 generator 并缓存。

执行 createGenerator,方法里触发 NormalModuleFactory.hooks:createGenerator for (type),该事件注册在 JavascriptModulesPlugin 插件,根据 type 不同返回不同的 generator 实例(目前代码里都是返的一致的 new JavascriptGenerator() )。

实例化之后,触发 NormalModuleFactory.hooks:generator for (type)。

得到这个组合对象 data 后,跳出 resolver 函数,执行 resolver 函数回调,到此 resolve 流程结束,开启创建 module 流程!

更新时间: 9/23/2020, 3:25:31 PM