2-2-5webpack源码

1.内容概述
1.webpack打包流程拆分不同的环节
2.做了什么工作
3.如何完成的
4.使用了什么技术
5.模块的加载和loader的使用
6.webpack的配置演示

2.打包后源码分析
path: path.resolve(‘dist’) 输出目录的路径
对输出文件进行分析
1.我们称之为模块定义
“./src/index.js”: 路径模块id 视为键值对
(function (module, exports) {
console.log(‘index.js内容’)
module.exports = ‘入口文件导出内容’ //模块定义
})
值是一个函数的调用
传递模块的参数
/**
* 01 打包后的文件就是一个函数自调用,当前函数调用时传入一个对象
* 02 这个对象我们为了方便将之称为是模块定义,它就是一个键值对
* 03 这个键名就是当前被加载模块的文件名与某个目录的拼接()
* 04 这个键值就是一个函数,和 node.js 里的模块加载有一些类似,会将被加载模块中的内容包裹于一个函数中
* 05 这个函数在将来某个时间点上会被调用,同时会接收到一定的参数,利用这些参数就可以实现模块的加载操作
*
* 06 针对于上述的代码就相当于是将 {}(模块定义) 传递给了 modules
*/

	2.installedModules  缓存  存放将来被加载的模块对象
	
	3.  function __webpack_require__(moduleId)    核心作用是返回模块的导出
			return了 module.exports
			
			1.先判断installedModules缓存里面有没有

	4.__webpack_require__.t 里面调用了  3的方法
		return __webpack_require__(__webpack_require__.s = "./src/index.js")  进入方法的调用
		
	5.__webpack_require__.s  缓存路径的   这个语句不影响
	
	6.初始化环境
		完成最简单的打包操作
		生成输出文件
		匿名函数自动调用   实参
			路径  模块的id   路径+文件名
			值为函数 被加载模块的内容  外部包裹函数  require函数里面调用
			调用就可以拿到模块的加载内容

3.单文件打包后源码调试
添加属性和方法
连续赋值 从右往左看
modules[moduleId] 为函数
调用函数
modules[moduleId].call(module.exports, module, module.exports, webpack_require);

		module.l= true 表示加载过了
		
得到自调用函数 生成模块定义
模块定义传给modules
调用reqire方法  传入模块id
调用模块方法  导出module.exports

4.功能函数说明
引入其他模块分析执行流程
在主js文件里 会有导入附属js 的语句
let name = webpack_require(/*! ./login.js */ “./src/login.js”)

	模块定义里有多个键值对了
	
1.installedModules  缓存  存放将来被加载的模块对象
2. __webpack_require__(moduleId)    核心作用是返回模块的导出
3.__webpack_require__.m = modules    将模块定义保存一份,通过 m 属性挂载到自定义的方法身上
4. __webpack_require__.c = installedModules    保存缓存
5.__webpack_require__.o   判断被传入的对象 obj 身上是否具有指定的属性 **** ,如果有则返回 true 
6. __webpack_require__.d   通过 o 方法判断是否没有 name 属性 并且添加一个name属性 和访问器
7.__webpack_require__.r
	1.判断是否是esmodules 
		是的话添加一个 值为 module 的Symbol
	2.添加一个 __esModule 属性
8.__webpack_require__.t     未调用
	调用后会拿到模块中的内容value
	对于 value 来说我们可能会直接返回,也可能会处理之后再返回
9.__webpack_require__.n  分会module的default属性	未调用

10.__webpack_require__.p   配置里面的public属性

5.commonJS模块打包
主js采用 require函数

只是对__webpack_require__  只是 对require进行了替换  就是上面学习的例子
js里面
	esModules模块
		调用了__webpack_require__.r 方法  判断是否esmodules  并且添加 Module的的Symbol
										添加 __esModule 属性 value 为true 
		 调用了__webpack_require__.d   处理age 属性 作为d方法里面name值 添加age属性和访问器

6.esModule 打包
主js 采用 import from
调用了__webpack_require__.r 方法 判断是否esmodules 并且添加 Module的的Symbol
添加 _esModule 属性 value 为true
webpack自定义方法 替换
var login_js__WEBPACK_IMPORTED_MODULE_0
= webpack_require(/*! ./login.js */ “./src/login.js”);

	module.exports = 'zce'
	使用 __webpack_require__.n  方法添加default 并赋予值   default 和别的值 区别时 多调一次 __webpack_require__.n 方法
		__webpack_require__.d  添加属性值 
			__webpack_require__.o 判断是否esmodule 

打包的最后一个环节

两种规范都可以使用  作打包处理   CJS处理的结果更少  但还是根据情况使用

7.功能函数手写

手写 mybuilt.js hzj.js
从点头到尾写一遍 不包含__webpack_require__.t 方法

下
	esmodule模式下的验证
	__webpack_require__下的r和d方法

8.懒加载流程梳理
懒加载 打包的时候并没有加载
import函数 then()
多出两个方法
webpack_require.e(“login”)
返回一个 Promise.all
webpack_require.t.bind (null, “./src/login.js”, 7)
null 修改 this 不关注
7 二进制 001
返回 一个 ns

1.import()可以实现指定模块的懒加载操作
2.核心原理时 jsonp
3.t 方法针对内容进行不同处理 处理方法取决于传入的数值( mode,8 6 7 3 2 1)

