webpack源码分析-编译的run和make阶段

webpack的编译分几个阶段进行,分别为创建对象阶段run、解析获取module阶段make、module分包阶段seal、输出打包后文件阶段emit

一些定义

  • module:webpack中用requireimport引入的都叫module

  • chunk:module的一个集合,入口文件就算一个chunk

  • dependency:包含一个模块的基本信息,用来创建module

  • moduleFactory:创建module对象的工厂类

  • assets:最后输出的资产文件

webpack流程图

webpack流程图

执行compiler.run方法

查看文件node_modules/webpack/lib/Compiler.js第247行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
webpack 开始工作
*/
run(callback) {
// 开始执行 beforerun 钩子函数
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
// 开始执行 run 钩子函数
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
// 开始读取记录
this.readRecords(err => {
if (err) return finalCallback(err);
// 开始编译
this.compile(onCompiled);
});
});
});
}

方法run最后执行了compile方法,即在compiler方法执行时,说明run阶段已经开始了。

compile方法定义

在文件660行查看compile方法的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
webpack 开始编译
*/
compile(callback) {
// 创建编译器参数
const params = this.newCompilationParams();
// 调用 beforeCompile 钩子函数
this.hooks.beforeCompile.callAsync(params, err => {
// 调用 compile 钩子函数
this.hooks.compile.call(params);
// 创建编译器对象
const compilation = this.newCompilation(params);
// 调用 compiler 的 make 钩子函数
this.hooks.make.callAsync(compilation, err => {
if (err) return callback(err);
// 调用 finishModules 钩子函数
compilation.finish(err => {
if (err) return callback(err);
// 开始对所有的 modules 进行封装
compilation.seal(err => {
if (err) return callback(err);
// 调用 afterCompile 钩子函数
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
// webpack 编译结束
return callback(null, compilation);
});
});
});
});
});
}

run阶段

compiler.run方法得知compiler方法执行时,说明run阶段已经开始

创建NormalModuleFactory和ContextModuleFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
webpack 开始编译
*/
compile(callback) {
// 创建编译器参数
const params = this.newCompilationParams();
...
}
/*
使用方法newCompilationParams()
创建 NormalModuleFactory 和 ContextModuleFactory
*/
newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(), // NormalModule 的工厂类
contextModuleFactory: this.createContextModuleFactory(), // ContextModules 的工厂类
compilationDependencies: new Set()
};
return params;
}

创建Compilation对象

