.37-浅析webpack源码之事件流make(4)
赶紧完结这个系列咯,webpack4都已经出正式版了。
之前的代码搜索到js文件的对应loader,并添加到了对象中返回,流程如下:
this.plugin("factory", () => (result, callback) => { let resolver = this.applyPluginsWaterfall0("resolver", null); // Ignored if (!resolver) return callback(); // 这里的data就是返回的大对象 /* data => { context: 'd:\\workspace\\doc', request: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js!d:\\workspace\\doc\\input.js', dependencies: [SingleEntryDependency],, userRequest: 'd:\\workspace\\doc\\input.js', rawRequest: './input.js', loaders: [ { loader: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js' } ], resource: 'd:\\workspace\\doc\\input.js', 还有package.json与parser相关的属性 } */ resolver(result, (err, data) => { if (err) return callback(err); // Ignored if (!data) return callback(); // direct module if (typeof data.source === "function") return callback(null, data); this.applyPluginsAsyncWaterfall("after-resolve", data, (err, result) => { // ... }) }) })
这个对象的request将入口文件的路径与loader拼接起来并用!分割,所有的属性基本上都与路径相关。
after-resolve事件流
这里会触发after-resolve事件流,注入地点如下:
const matchJson = /\.json$/i; params.normalModuleFactory.plugin("after-resolve", (data, done) => { // if this is a json file and there are no loaders active, we use the json-loader in order to avoid parse errors // @see https://github.com/webpack/webpack/issues/3363 if (matchJson.test(data.request) && data.loaders.length === 0) { data.loaders.push({ loader: jsonLoaderPath }); } done(null, data); });
注释已经写的很明白了,这里是检测待处理文件类型是否是json文件,如果是并且没有对应的loader,就将内置的json-loader作为loader。
callback
接下来看回调函数内容:
this.applyPluginsAsyncWaterfall("after-resolve", data, (err, result) => { if (err) return callback(err); // Ignored if (!result) return callback(); // 无此事件流 返回undefined let createdModule = this.applyPluginsBailResult("create-module", result); if (!createdModule) { if (!result.request) { return callback(new Error("Empty dependency (no request)")); } // 创建 createdModule = new NormalModule( result.request, result.userRequest, result.rawRequest, result.loaders, result.resource, result.parser ); } // 无此事件流 createdModule = this.applyPluginsWaterfall0("module", createdModule); return callback(null, createdModule); });
这里的两个事件流都是没有的,所以只需要看那个创建类的过程,然后就直接返回了。
只看下构造函数:
class NormalModule extends Module { /* request => 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js!d:\\workspace\\doc\\input.js' userRequest => 'd:\\workspace\\doc\\input.js' rawRequest => './input.js' loaders => [ { loader: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js' } ] resource => 'd:\\workspace\\doc\\input.js' parser => [Parser] */ constructor(request, userRequest, rawRequest, loaders, resource, parser) { super(); this.request = request; this.userRequest = userRequest; this.rawRequest = rawRequest; this.parser = parser; this.resource = resource; this.context = getContext(resource); this.loaders = loaders; this.fileDependencies = []; this.contextDependencies = []; this.warnings = []; this.errors = []; this.error = null; this._source = null; this.assets = {}; this.built = false; this._cachedSource = null; } // ...原型方法 }
只有一个getContext方法是执行的,这个方法比较简单,过一下就行:
exports.getContext = function getContext(resource) { var splitted = splitQuery(resource); return dirname(splitted[0]); }; // 切割参数 function splitQuery(req) { var i = req.indexOf("?"); if (i < 0) return [req, ""]; return [req.substr(0, i), req.substr(i)]; } // 返回目录 // d:\\workspace\\doc\\input.js => d:\\workspace\\doc(反斜杠这里有转义) function dirname(path) { if (path === "/") return "/"; var i = path.lastIndexOf("/"); var j = path.lastIndexOf("\\"); var i2 = path.indexOf("/"); var j2 = path.indexOf("\\"); var idx = i > j ? i : j; var idx2 = i > j ? i2 : j2; if (idx < 0) return path; if (idx === idx2) return path.substr(0, idx + 1); return path.substr(0, idx); }
返回了入口文件路径的目录,也就是上下文。
这样一路回调返回,回到了最初的原型方法create:
// NormalModuleFactory const factory = this.applyPluginsWaterfall0("factory", null); // Ignored if (!factory) return callback(); factory(result, (err, module) => { // 这里的module就是new出来的NormalModule对象 if (err) return callback(err); // this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache); // 由于默认情况下unsafeCache为true 所以这个函数默认一直返回true if (module && this.cachePredicate(module)) { dependencies.forEach(d => d.__NormalModuleFactoryCache = module); } callback(null, module); });
这里对之前处理的入口文件对象做了缓存。
返回又返回,回到了Compilation对象中:
_addModuleChain(context, dependency, onModule, callback) { // ... this.semaphore.acquire(() => { // 从这里出来 moduleFactory.create({ contextInfo: { issuer: "", compiler: this.compiler.name }, context: context, dependencies: [dependency] }, (err, module) => { if (err) { this.semaphore.release(); return errorAndCallback(new EntryModuleNotFoundError(err)); } let afterFactory; // 不存在的属性 if (this.profile) { if (!module.profile) { module.profile = {}; } afterFactory = Date.now(); module.profile.factory = afterFactory - start; } const result = this.addModule(module); // ... }); }); }
常规的错误处理,然后判断了下profile属性,这个属性大概是用计算前面factory整个事件流的触发时间。
总之,接着调用了addModule原型方法
addModule
// 第二个参数未传 addModule(module, cacheGroup) { // 这里调用的是原型方法 /* identifier() { return this.request; } */ // 愚蠢的方法 const identifier = module.identifier(); // 空对象没有的 if (this._modules[identifier]) { return false; } // 缓存名是'm' + request const cacheName = (cacheGroup || "m") + identifier; // 如果有缓存的情况下 if (this.cache && this.cache[cacheName]) { // ... } // 这个方法是在父类上面 // 作用是清空属性 // 然而并没有什么属性可以清 跳过 module.unbuild(); // 将类分别加入各个对象/数组 this._modules[identifier] = module; if (this.cache) { this.cache[cacheName] = module; } this.modules.push(module); return true; }
这个方法其实并没有做什么实际的东西,简单来讲只是添加了缓存。
接下来看下面的代码:
_addModuleChain(context, dependency, onModule, callback) { // ... this.semaphore.acquire(() => { moduleFactory.create({ contextInfo: { issuer: "", compiler: this.compiler.name }, context: context, dependencies: [dependency] }, (err, module) => { // ... // 返回一个true const result = this.addModule(module); // 跳过下面两步 if (!result) { // ... } if (result instanceof Module) { // ... } // 进入这里 /* (module) => { entry.module = module; this.entries.push(module); module.issuer = null; } */ onModule(module); this.buildModule(module, false, null, null, (err) => { if (err) { this.semaphore.release(); return errorAndCallback(err); } if (this.profile) { const afterBuilding = Date.now(); module.profile.building = afterBuilding - afterFactory; } moduleReady.call(this); }); function moduleReady() { this.semaphore.release(); this.processModuleDependencies(module, err => { if (err) { return callback(err); } return callback(null, module); }); } }); }); }
这个onModule来源于方法的第三个参数,比较简单,没做什么事,然后继续跑调用了原型方法buildModule。
buildModule
// this.buildModule(module, false, null, null, (err) => {}) buildModule(module, optional, origin, dependencies, thisCallback) { // 无此事件流 this.applyPlugins1("build-module", module); if (module.building) return module.building.push(thisCallback); const building = module.building = [thisCallback]; function callback(err) { module.building = undefined; building.forEach(cb => cb(err)); } module.build(this.options, this, this.resolvers.normal, this.inputFileSystem, (error) => { // ... }); }
总的来说并没有做什么事,传进来的callback被封装并带入build方法的回调中。
module.build
// options为添加了默认参数的配置对象webpack.config.js build(options, compilation, resolver, fs, callback) { this.buildTimestamp = Date.now(); this.built = true; this._source = null; this.error = null; this.errors.length = 0; this.warnings.length = 0; this.meta = {}; return this.doBuild(options, compilation, resolver, fs, (err) => { // ... }); }
一串串的赋值,又是doBuild方法。
module.doBuild
doBuild(options, compilation, resolver, fs, callback) { this.cacheable = false; const loaderContext = this.createLoaderContext(resolver, options, compilation, fs); runLoaders({ resource: this.resource, loaders: this.loaders, context: loaderContext, readResource: fs.readFile.bind(fs) }, (err, result) => { // ... }); }
呃,已经连续调用好多个函数了。
createLoaderContext
createLoaderContext(resolver, options, compilation, fs) { const loaderContext = { // .. }; compilation.applyPlugins("normal-module-loader", loaderContext, this); // 无此值 if (options.loader) Object.assign(loaderContext, options.loader); return loaderContext; }
这里先不关心这个loaderContext里面到底有什么,因为后面会调用的,直接看事件流'normal-module-loader'。
// LoaderTargetPlugin.js compilation.plugin("normal-module-loader", (loaderContext) => loaderContext.target = this.target);
这里由于配置对象没有target参数,所以这个没有用。
// LoaderPlugin.js compilation.plugin("normal-module-loader", (loaderContext, module) => { loaderContext.loadModule = function loadModule(request, callback) { // ... }; });
这个仅仅是加了个属性方法,跳过。
最后返回了一个包含很多方法的对象loaderContext。
第二步是调用runLoaders方法,将之前生成的对象传进去,这里终于要调用loader对文件进行处理了!!!!
下节可以有干货,这节流水账先这样了。