9.T方法分析及实现
webpack_require.t(value,mode) 导出模块定义或者处理再导出
01 接收二个参数,一个是 value 一般用于表示被加载的模块id ,第二个值 mode 是一个二进制的数值
02 t 方法内部做的第一件事情就是调用自定义的 require 方法加载value 对应的模块导出,重新赋值给 value
03 当获取到了这个 value 值之后余下的 8 4 ns 2 都是对当前的内容进行加工处理,然后返回使用
04 当mode & 8 成立是直接将 value 返回 ( commonJS )
05 当 mode & 4 成立时直接将 value 返回(esModule)
06 如果上述条件都不成立,还是要继续处理 value ,定义一个 ns {}
6-1 如果拿到的 value 是一个可以直接使用的内容,例如是一个字符串,将它挂载到 ns 的 default 属性上
6-2 如果不是可直接使用的,就先遍历对象的值,给ns上加各种属性,方便直接调用

按位方式
	a	1000     8
	b	0010	 2
	a&b 0000	 0

10.懒加载源码分析

.bind() 补充之前函数的第一个入参修改
第一个参数会作为原函数运行时的 this 指向

	webpackJsonpCallback
		1.模块依赖关系的合并
		2.让当前的promise变成 成功的状态
		
		
  jsonpArray.push = webpackJsonpCallback;  也重写了window["webpackJsonp"] 
  
下  实现懒加载的流程
  	__webpack_require__.e  方法  返回promise	 jsonp
		installedChunkData 为0表示已加载   
						 undefined 未加载
						 promise  正在加载
		undefined的时候
			installedChunkData 添加 resolve reject 和promise 
			调用 jsonpScriptSrc  方法
			
		document.head.appendChild(script);  添加一个head标签

		
	返回Promise.all(promises); 但是promise没有进入成功态
	这个时候加载指定的文件内容
	进入webpackJsonpCallback
		获取需要加载的id         chunkId = chunkIds[i];
		获取模块的resolve方法
		标记加载过      installedChunks[chunkId] = 0;
		判断模块数据有没有当前要加载的模块数据     并复制
		执行resolve方法
	进入__webpack_require__.t 方法	
		加载模块  获得导出模块内容
			对模块内容进行处理 创建一个ns对象 并将模块内容加进去
			返回ns对象(导出内容)

	jsonpScriptSrc	路径和命名

11.手写懒加载源码
webpackJsonpCallback 回调方法重写
Object.prototype.hasOwnProperty.call(installedChunks, chunkId) 判断是否加载了
将 模块的resolve 放到resolves
将关系放到modules上
执行放到resolves里的resolve方法

__webpack_require__.e  实现json加载内容 利用promise实现异步加载操作
	有primise就执行 没有就创建
	var promise = new Promise(function (resolve, reject) {
      installedChunkData = installedChunks[chunkId] = [resolve, reject];
    });
	promises.push(installedChunkData[2] = promise);

12.webpack 与tapable 底层核心工具库
配置初始化
内容编译
编译后输出
事件驱动型事件流工作机制

bundles的compilation

tapable 独立的库
	实例化hook注册事件监听
	通过hook触发事件监听
	执行懒编译生成的可执行代码
	
hook本质是tapable实例对象   同步和异步
	Hook      普通钩子  监听器之间互相独立不干扰
	BailHook  熔断钩子  某个监听返回非undefined时 后续不执行
	WaterfallHook 瀑布钩子  上一个监听的返回值可传递至下一个
	LoopHook 循环钩子 如果当前未返回false  则一直执行

同步钩子
	SynckHook
	SyncBailHook
	SyncWaterfallHook
	SyncLoopHook
	
异步串行钩子
	AsyncSeriesHook
	AsyncSeriesBailHook
	AsyncSeriesWaterfallHook
异步并行钩子
	AsyncParalleHook
	AsyncParalleBailHook

13.同步钩子的使用及调试
依赖 tapable
SyncHook
导入SyncHook require(‘tapable’)
定义Hook实例 new SyncHook([‘name’, ‘age’])
tap 设置监听
call 触发监听

SyncBailHook   同理  熔断:某一个地方断掉了 就不执行了
	return 非 undefined  后续监听不执行了
	
SyncWaterfallHook
	return 的东西可以给下一个钩子使用
	
SyncLoopHook
	在钩子循环结束前 不会执行之后的钩子  只会执行前面的钩子
	
	taps里面放了多个数组
		如果当前的钩子返回undefined  则会从头循环钩子  从头开始循环

14.异步钩子的使用及调试
安装tapable库
库里有很多类
1.AsyncParallelHook 并行钩子
异步钩子 添加事件监听有三种方式
tap tapAsync tapPromise
tap
hook.tap(‘fn1’, function (name) {
console.log(‘fn1—>’, name)
})
allAsync 后面的参数要回调函数
hook.callAsync(‘zoe’, function () {
console.log(‘最后执行了回调操作’)
})

			tapAsync
				hook.tapAsync('fn1', function (name, callback) {
				  setTimeout(() => {
					console.log('fn1--->', name)
					callback()
				  }, 1000)
				})
				
				hook.callAsync('lg', function () {
				  console.log('最后一个回调执行了')
				  console.timeEnd('time')
				}) 
				
			tapPromise  回调方式通过promise实现
				hook.tapPromise('fn1', function (name) {
				  return new Promise(function (resolve, reject) {
					setTimeout(() => {
					  console.log('fn1--->', name)
					  resolve()
					}, 1000)
				  })
				})
				
				调用 直接使用promise
				hook.promise('foo').then(() => {
				  console.log('end执行了')
				  console.timeEnd('time')
				})
		2.AsyncParallelBailHook  并行异步熔断钩子	
			hook.tapAsync('fn2', function (name, callback) {
			  setTimeout(() => {
				console.log('fn2--->', name)
				callback('err')
			  }, 2000)
			})
			
			执行到错误的钩子然后回调 callAsync函数
			回调callAsync函数之后执行 报错误的钩子后的钩子
			
		3.AsyncSeriesHook   异步串行钩子
			同一  只是是串行的