对象Compilation对象是webpack的编译器核心对象,被Compiler用来创建新的编译或构建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
webpack 开始编译
*/
compile(callback) {
// 创建编译器参数
const params = this.newCompilationParams();
// 调用 beforeCompile 钩子函数
this.hooks.beforeCompile.callAsync(params, err => {
// 调用 compile 钩子函数
this.hooks.compile.call(params);
// 创建编译器对象
const compilation = this.newCompilation(params);
...
}
/*
创建 Compilation 对象
*/
newCompilation(params) {
// 创建 Compilation 对象
const compilation = this.createCompilation();
...
// 调用 thisCompilation 钩子函数
this.hooks.thisCompilation.call(compilation, params);
// 调用 compilation 钩子函数
this.hooks.compilation.call(compilation, params);
return compilation;
}
/**
创建并返回 Compilation 对象
*/
createCompilation() {
return new Compilation(this);
}

至此,webpack的run阶段结束

make阶段

compile方法得知,make阶段开始调用如下:

1
2
// 调用 compiler 的 make 钩子函数
this.hooks.make.callAsync(compilation, err => {

在初始化过程中得知,钩子函数makeSingleEntryPlugin处被监听,等待make操作的执行。

添加入口文件entry

查看文件node_modules/webpack/lib/SingleEntryPlugin.js第46行

1
2
3
4
5
6
7
8
// 监听 compiler 对象的 make 操作
compiler.hooks.make.tapAsync('SingleEntryPlugin', (compilation, callback) => {
const { entry, name, context } = this;
// 创建一个 dependency
const dep = SingleEntryPlugin.createDependency(entry, name);
//添加 entry(入口),并将 dependency 添加到 compilation 对象
compilation.addEntry(context, dep, name, callback);
});

查看文件node_modules/webpack/lib/Compilation.js第1142行addEntry方法的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

addEntry(context, entry, name, callback) {
// context:默认是我们的工程目录
// entry:是包含工程目录中的 “/src/index.js” 文件的 “dependency” 对象
// name:默认是 “main”
this._addModuleChain( // 通过 dependency 创建 module 并添加至 compilation 对象
context,
entry,
module => {
this.entries.push(module);
},
(err, module) => {
...
// 成功后回到 SingleEntryPlugin 插件
return callback(null, module);
}
);
}

查看_addModuleChain方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
根据 dependency 添加 module 对象至 compilation 对象
*/
_addModuleChain(context, dependency, onModule, callback) {
const Dep = /** @type {DepConstructor} */ (dependency.constructor);
// 获取模块工厂对象
const moduleFactory = this.dependencyFactories.get(Dep);
...
// 通过模块工厂创建对应的 module 对象
moduleFactory.create(
{
contextInfo: {
issuer: "",
compiler: this.compiler.name
},
context: context,
dependencies: [dependency]
},
(err, module) => {
...
});
}

通过调用moduleFactorycreate方法创建module对象

若没有定义moduleFactory,则webpack默认使用NormalModuleFactory创建NormalModule

触发ModuleFactory的create方法

之前的基础用法demo中没有自定义moduleModuleFactory,故webpack使用NormalModuleFactorycreate方法创建,查看文件/node_modules/webpack/lib/NormalModuleFactory.js的373行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
创建 NormalModule 对象
*/
create(data, callback) {
// 获取 dependencies
const dependencies = data.dependencies;
// 获取上下文目录
const context = data.context || this.context;
// 获取 dependency 的文件地址,也就是“项目名/src/index.js”
const request = dependencies[0].request;
// 执行 beforeResolve 钩子函数
this.hooks.beforeResolve.callAsync(
{
contextInfo,
resolveOptions,
context,
request,
dependencies
},
(err, result) => {
...
});
}
);
}

触发ModuleFactory的resolver方法

方法resolver负责根据传入的dependency对象获取模块中的基本信息,根据这些信息返回模块的loaders(能处理当前module的一个loader集合)、hash值query(请求参数)等,用于构建module对象的信息,并new一个NormalModule对象

查看文件/node_modules/webpack/lib/NormalModuleFactory.js的123行中resolver方法的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {
// 调用 resolver 钩子函数获取 resolver 方法
let resolver = this.hooks.resolver.call(null);
// 直接调用 resolver 方法
resolver(result, (err, data) => {
this.hooks.afterResolve.callAsync(data, (err, result) => {
// 调用 createModule 钩子函数
let createdModule = this.hooks.createModule.call(result);
if (!createdModule) {
if (!result.request) {
return callback(new Error("Empty dependency (no request)"));
}
// 创建 NormalModule 对象
createdModule = new NormalModule(result);
}
createdModule = this.hooks.module.call(createdModule, result);
return callback(null, createdModule);
});
});
});
// 注册 resolver 钩子函数监听
this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {
...
// 返回 resolve 方法
});

compilateion触发addModule方法

工厂NormalModuleFactory创建完NormalModule对象后回到Compilation_addModuleChain方法,开始添加module

查看文件node_modules/webpack/lib/Compilation.js第1085行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
根据 dependency 添加 module 对象至 compilation 对象
*/
_addModuleChain(context, dependency, onModule, callback) {
...
// 添加 module 至 compilation
const addModuleResult = this.addModule(module);
...
});
}
/*
添加 module
*/
addModule(module, cacheGroup) {
// 添加 module 至 compilation 的 modules 集合中
...
this.modules.push(module);
return {
module: module,
issuer: true,
build: true,
dependencies: true
};
}

