index.js
import { A } from './a.js'
function test() {
const tmp = 'something'
return tmp + A
}
const r = test()
a.js
export const A = "a"
如上所示,index.js和a.js文件,根据这个demo看看webpack的make事件流是怎么样的。
var webpack = require("../lib/webpack.js");
var compiler = webpack(options);
compiler.run(compilerCallback);
执行webpack入口webpack函数,返回compiler对象,然后执行compiler.run开始编译。
Compiler.prototype.run = function(callback) {
this.compile(callback);
}
Compiler.prototype.compile = function(callback){
var self = this;
self.applyPluginAsync(“before-compile”, params, function(err) {
var compilation = self.newCompilation(params);
self.applyPluginParallel(“make”, compilation, function(err){
compilation.finish( );
compilation.seal(callback);
}
}
}
Compiler的run方法调用Compiler的compile方法,在compile方法中,生成compilation实例,触发make事件执行对应事件的监听。
compiler.plugin("make", (compilation, callback) => {
const dep = SingleEntryPlugin.createDependency(this.entry, this.name);
compilation.addEntry(this.context, dep, this.name, callback);
});
在SingleEntryPlugin插件定义了make事件的监听,根据入口new SingleEntryDependency依赖dep,调用compilaition的addEntry方法。
class Compilation extends Tapable {
addEntry(context, entry, name, callback) {
this._addModuleChain(context, entry, (module) => {
entry.module = module;
this.entries.push(module);
}, (err, module) => {
return callback();
});
}
_addModuleChain(context, dependency, onModule, callback) {
const moduleFactory = this.denpendencyFactories.get(dependency.constructor);
moduleFactory.create({
context: context,
dependencies: [dependency]
}, (err, module) => {
this.addModule(module);
onModule(module);
this.buildModule(module, false, null, null, (err) => {
moduleReady.call(this);
});
function moduleReady( ) {
this.processModuleDependencies(module, err => {
if(err) {
return callback(err);
}
return callback(null, module);
});
}
});
}
addModule(module) {
this._modules[identifier] = module;
this.modules.push(module);
}
buildModule(module, optional, origin, dependencies, thisCallback){
module.build(this.options, this, this.resolvers.normal, this.inputFileSystem, (err) => {
module.dependencies.sort(Dependency.compare);
return thisCallback();
});
}
}
addEntry方法内执行_addModuleChain方法,_addModuleChain创建module,把module挂载在compilation上,构建module和dependency的模块依赖树,最后执行make事件的回调函数。在 _addModuleChain中,执行moduleFactory.create方法创建module,执行addModule方法把module存储到modules数组中。执行buildModule方法执行各种loaders解析字符串source,并把source转换成AST收集依赖。执行完buildModule后调用回调函数moduleReady,这个函数调用proceseeModuleDependencies方法处理当前module的dependencies,递归处理子模块。下面看看执行的函数是怎么实现的。
addModule方法,给this._modules对象添加属性identifier,属性值为module。其中变量identifier值为module的绝对路径。把module放到modules数组中。
buildModule方法,执行module模块的build方法,执行各种loaders解析module并收集依赖。
NormalModuleFactory.prototype.create = function(data, callback) {
var _this = this;
var dependencies = data.dependencies;
var context = data.context || this.context;
var request = dependencies[0].request;
var contextInfo = data.contextInfo || { };
_this.applyPluginsAsyncWaterfal("before-resolve", {
contextInfo: contextInfo,
context: context,
request: request,
Dependencies: dependencies
}, function(err, result) {
var factory = _this.applyPluginsWaterfall0("factory", null);
factory(result, function(err, module) {
if(module && _this.cachePredicate(module)) {
dependencies.forEach(function(d) {
d._NormalModuleFactoryCache = module;
});
}
callback(null, module);
});
});
};
现在看看moduleFactory.create是如何创建模块的。触发factory事件,执行监听函数。
function NormalModuleFactory(context, resolvers, options) {
Tapable.call(this);
this.plugin("factory", function( ) {
var _this = this;
return function(result, callback) {
var resolver = _this.applyPluginsWaterfall0("resolver", null);
resolver(result, function onDoneResolving(err, data) {
_this.applyPluginsAsyncWaterfall("after-resolve", data, function(err, result) {
var createdModule = new NormalModule(
result.request,
result.userRequest,
result.rawRequest,
result.loaders,
result.resource,
result.parser
);
return callback(null, createdModule);
});
});
}
});
}
"factory"事件监听是在类NormalModuleFactory构造函数中定义的,监听函数包含两部分,resolver和create module。resolver解析index.js的路径信息和相关的loaders路径等。create module根据解析的信息创建NormalModule对象module。 resolver的信息如下:
{
context: "/Users/lispringli/Desktop/webpack-2.2.0/bin",
request: "/Users/lispringli/Desktop/webpack-2.2.0/node_modules/babel-loader/lib/index.js?{\"presets\":[\"@babel/preset-env\"]}!/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
userRequest: "/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
rawRequest: "./test/src/index.js",
loaders: [
{
options: {
presets: [
"@babel/preset-env",
],
},
loader: "/Users/lispringli/Desktop/webpack-2.2.0/node_modules/babel-loader/lib/index.js",
},
],
resource: "/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
parser: {}
}
创建的module对象:
{
dependencies: [],
blocks: [],
variables: [],
context: "/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src",
reasons: [],
debugId: 1000,
lastId: -1,
id: null,
portableId: null,
index: null,
index2: null,
depth: null,
used: null,
usedExports: null,
providedExports: null,
chunks: [],
strict: false,
meta: {},
request: "/Users/lispringli/Desktop/webpack-2.2.0/node_modules/babel-loader/lib/index.js?{\"presets\":[\"@babel/preset-env\"]}!/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
userRequest: "/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
rawRequest: "./test/src/index.js",
parser: {},
resource: "/Users/lispringli/Desktop/webpack-2.2.0/bin/test/src/index.js",
loaders: [
{
options: {
presets: [
"@babel/preset-env",
],
},
loader: "/Users/lispringli/Desktop/webpack-2.2.0/node_modules/babel-loader/lib/index.js",
},
],
fileDependencies: [],
contextDependencies: [],
_source: null,
assets: {},
built: false,
_cachedSource: null,
}
NormalModule.prototype.build = function build(options, compilation, resolver, fs, callback){
var _this = this;
return _this.doBuild(options, compilation, resolver, fs, function(err){
_this.parser.parse(_this._source.source(), {
current: _this,
module: _this,
compilation: compilation,
options: options
});
return callback( );
});
}
var runloaders = require("loader-runner").runLoaders;
NormalModule.prototype.doBuild = function doBuild(options, compilation, resolver, fs, callback) {
runloaders({
resource: this.resource,
loaders: this.loaders,
context: loaderContext,
readResource: fs.readFile.bind(fs)
}, function(err, result) {
var source = result.result[0];
Module._source = new OriginalSource(source, module.identifier( ));
return callback( );
});
}
build方法包含两部分,doBuild和doBuild回调。doBuild执行各种loaders解析index.js,生成source字符串。source字符串如下所示。
"import { A } from './a.js';\nfunction test() {\n var tmp = 'something';\n return tmp + A;\n}\nvar r = test();"
doBuild回调执行parse.parse,把source转成AST,遍历AST获取依赖。parse把index.js解析成AST树,然后遍历AST树收集依赖,生成的模块依赖树如下图所示。然后进入seal阶段,运用module以及module的dependencies信息整合出最终的chunk。在遍历AST时,遇到import语句增加HarmonyImportDependency依赖,这个依赖又会创建生成a.js module。在遍历到函数test的return temp + A时,A是引入的变量,给module增加一个HarmonyImportSpecifierDepency依赖,在最终打包文件的时候会把return tmp + A 替换成return tmp + a_js_WEBPACK_IMPORTED_MODULE_0["A"]。
make过程:
- 创建 index.js module。
- 将module保存到compilition。
- build Module。解析index.js文件,执行loaders生成source 字符串。然后通过parse把source生成AST,并遍历AST来收集依赖。
- 处理module的依赖,处理递归处理子模块,最终生成模块依赖树。