15.SyncHook源码分析
Hook.js是父类
HookCodeFactory 钩子代码工厂 继承

先实例化Hook 再SyncHook
构造Hook 简写
this.args = args
this.taps = [] // 将来用于存放组装好的 {} 默认空数组
this._x = undefined // 将来在代码工厂函数中会给 _x = [f1, f2, f3…]
赋值一系列方法
重写 SyncHook 里的方法修饰 主要还是调用Hook的方法
tap方法 将字符串转化为对象
调用_insert 方法 将东西添加到taps里面
_insert
this.taps[this.taps.length] = options 将内容转换成对象存到taps里面

下
	call方法
		里面调用_createCall方法
	_createCall
		返回对象this.compile
			taps: this.taps,
			args: this.args
			
	到了  SyncHook的compile方法 	调了factory的setup方法  返回create方法
		factory   
			setup  把taps的函数放在了.x属性里面
			
			create 心就是创建一段可执行的代码体然后返回  然后拼接头和身体

16.SyncHook手写实现
上 监听
* 01 实例化 hook , 定义 _x = [f1, f2, …] taps = [{}, {}]
* 02 实例调用 tap taps = [{}, {}]
* 03 调用 call 方法, HookCodeFactory setup create
* 04 Hook SyncHook HookCodeFactory

下 调用	
	call()
		核心为factory的 create方法 
		组装函数     fn = new Function("name, age", 
		"var _x = this._x, var _fn0 = _x[0]; _fn0(name, age);")
		head content

17.AsyncParalleHook源码分析
核心为factory的 create方法
组装函数 核心为content方法 判断是否执行完最后的事情 再回调
相比同步函数 异步函数需要有一个最后的回调函数

18.定位webpack打包入口
执行入口
complier
run方法

看源码不需要从上到下看懂全部的含义
从执行顺序看

01 cmd 文件核心的作用就组装了 node *****/webpack/bin/webpack.js

02 webpack.js 中核心的操作就是 require 了 node_modules/webpack-cli/bin/cli.js

03 cli.js 
  01 当前文件一般有二个操作,处理参数,将参数交给不同的逻辑(分发业务)
  02 options 
  03 complier 
  04 complier.run( 至于run 里面做了什么,后续再看,当前只关注代码入口点 )

19.编译主流程分析
options就是webpack.config.js导出的内容
complier继承了tapable的类
主流程编译流程:
合并默认配置和自定义配置 —》 实例化compiler 设置Node文件读写的能力 通过循环 挂载plugins 处理webpack内部默认的插件(入口文件)
触发配置
Compiler.beforeRun
Compiler.run
Compiler.beforeCompile
Compiler.compile 编译
Compiler.make

20.手写webpack.js
01 实例化 compiler 对象
02 初始化 NodeEnvironmentPlugin(让compiler具体文件读写能力)
03 挂载所有 plugins 插件至 compiler 对象身上
04 挂载所有 webpack 内置的插件(入口)
05 返回 compiler 对象即可

1.run.js   
	使用		2.webpack.js    调用webpack.config.js的内容作为options
		生成compiler 
			引用NodeEnvironmentPlugin类   3.NodeEnvironmentPlugin.js
		
	调用 compiler的 run方法    4.compiler .js