方法_addModuleChain直接调用addModule方法将module对象添加到Compilationmodule集合中

Compilation触发buildModule构建模块

查看文件node_modules/webpack/lib/Compilation.js第1111行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
根据 dependency 添加 module 对象至 compilation 对象
*/
_addModuleChain(context, dependency, onModule, callback) {
...
// 开始构建当前 module 对象
this.buildModule(module, false, null, null, err => {
...
});
}
/*
构建模块
*/
buildModule(module, optional, origin, dependencies, thisCallback) {
// 直接调用 module 的 build 方法构建
module.build(
this.options,
this,
this.resolverFactory.get("normal", module.resolveOptions),
this.inputFileSystem,
error => {
...
// 构建完毕后回到再次 _addModuleChain 方法
return callback();
}
);
}

由构建模块可看出,buildModule方法执行的是每个module对象的build方法。在基础使用demo中,则为之前创建的NormalModule,查看文件/node_modules/webpack/lib/NormalModule.js文件第427行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
build(options, compilation, resolver, fs, callback) {
...
// 直接调用 dobuild 方法
return this.doBuild(options, compilation, resolver, fs, err => {
...
});
}

/*
执行所有的 loader 获取返回结果,根据返回结果创建 loader 加载过后的 source 源码
*/
doBuild(options, compilation, resolver, fs, callback) {
// 创建执行 loader 的上下文对象
const loaderContext = this.createLoaderContext(
resolver,
options,
compilation,
fs
);
// 执行所有的 loader
runLoaders(
{
resource: this.resource,
loaders: this.loaders,
context: loaderContext,
readResource: fs.readFile.bind(fs)
},
(err, result) => {
// 根据返回的结果创建 source 源码对象
this._source = this.createSource(
this.binary ? asBuffer(source) : asString(source),
resourceBuffer,
sourceMap
);
this._sourceSize = null;
this._ast =
typeof extraInfo === "object" &&
extraInfo !== null &&
extraInfo.webpackAST !== undefined
? extraInfo.webpackAST
: null;
// 返回到 buildModule 方法
return callback();
}
);
}

上述doBuild方法中使用的runLoaders方法是单独由第三方库loader-runner提供,用来执行所有的loader并返回执行后的结果。根据此结果创建source对象

执行parse方法解析源码

查看文件/node_modules/webpack/lib/NormalModule.js文件第482行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
build(options, compilation, resolver, fs, callback) {
...
return this.doBuild(options, compilation, resolver, fs, err => {
// 开始解析 loader 处理过后的源码
const result = this.parser.parse(
this._ast || this._source.source(),
{
current: this,
module: this,
compilation: compilation,
options: options
},
(err, result) => {
if (err) {
handleParseError(err);
} else {
handleParseResult(result);
}
}
);
}
}

webpack默认支持JSONJavaScript代码解析,JavaScript解析默认使用Acorn框架,parse方法主要用来解析代码中的模块定义、获取代码中的模块引用、转换alias、转换自定义变量方法等。

addModuleDependencies编译模块中关联的依赖

解析后,compilationbuildModule结束,开始编译module中关联的依赖,查看文件node_modules/webpack/lib/Compilation.js第1093行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
_addModuleChain(context, dependency, onModule, callback) {
...
const afterBuild = () => {
if (addModuleResult.dependencies) {
//开始编译模块中关联的依赖
this.processModuleDependencies(module, err => {
if (err) return callback(err);
callback(null, module);
});
} else {
return callback(null, module);
}
};
}

processModuleDependencies(module, callback) {
...
//addModuleDependencies方法用来编译模块中关联的依赖,与_addModuleChain类似
this.addModuleDependencies(
module,
sortedDependencies,
this.bail,
null,
true,
callback
);
}

至此,webpack的make阶段结束。

  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020-2024 Aweso Lynn
  • PV: UV: