webpack源码之模块编译+输出最终文件

前言

紧接着上一篇run方法的来讲,当执行逻辑执行到moduleFactory.create的回调函数中时,回调函数中逻辑主要有三点:

  • addModule:添加模块到指定地方
  • onModule:entry对应模块赋值操作
  • buildModule:编译模块

重要逻辑不言而喻即buildModule,本篇即以此处为起点分析webpack的主要的编译逻辑。

buildModule之前相关逻辑

首先梳理下moduleFactory.create回调函数的具体逻辑,具体如下:
在这里插入图片描述

addModule

该实例方法是用于添加module对象到modules和_modules实例属性中的,主要逻辑有三点:

  • _modules中是否已存在对应模块
  • cache中是否存在对应模块即对应模块是否已加载过

    如果已加载过,就需要判断是否需要重新编译,需要需要重新编译就会调用相应方法并返回编译后的模块对象

  • 添加模块并返回布尔值

该实例方法存在两种返回值:布尔值、module对象

onModule

这里暂不分析缓存过的模块处理,直接看初始化过程的逻辑,onModule函数,该函数是从由于赋值entry相应参数的,即逻辑如下:

entry.module = module;
this.entries.push(module);
module.issuer = null;

buildModule具体逻辑

this.buildModule(
	module,
	false,
	null,
	null,
	(err) => {
		// 其他逻辑点
		moduleReady.call(this);
	}
);

这里需要注意有两处:buildModule方法执行、moduleReady函数。其中buildModule的逻辑主要有两点:

  • applyPlugins1(‘build-module’, module)
  • module.build实例方法调用

而module.build实例方法主要逻辑有两点:

  • 重置module对象的相关属性
  • 调用doBuild实例方法并返回其结果

doBuild方法中主要逻辑如下:

  • createLoaderContext创建当前的loaderContext即上下文
  • runLoaders函数执行,即使用loader-runner来进行模块的转换

当runLoaders转换完模块后会调用其对应的回调函数创建createSource对象,该对象就是转换后的对应模块赋值给_source实例属性。

最重要的逻辑是doBuild执行完毕后的回调函数,该回调函数中会parse对应的模块。

// _source就是loader转换后的模块对象
this.parser.parse(this._source.source(), {
	current: this,
	module: this,
	compilation: compilation,
	options: options
});

这里需要注意有2点:

  • parser背后是使用acorn来实现转换后JS模块的解析
  • 当前模块依赖的收集也是在此处完成的

当parser解析完成得到当前模块的所有依赖,实际上module.build的主要工作就已经完成了,就会执行其相关回调函数。webpack源码中函数 + 其回调的形式很多,最后执行buildModule的回调函数,主要就是moduleReady函数的执行。

moduleReady函数执行

该函数实际上就是对开启模块的依赖的编译,即webpack以入口文件收集所有依赖的动作。

function moduleReady() {
	this.processModuleDependencies(module, err => {
		if(err) {
			return callback(err);
		}

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

processModuleDependencies函数,顾名思义处理模块的依赖。该函数的逻辑主要是:

  • addDependenciesBlock函数执行

    简而言之就是将某一module对象中的依赖copy到Compiltion对象的dependencies实例属性中

  • addModuleDependencies实例方法执行
addModuleDependencies

该实例函数的逻辑实际上有处理addEntry时的_addModuleChain逻辑类似:

  • 找到对应的moduleFactory对象
  • 执行moduleFactory对象的create方法创建对应的模块对象
  • create回调函数中主要是开始module的编译即buildModule

addModuleDependencies相对于_addModuleChain,差异的主要逻辑是:

  • 找到每一个dependency依赖对象的构造函数对应的ModuleFactory并生成对应的factory对象并构键[factory,dependencies]数组结构
  • 调用async库中的forEach对构建的数组结构进行遍历,而迭代器函数就是使用调用对应的factory的create实例方法
  • 对应的factory的create的回调函数中主要逻辑也是执行buildModule,buildModule的回调函数再执行processModuleDependencies如此循环往复

processModuleDependencies的一直执行开启所有依赖的收集和处理工作,当所有依赖都执行完毕后呢?webpack的逻辑是什么?

所有逻辑回到compile实例函数的make事件执行完后的回调函数

模块编译完成后逻辑

当所有make事件对应的函数执行完毕后,会调用相关的回调函数,具体代码如下:

compilation.finish();
compilation.seal(err => {
	if(err) return callback(err);
	this.applyPluginsAsync(
		"after-compile",
		compilation,
		err => {
			if(err) return callback(err);
			return callback(null, compilation);
		});
});

编译完成后的回调函数中主要有两个逻辑:

  • Compilation对象的finish实例方法的执行

    主要逻辑:执行finish-modules事件、report对应模块的报错或警告信息

  • Compilation对象的seal实例方法的执行
  • seal实例方法中callback的执行
seal实例方法

seal实例方法中主要是各个事件的触发执行,具体如下:

  • seal
  • optimize
  • optimize-modules-basic
  • optimize-modules
  • optimize-modules-advanced
  • after-optimize-modules
  • optimize-chunks-basic
  • optimize-chunks
  • optimize-chunks-advanced
  • after-optimize-chunks
  • optimize-tree
  • after-optimize-tree
  • optimize-chunk-modules-basic
  • optimize-chunk-modules
  • optimize-chunk-modules-advanced
  • after-optimize-chunk-modules

等等,上面仅仅列举了一小部分触发的事件。seal中涉及到的事件可以查看compilation钩子涉及到optimize和chunks相关的,基本上就是seal中触发的了。
seal实例方法主要的逻辑简单来说是有三点:

  • 处理module
  • 处理并优化chunks
  • 最终文件内容生成

这里最需要关注的就是Compilation对象的createChunkAssets实例方法,具体逻辑如下:
在这里插入图片描述
整个createChunkAssets实例方法生成打包文件内容,该部分逻辑主要是生成的是一个数组,该数组包含两类值:

  • 静态文本就是webpack实现的函数代码文本和相关注释
  • 相关的source对象,是输出文件中动态的部分

实际上seal实例主要的逻辑到此就结束了,seal回调函数的执行,即callback的执行。这里callback就是compile实例方法被调用被传入进来的,回溯到以前的逻辑,可以知道:

run实例方法中定义了onCompiled内部函数,作为了compile实例方法的callback参数传入

onCompiled函数执行

该函数的主要逻辑是执行emitAssets,而该实例函数就是输出最终的打包文件,逻辑分为:

  • 输出文件的地址outputPath值的处理
  • 使用outputFileSystem来创建目录名和文件名
  • 向文件中写入内容
  • 触发after-emit事件:生成资源到 output 目录之后

总结

当创建模块对象后,就进行下一步模块编译的动作,之后的逻辑归纳总结如下:

  • buildModule开启模块编译
  • 调用指定module对象的build方法,最终调用doBuild实例方法
  • doBuild实例方法中调用runLoaders通过loader将模块转换成JS模块
  • 完成模块loader转换后,执行doBuild的回调函数:通过acorn来parser转换后的JS模块
  • parser过程会做相关依赖收集等工作
  • 完成一个模块对象的转换和解析后,就会开启循环处理转换和解析所有依赖模块
  • 完成所有的模块解析后,就会触发相应的事件,并完成chunks的优化处理和内容构建
  • 最终生成output目录和文件并写入相关内容
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值