21.EntryOptionPlugin webpack.config.js里的入口配置插件管理
WebpackOptionsApply实例对象
process(options, compiler) 方法
EntryOptionPlugin 类 apply 方法
里面的 entryOption 是一个钩子实例,
entryOption 在 EntryOptionPlugin 内部的 apply 方法中调用了 tap (注册了事件监听)
上述的事件监听在 new 完了 EntryOptionPlugin 之后就调用了
itemToPlugin, 它是一个函数,接收三个参数( context item 'main)
SingleEntryPlugin
在调用 itemToPlugin, 的时候又返回了一个 实例对象
有一个构造函数,负责接收上文中的 context entry name
compilation 钩子监听
make 钩子监听

22.手写EntryOptionPlugin 流程
先新建 文件 WebpackOptionsApply.js 声明类 WebpackOptionsApply WebpackOptionsApply.js
调用 process(options, compiler)方法 WebpackOptionsApply.js
里面调用了 EntryOptionPlugin 的 apply方法 EntryOptionPlugin.js
方法里定义了一个钩子 entryOption EntryOptionPlugin.js
钩子调用了 itemToPlugin 函数 EntryOptionPlugin.js
函数返回了 SingleEntryPlugin 的实例的apply方法 SingleEntryPlugin.js
apply方法里监听了make 异步钩子 SingleEntryPlugin.js
compilation 触发

23.run方法实现
run方法流程
finalCallback方法 调用callback
onCompiled 对callbak处理
this.hooks.beforeRun 触发钩子

24.compile方法实现
newCompilationParams 方法 获取参数
normalModuleFactory 模块工厂 还有上下文等等
beforeRun 触发钩子的监听
compile 触发钩子的监听
创建一个 compilation newCompilation类 传入最开始的参数
make 触发钩子的监听

		-------------------------------------------------------------------------------
		  02 compile 方法执行
			1 准备参数(其中 normalModuleFactory 是我们后续用于创建模块的)
			2 触发beforeCompile
			3 将第一步的参数传给一个函数,开始创建一个 compilation (newCompilation)
			4 在调用 newCompilation 的内部
			  - 调用了 createCompilation 
			  - 触发了 this.compilation 钩子 和 compilation 钩子的监听
		------------------------------------------------------------------------------------

25.make前流程回顾
一、步骤 run.js
01 实例化 compiler 对象( 它会贯穿整个webpack工作的过程 )
02 由 compiler 调用 run 方法

	二、compiler 实例化操作 webpack.js
	  01 compiler 继承 tapable,因此它具备钩子的操作能力(监听事件,触发事件,webpack是一个事件流)

	  02 在实例化了 compiler 对象之后就往它的身上挂载很多属性,其中 NodeEnvironmentPlugin 这个操作就让它具备了
	  文件读写的能力(我们的模拟时采用的是 node 自带的 fs )

	  03 具备了 fs 操作能力之后又将 plugins 中的插件都挂载到了 compiler 对象身上  

	  04 将内部默认的插件与 compiler 建立关系,其中 EntryOptionPlugin 处理了入口模块的 id 

	  05 在实例化 compiler 的时候只是监听了 make 钩子(SingleEntryPlugin)
		5-1 在 SingleEntryPlugin 模块的 apply 方法中有二个钩子监听  compilation和make
		5-2 其中 compilation 钩子就是让 compilation 具备了利用 normalModuleFactory 工厂创建一个普通模块的能力
		5-3 因为它就是利用一个自己创建的模块来加载需要被打包的模块 
		5-4 其中 make 钩子 在 compiler.run 的时候会被触发,走到这里就意味着某个模块执行打包之前的所有准备工作就完成了
		5-5 addEntry 方法调用()

	三、run 方法执行( 当前想看的是什么时候触发了 make 钩子 ) compiler.js

	  01 run 方法里就是一堆钩子按着顺序触发(beforeRun run compile)

	  02 compile 方法执行
		1 准备参数(其中 normalModuleFactory 是我们后续用于创建模块的)
		2 触发beforeCompile
		3 将第一步的参数传给一个函数,开始创建一个 compilation (newCompilation)
		4 在调用 newCompilation 的内部
		  - 调用了 createCompilation 
		  - 触发了 this.compilation 钩子 和 compilation 钩子的监听
	  03 当创建了 compilation 对象之后就触发了 make 钩子

	  04 当我们触发 make 钩子监听的时候,将 compilation 对象传递了过去 

	四、总结 

	  1 实例化 compiler  
	  2 调用 compile 方法
	  3 newCompilation 
	  4 实例化了一个 compilation 对象(它和 compiler 是有关系)
	  5 触发 make 监听 
	  6 addEntry 方法(这个时候就带着 context name entry  一堆的东西) 就奔着编译去了.....

1.内容概述
1.webpack打包流程拆分不同的环节
2.做了什么工作
3.如何完成的
4.使用了什么技术
5.模块的加载和loader的使用
6.webpack的配置演示

2.打包后源码分析
path: path.resolve(‘dist’) 输出目录的路径
对输出文件进行分析
1.我们称之为模块定义
“./src/index.js”: 路径模块id 视为键值对
(function (module, exports) {
console.log(‘index.js内容’)
module.exports = ‘入口文件导出内容’ //模块定义
})
值是一个函数的调用
传递模块的参数
/**
* 01 打包后的文件就是一个函数自调用,当前函数调用时传入一个对象
* 02 这个对象我们为了方便将之称为是模块定义,它就是一个键值对
* 03 这个键名就是当前被加载模块的文件名与某个目录的拼接()
* 04 这个键值就是一个函数,和 node.js 里的模块加载有一些类似,会将被加载模块中的内容包裹于一个函数中
* 05 这个函数在将来某个时间点上会被调用,同时会接收到一定的参数,利用这些参数就可以实现模块的加载操作
*
* 06 针对于上述的代码就相当于是将 {}(模块定义) 传递给了 modules
*/

	2.installedModules  缓存  存放将来被加载的模块对象
	
	3.  function __webpack_require__(moduleId)    核心作用是返回模块的导出
			return了 module.exports
			
			1.先判断installedModules缓存里面有没有

	4.__webpack_require__.t 里面调用了  3的方法
		return __webpack_require__(__webpack_require__.s = "./src/index.js")  进入方法的调用
		
	5.__webpack_require__.s  缓存路径的   这个语句不影响
	
	6.初始化环境
		完成最简单的打包操作
		生成输出文件
		匿名函数自动调用   实参
			路径  模块的id   路径+文件名
			值为函数 被加载模块的内容  外部包裹函数  require函数里面调用
			调用就可以拿到模块的加载内容

3.单文件打包后源码调试
添加属性和方法
连续赋值 从右往左看
modules[moduleId] 为函数
调用函数
modules[moduleId].call(module.exports, module, module.exports, webpack_require);

		module.l= true 表示加载过了
		
得到自调用函数 生成模块定义
模块定义传给modules
调用reqire方法  传入模块id
调用模块方法  导出module.exports

4.功能函数说明
引入其他模块分析执行流程
在主js文件里 会有导入附属js 的语句
let name = webpack_require(/*! ./login.js */ “./src/login.js”)

	模块定义里有多个键值对了
	
1.installedModules  缓存  存放将来被加载的模块对象
2. __webpack_require__(moduleId)    核心作用是返回模块的导出
3.__webpack_require__.m = modules    将模块定义保存一份,通过 m 属性挂载到自定义的方法身上
4. __webpack_require__.c = installedModules    保存缓存
5.__webpack_require__.o   判断被传入的对象 obj 身上是否具有指定的属性 **** ,如果有则返回 true 
6. __webpack_require__.d   通过 o 方法判断是否没有 name 属性 并且添加一个name属性 和访问器
7.__webpack_require__.r
	1.判断是否是esmodules 
		是的话添加一个 值为 module 的Symbol
	2.添加一个 __esModule 属性
8.__webpack_require__.t     未调用
	调用后会拿到模块中的内容value
	对于 value 来说我们可能会直接返回,也可能会处理之后再返回
9.__webpack_require__.n  分会module的default属性	未调用

10.__webpack_require__.p   配置里面的public属性

5.commonJS模块打包
主js采用 require函数

只是对__webpack_require__  只是 对require进行了替换  就是上面学习的例子
js里面
	esModules模块
		调用了__webpack_require__.r 方法  判断是否esmodules  并且添加 Module的的Symbol
										添加 __esModule 属性 value 为true 
		 调用了__webpack_require__.d   处理age 属性 作为d方法里面name值 添加age属性和访问器

6.esModule 打包
主js 采用 import from
调用了__webpack_require__.r 方法 判断是否esmodules 并且添加 Module的的Symbol
添加 _esModule 属性 value 为true
webpack自定义方法 替换
var login_js__WEBPACK_IMPORTED_MODULE_0
= webpack_require(/*! ./login.js */ “./src/login.js”);

	module.exports = 'zce'
	使用 __webpack_require__.n  方法添加default 并赋予值   default 和别的值 区别时 多调一次 __webpack_require__.n 方法
		__webpack_require__.d  添加属性值 
			__webpack_require__.o 判断是否esmodule 

打包的最后一个环节

两种规范都可以使用  作打包处理   CJS处理的结果更少  但还是根据情况使用

7.功能函数手写

手写 mybuilt.js hzj.js
从点头到尾写一遍 不包含__webpack_require__.t 方法

下
	esmodule模式下的验证
	__webpack_require__下的r和d方法

8.懒加载流程梳理
懒加载 打包的时候并没有加载
import函数 then()
多出两个方法
webpack_require.e(“login”)
返回一个 Promise.all
webpack_require.t.bind (null, “./src/login.js”, 7)
null 修改 this 不关注
7 二进制 001
返回 一个 ns

1.import()可以实现指定模块的懒加载操作
2.核心原理时 jsonp
3.t 方法针对内容进行不同处理 处理方法取决于传入的数值( mode,8 6 7 3 2 1)

9.T方法分析及实现
webpack_require.t(value,mode) 导出模块定义或者处理再导出
01 接收二个参数,一个是 value 一般用于表示被加载的模块id ,第二个值 mode 是一个二进制的数值
02 t 方法内部做的第一件事情就是调用自定义的 require 方法加载value 对应的模块导出,重新赋值给 value
03 当获取到了这个 value 值之后余下的 8 4 ns 2 都是对当前的内容进行加工处理,然后返回使用
04 当mode & 8 成立是直接将 value 返回 ( commonJS )
05 当 mode & 4 成立时直接将 value 返回(esModule)
06 如果上述条件都不成立,还是要继续处理 value ,定义一个 ns {}
6-1 如果拿到的 value 是一个可以直接使用的内容,例如是一个字符串,将它挂载到 ns 的 default 属性上
6-2 如果不是可直接使用的,就先遍历对象的值,给ns上加各种属性,方便直接调用

按位方式
	a	1000     8
	b	0010	 2
	a&b 0000	 0

10.懒加载源码分析

.bind() 补充之前函数的第一个入参修改
第一个参数会作为原函数运行时的 this 指向

	webpackJsonpCallback
		1.模块依赖关系的合并
		2.让当前的promise变成 成功的状态
		
		
  jsonpArray.push = webpackJsonpCallback;  也重写了window["webpackJsonp"] 
  
下  实现懒加载的流程
  	__webpack_require__.e  方法  返回promise	 jsonp
		installedChunkData 为0表示已加载   
						 undefined 未加载
						 promise  正在加载
		undefined的时候
			installedChunkData 添加 resolve reject 和promise 
			调用 jsonpScriptSrc  方法
			
		document.head.appendChild(script);  添加一个head标签

		
	返回Promise.all(promises); 但是promise没有进入成功态
	这个时候加载指定的文件内容
	进入webpackJsonpCallback
		获取需要加载的id         chunkId = chunkIds[i];
		获取模块的resolve方法
		标记加载过      installedChunks[chunkId] = 0;
		判断模块数据有没有当前要加载的模块数据     并复制
		执行resolve方法
	进入__webpack_require__.t 方法	
		加载模块  获得导出模块内容
			对模块内容进行处理 创建一个ns对象 并将模块内容加进去
			返回ns对象(导出内容)

	jsonpScriptSrc	路径和命名

11.手写懒加载源码
webpackJsonpCallback 回调方法重写
Object.prototype.hasOwnProperty.call(installedChunks, chunkId) 判断是否加载了
将 模块的resolve 放到resolves
将关系放到modules上
执行放到resolves里的resolve方法

__webpack_require__.e  实现json加载内容 利用promise实现异步加载操作
	有primise就执行 没有就创建
	var promise = new Promise(function (resolve, reject) {
      installedChunkData = installedChunks[chunkId] = [resolve, reject];
    });
	promises.push(installedChunkData[2] = promise);

12.webpack 与tapable 底层核心工具库
配置初始化
内容编译
编译后输出
事件驱动型事件流工作机制

bundles的compilation

tapable 独立的库
	实例化hook注册事件监听
	通过hook触发事件监听
	执行懒编译生成的可执行代码
	
hook本质是tapable实例对象   同步和异步
	Hook      普通钩子  监听器之间互相独立不干扰
	BailHook  熔断钩子  某个监听返回非undefined时 后续不执行
	WaterfallHook 瀑布钩子  上一个监听的返回值可传递至下一个
	LoopHook 循环钩子 如果当前未返回false  则一直执行

同步钩子
	SynckHook
	SyncBailHook
	SyncWaterfallHook
	SyncLoopHook
	
异步串行钩子
	AsyncSeriesHook
	AsyncSeriesBailHook
	AsyncSeriesWaterfallHook
异步并行钩子
	AsyncParalleHook
	AsyncParalleBailHook

13.同步钩子的使用及调试
依赖 tapable
SyncHook
导入SyncHook require(‘tapable’)
定义Hook实例 new SyncHook([‘name’, ‘age’])
tap 设置监听
call 触发监听

SyncBailHook   同理  熔断:某一个地方断掉了 就不执行了
	return 非 undefined  后续监听不执行了
	
SyncWaterfallHook
	return 的东西可以给下一个钩子使用
	
SyncLoopHook
	在钩子循环结束前 不会执行之后的钩子  只会执行前面的钩子
	
	taps里面放了多个数组
		如果当前的钩子返回undefined  则会从头循环钩子  从头开始循环

14.异步钩子的使用及调试
安装tapable库
库里有很多类
1.AsyncParallelHook 并行钩子
异步钩子 添加事件监听有三种方式
tap tapAsync tapPromise
tap
hook.tap(‘fn1’, function (name) {
console.log(‘fn1—>’, name)
})
allAsync 后面的参数要回调函数
hook.callAsync(‘zoe’, function () {
console.log(‘最后执行了回调操作’)
})

			tapAsync
				hook.tapAsync('fn1', function (name, callback) {
				  setTimeout(() => {
					console.log('fn1--->', name)
					callback()
				  }, 1000)
				})
				
				hook.callAsync('lg', function () {
				  console.log('最后一个回调执行了')
				  console.timeEnd('time')
				}) 
				
			tapPromise  回调方式通过promise实现
				hook.tapPromise('fn1', function (name) {
				  return new Promise(function (resolve, reject) {
					setTimeout(() => {
					  console.log('fn1--->', name)
					  resolve()
					}, 1000)
				  })
				})
				
				调用 直接使用promise
				hook.promise('foo').then(() => {
				  console.log('end执行了')
				  console.timeEnd('time')
				})
		2.AsyncParallelBailHook  并行异步熔断钩子	
			hook.tapAsync('fn2', function (name, callback) {
			  setTimeout(() => {
				console.log('fn2--->', name)
				callback('err')
			  }, 2000)
			})
			
			执行到错误的钩子然后回调 callAsync函数
			回调callAsync函数之后执行 报错误的钩子后的钩子
			
		3.AsyncSeriesHook   异步串行钩子
			同一  只是是串行的

15.SyncHook源码分析
Hook.js是父类
HookCodeFactory 钩子代码工厂 继承

先实例化Hook 再SyncHook
构造Hook 简写
this.args = args
this.taps = [] // 将来用于存放组装好的 {} 默认空数组
this._x = undefined // 将来在代码工厂函数中会给 _x = [f1, f2, f3…]
赋值一系列方法
重写 SyncHook 里的方法修饰 主要还是调用Hook的方法
tap方法 将字符串转化为对象
调用_insert 方法 将东西添加到taps里面
_insert
this.taps[this.taps.length] = options 将内容转换成对象存到taps里面

下
	call方法
		里面调用_createCall方法
	_createCall
		返回对象this.compile
			taps: this.taps,
			args: this.args
			
	到了  SyncHook的compile方法 	调了factory的setup方法  返回create方法
		factory   
			setup  把taps的函数放在了.x属性里面
			
			create 心就是创建一段可执行的代码体然后返回  然后拼接头和身体

16.SyncHook手写实现
上 监听
* 01 实例化 hook , 定义 _x = [f1, f2, …] taps = [{}, {}]
* 02 实例调用 tap taps = [{}, {}]
* 03 调用 call 方法, HookCodeFactory setup create
* 04 Hook SyncHook HookCodeFactory

下 调用	
	call()
		核心为factory的 create方法 
		组装函数     fn = new Function("name, age", 
		"var _x = this._x, var _fn0 = _x[0]; _fn0(name, age);")
		head content

17.AsyncParalleHook源码分析
核心为factory的 create方法
组装函数 核心为content方法 判断是否执行完最后的事情 再回调
相比同步函数 异步函数需要有一个最后的回调函数

18.定位webpack打包入口
执行入口
complier
run方法

看源码不需要从上到下看懂全部的含义
从执行顺序看

01 cmd 文件核心的作用就组装了 node *****/webpack/bin/webpack.js

02 webpack.js 中核心的操作就是 require 了 node_modules/webpack-cli/bin/cli.js

03 cli.js 
  01 当前文件一般有二个操作,处理参数,将参数交给不同的逻辑(分发业务)
  02 options 
  03 complier 
  04 complier.run( 至于run 里面做了什么,后续再看,当前只关注代码入口点 )

19.编译主流程分析
options就是webpack.config.js导出的内容
complier继承了tapable的类
主流程编译流程:
合并默认配置和自定义配置 —》 实例化compiler 设置Node文件读写的能力 通过循环 挂载plugins 处理webpack内部默认的插件(入口文件)
触发配置
Compiler.beforeRun
Compiler.run
Compiler.beforeCompile
Compiler.compile 编译
Compiler.make

20.手写webpack.js
01 实例化 compiler 对象
02 初始化 NodeEnvironmentPlugin(让compiler具体文件读写能力)
03 挂载所有 plugins 插件至 compiler 对象身上
04 挂载所有 webpack 内置的插件(入口)
05 返回 compiler 对象即可

1.run.js   
	使用		2.webpack.js    调用webpack.config.js的内容作为options
		生成compiler 
			引用NodeEnvironmentPlugin类   3.NodeEnvironmentPlugin.js
		
	调用 compiler的 run方法    4.compiler .js

21.EntryOptionPlugin webpack.config.js里的入口配置插件管理
WebpackOptionsApply实例对象
process(options, compiler) 方法
EntryOptionPlugin 类 apply 方法
里面的 entryOption 是一个钩子实例,
entryOption 在 EntryOptionPlugin 内部的 apply 方法中调用了 tap (注册了事件监听)
上述的事件监听在 new 完了 EntryOptionPlugin 之后就调用了
itemToPlugin, 它是一个函数,接收三个参数( context item 'main)
SingleEntryPlugin
在调用 itemToPlugin, 的时候又返回了一个 实例对象
有一个构造函数,负责接收上文中的 context entry name
compilation 钩子监听
make 钩子监听

22.手写EntryOptionPlugin 流程
先新建 文件 WebpackOptionsApply.js 声明类 WebpackOptionsApply WebpackOptionsApply.js
调用 process(options, compiler)方法 WebpackOptionsApply.js
里面调用了 EntryOptionPlugin 的 apply方法 EntryOptionPlugin.js
方法里定义了一个钩子 entryOption EntryOptionPlugin.js
钩子调用了 itemToPlugin 函数 EntryOptionPlugin.js
函数返回了 SingleEntryPlugin 的实例的apply方法 SingleEntryPlugin.js
apply方法里监听了make 异步钩子 SingleEntryPlugin.js
compilation 触发

23.run方法实现
run方法流程
finalCallback方法 调用callback
onCompiled 对callbak处理
this.hooks.beforeRun 触发钩子

24.compile方法实现
newCompilationParams 方法 获取参数
normalModuleFactory 模块工厂 还有上下文等等
beforeRun 触发钩子的监听
compile 触发钩子的监听
创建一个 compilation newCompilation类 传入最开始的参数
make 触发钩子的监听

		-------------------------------------------------------------------------------
		  02 compile 方法执行
			1 准备参数(其中 normalModuleFactory 是我们后续用于创建模块的)
			2 触发beforeCompile
			3 将第一步的参数传给一个函数,开始创建一个 compilation (newCompilation)
			4 在调用 newCompilation 的内部
			  - 调用了 createCompilation 
			  - 触发了 this.compilation 钩子 和 compilation 钩子的监听
		------------------------------------------------------------------------------------

25.make前流程回顾
一、步骤 run.js
01 实例化 compiler 对象( 它会贯穿整个webpack工作的过程 )
02 由 compiler 调用 run 方法

	二、compiler 实例化操作 webpack.js
	  01 compiler 继承 tapable,因此它具备钩子的操作能力(监听事件,触发事件,webpack是一个事件流)

	  02 在实例化了 compiler 对象之后就往它的身上挂载很多属性,其中 NodeEnvironmentPlugin 这个操作就让它具备了
	  文件读写的能力(我们的模拟时采用的是 node 自带的 fs )

	  03 具备了 fs 操作能力之后又将 plugins 中的插件都挂载到了 compiler 对象身上  

	  04 将内部默认的插件与 compiler 建立关系,其中 EntryOptionPlugin 处理了入口模块的 id 

	  05 在实例化 compiler 的时候只是监听了 make 钩子(SingleEntryPlugin)
		5-1 在 SingleEntryPlugin 模块的 apply 方法中有二个钩子监听  compilation和make
		5-2 其中 compilation 钩子就是让 compilation 具备了利用 normalModuleFactory 工厂创建一个普通模块的能力
		5-3 因为它就是利用一个自己创建的模块来加载需要被打包的模块 
		5-4 其中 make 钩子 在 compiler.run 的时候会被触发,走到这里就意味着某个模块执行打包之前的所有准备工作就完成了
		5-5 addEntry 方法调用()

	三、run 方法执行( 当前想看的是什么时候触发了 make 钩子 ) compiler.js

	  01 run 方法里就是一堆钩子按着顺序触发(beforeRun run compile)

	  02 compile 方法执行
		1 准备参数(其中 normalModuleFactory 是我们后续用于创建模块的)
		2 触发beforeCompile
		3 将第一步的参数传给一个函数,开始创建一个 compilation (newCompilation)
		4 在调用 newCompilation 的内部
		  - 调用了 createCompilation 
		  - 触发了 this.compilation 钩子 和 compilation 钩子的监听
	  03 当创建了 compilation 对象之后就触发了 make 钩子

	  04 当我们触发 make 钩子监听的时候,将 compilation 对象传递了过去 

	四、总结 

	  1 实例化 compiler  
	  2 调用 compile 方法
	  3 newCompilation 
	  4 实例化了一个 compilation 对象(它和 compiler 是有关系)
	  5 触发 make 监听 
	  6 addEntry 方法(这个时候就带着 context name entry  一堆的东西) 就奔着编译去了.....

26.addEntry流程分析

01 make 钩子在被触发的时候,接收到了 compilation 对象实现,它的身上挂载了很多内容

	02 从 compilation 当中解构了三个值 
	  entry : 当前需要被打包的模块的相对路径(./src/index.js)
	  name: main 
	  context: 当前项目的根路径 

	03 dep 是对当前的入口模块中的依赖关系进行处理 

	04 调用了 addEntry 方法 

	05 在 compilation实例的身上有一个 addEntry 方法,然后内部调用了 _addModuleChain 方法,去处理依赖
	_addModuleChain方法内
	06 在 compilation 当中我们可以通过 NormalModuleFactory 工厂来创建一个普通的模块对象  

	07 在 webpack 内部默认启了一个 100 并发量的打包操作,当前我们看到的是 normalModule.create()

	08 在 beforeResolve 里面会触发一个 factory 钩子监听【 这个部分的操作其实是处理 loader, 当前我们重点去看 】

	09 上述操作完成之后获取到了一个函数被存在 factory 里,然后对它进行了调用  

	10 在这个函数调用里又触发了一个叫 resolver 的钩子( 处理 loader的,拿到了 resolver方法就意味着所有的Loader 处理完毕 )

	11 调用 resolver() 方法之后,就会进入到  afterResolve 这个钩子里,然后就会触发  new NormalModule 

下
	12 在完成上述操作之后就将module 进行了保存和一些其它属性的添加  
	_addModuleChain方法内
	13 调用 buildModule 方法开始编译---》 调用 build ---》doBuild 	
		build	
			* 01 从文件中读取到将来需要被加载的 module 内容,这个
			* 02 如果当前不是 js 模块则需要 Loader 进行处理,最终返回 js 模块 
			* 03 上述的操作完成之后就可以将 js 代码转为 ast 语法树
			* 04 当前 js 模块内部可能又引用了很多其它的模块,因此我们需要递归完成 
			* 05 前面的完成之后,我们只需要重复执行即可

27.addEnter 初始化
手写 Compilation.js
构造方法
addEntry方法
_addModuleChain方法

28._addModuleChain实现
创建 NormalModule 命名 entryModule
调用 buildModule 方法 创建模块

29.buildModule 实现
调用 NormalModule 的build方法
在 NormalModule.js调用 doBuild方法
doBuild方法
_source 通过 getSource 方法 读取文件内容并存取
_ast 通过 parse 实现语法树?

30.build及parse实现
babylon模块
return babylon.parse(source, {
sourceType: ‘module’,
plugins: [‘dynamicImport’] // 当前插件可以支持 import() 动态导入的语法
})
status 动态传参

31.处理模块依赖
打包 :创建一个新的模块 ,让这个模块加载需要被打包的模块的内容 ,对内容进行一些处理

	01 需要将 Index.js 里的 require 方法替换成 __webpack_require__ 
	02 还有将 ./title 替换成 ./src/title.js 

	03 实现递归的操作 ,所以要将依赖的模块信息保存好,方例交给下一次 create 

	操作 _ast 语法树 内容处理
		在 dobuild 方法 转换语法树的时候操作
			generator _ast 改完之后不能运行 ,需要再转换成代码    @babel/generator
			traverse  进到这个语法里面取修改  		@babel/traverse
				// 这里的 _ast 就是当前 module 的语法树,我们可以对它进行修改,最后再将 ast 转回成 code 代码     
		
			替换内容
			    node.arguments = [types.stringLiteral(depModuleId)]  可以直接换数组内容   @babel/types
				
	afterBuild   处理依赖
		通过 processDependencies 方法实现模块依赖加载
			// 01 当前的函数核心功能就是实现一个被依赖模块的递归加载
			// 02 加载模块的思想都是创建一个模块,然后想办法将被加载模块的内容拿进来?
			// 03 当前我们不知道 module 需要依赖几个模块, 此时我们需要想办法让所有的被依赖的模块都加载完成之后再执行 callback?【 neo-async 】

32.抽离 createModule 方法
解耦创建 NormalModule 达到复用的目的
改写_addModuleChain 方法 单词创建 NormalModule 大部分逻辑转入 createModule 方法

33.编译依赖模块
完成 processDependencies
可以直接递归导入依赖 调用 createModule 加载依赖
parser, //没有这个语法树无法加载代码

34.chunk流程分析及实现
所谓封装 chunk 指的就是依据某个入口,然后找到它的所有依赖,将它们的源代码放在一起,之后再做合并
不同的模块 生成不同的代码块 chunk
make 钩子触发的时候吹chunk
compilation.seal 封装chunk
保存 chunk 信息和modules里对应的module的模块

			// 01 当前所有的入口模块都被存放在了 compilation 对象的 entries 数组里
			// 02 所谓封装 chunk 指的就是依据某个入口,然后找到它的所有依赖,将它们的源代码放在一起,之后再做合并

35.生成chunk代码 具体内容实现
存取文件的名字
chunk.files.push(fileName)
模板文件和源代码
// 01 获取模板文件的路径
let tempPath = path.posix.join(__dirname, ‘temp/main.ejs’)
// 02 读取模块文件中的内容
let tempCode = this.inputFileSystem.readFileSync(tempPath, ‘utf8’)
// 03 获取渲染函数
let tempRender = ejs.compile(tempCode)
// 04 按ejs的语法渲染数据
let source = tempRender({
entryModuleId: chunk.entryModule.moduleId,
modules: chunk.modules
})
资源组装到source
把source赋值到this.assets对应的文件中

36.生成打包文件
emitAssets Compiler.js
// 当前需要做的核心: 01 创建dist 02 在目录创建完成之后执行文件的写操作
在 emit 钩子调用 emitFlies 方法写入文件
emitFlies
this.outputFileSystem.writeFileSync(targetPath, source, ‘utf8’)//输出文件 路径 内容 编码级

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值