本文以此为例子
entry: ['main.js']
// main.js
import { a } from './a.js'
import('./b.js').then(res => {
console.log(res, a)
})
// a.js
export const a = 'a'
// b.js
export const b = 'b'
生成 moduleGraph
数据结构
moduleGraph : {
_dependencyMap: Map<Dependency, ModuleGraphConnection>
_moduleMap: Map<NormalModule, ModuleGraphModule>
}
Module: {
dependencies: Dependency[],
block: block[]
}
Dependency: {
_parentModule: Module,
}
ModuleGraphModule: {
incomingConnections: Set<ModuleGraphConnection>
outgoingConnections:Set<ModuleGraphConnection>
}
ModuleGraphConnection: {
module: Module,
originModule: Module,
dependency: HarmonyImportSideEffectDependency
}
moduleGraph
- 通过一个依赖,可以知道这个依赖是谁提供,被谁使用。
- 通过一个模块,可以知道它提供的依赖,和使用的依赖
Module
模块,包含文件源代码,dependencies 为其同步依赖,block 为其异步依赖。
Dependency
依赖,是某个模块提供的导出,_parentModule 为使用它的模块
ModuleGraphConnection
- module:提供该依赖的模块
- originModule:使用该依赖的模块
ModuleGraphModule
- 通过 incomingConnections 获取模块提供的内部依赖
- 通过 outgoingConnections 获取模块使用的外部依赖
输出
moduleGraph:{
_dependencyMap:{
EntryDependency(main.js): ModuleGraphConnection(main.js),
HarmonyImportSideEffectDependency(a.js): ModuleGraphConnection(a.js),
HarmonyImportSpecifierDependency(a.js|a): ModuleGraphConnection(a.js|a),
ImportDependency(b.js): ModuleGraphConnection(b.js)
},
_moduleMap: {
NormalModule(main.js): ModuleGraphModule(main.js),
NormalModule(a.js): ModuleGraphModule(a.js),
NormalModule(b.js): ModuleGraphModule(b.js),
null: ModuleGraphModule(null)
}
}
生成 chunkGraph
初始化 ChunkGraph
数据结构
ChunkGraph:{
_chunks:Map<Chunk, ChunkGraphModule>
_modules: Map<Chunk, ChunkGraphChunk>
}
Chunk: {
_group: [entrypoint(main)]
}
ChunkGroup: {
chunks: Chunk[]
}
ChunkGraphModule: {
entryModules: Map<Module, ChunkGroup>,
modules: Module[]
}
ChunkGraphChunk: {
entryInChunks: Set<Chunk>,
chunks: Chunk[]
}
chunkGraph
- 存储 chunk 和 module 的关系。
- 通过一个 chunk,可以知道该 chunk 包含的模块,是否对应一个或多个入口 module
- 通过一个 module,可以知道该 module 被哪些 chunk 使用,是否对应一个或多个入口 chunk
Chunk
chunk 表示一个待生成的文件,通过 _group 字段与 ChunkGroup 关联
ChunkGroup
单个入口或异步模块对应一个 ChunkGroup,其可能产生一个或多个(splitChunk) chunk,存于 chunks 字段。
ChunkGraphModule
- modules 是被该 chunk 直接和间接使用的所有模块。
- 根据入口生成的 entry chunk 对应一个或多个 entry module,取决于入口配置中的入口对应的文件数量。
- 异步模块生成的 chunk 没有 entry module
ChunkGraphChunk
- 一个 module 可能被多个 chunk 使用
- 一个 entry module 可能被多个 entry chunk 使用,取决于入口配置被多个入口设置为入口文件
输出
ChunkGraph: {
_chunks: {},
_modules: {}
}
生成 entryPoint、entry chunk 和 chunkGraphInit
流程
遍历入口配置 entries
- 生成 entry chunk 和 entryPoint(即是chunkGroup),将两者建立关联
- 通过入口的依赖和moduleGraph,获取 entry module,将 entry chunk 和 entry module 建立关联
- 收集 entryPoint 和对应的 entry module,生成 chunkGraphInit
输出
ChunkGraph: {
_chunks: {
chunk(main): {
entryModules: {
module(main): chunkGroup(main)
},
modules: []
}
},
_modules: {
module(main): {
entryInChunks: [chunk(main)],
chunks: []
}
}
}
chunkGroup(main): {
chunks: [chunk(main)]
}
chunk(main): {
_group: [chunkGroup(main)]
}
chunkGraphInit: {
chunkGroup(main): [module(main)]
}
生成 blockModulesMap
数据结构:
blockModulesMap: Map<module, Map<module, ModuleGraphConnection[]>>
moduleMap: Map<Dependency, ModuleGraphConnection>
blockModulesMap
储存模块和该模块依赖的模块和其依赖的关系
moduleMap
储存模块和其有效依赖的关系
流程
由 moduleGraph 获取 module(main.js) 的 outgoingConnections,
outgoingConnections(main.js): [ModuleGraphConnection(a.js), ModuleGraphConnection(a.js|a), ModuleGraphConnection(b.js)]
由 ModuleGraphConnection 可获取依赖,ast分析模块代码时,会将依赖打上是否活跃的标记。根据这个标记我们最后输出 moduleMap
moduleMap: {
dep(a.js|a): ModuleGraphConnection(a.js|a),
dep(b): ModuleGraphConnection(b.js),
}
根据 module(main) 的 dependencies 字段获取其同步依赖: [dep(a.js), dep(a.js|a)], ,ModuleGraphConnection(a.js|a) 在 moduleMap 存在,获取该依赖的模块为 module(a.js)。
将这些关系填充进 blockModulesMap:
blockModulesMap: {
module(main.js): {
module(a.js): [ModuleGraphConnection(a.js|a]
}
}
获取 module(main.js) 的异步模块 [block(b.js)], block(b.js) 的同步依赖为 [dep(b.js)], dep(b.js) 在 moduleMap 存在,获取该依赖的模块为 module(b.js)。
将这些关系填充进 blockModulesMap
blockModulesMap: {
module(main.js): {
module(a): [ModuleGraphConnection(a.js|a)]
},
block(b): {
module(b): [ModuleGraphConnection(b.js)]
}
}
- module(a.js) 和 module(b.js)无外部依赖跳过
输出:
blockModulesMap: {
module(main.js): {
module(a.js): [ModuleGraphConnection(a.js|a)]
},
block(b.js): {
module(b.js): [ModuleGraphConnection(b.js)]
}
}
连接 chunk 和 module
流程
- 遍历 chunkGraphInit 生成 jobs
jobs: [{
action: ADD_AND_ENTER_MODULE,
block: module(main.js),
module: module(main.js),
chunk: chunk(main),
chunkGroup: chunkGroup(main),
chunkGroupInfo: chunkGroupInfo(main)
}]
- 遍历 jobs,
- 将 module(main.js) 与 chunk(main.js) 塞入 ChunkGraph
ChunkGraph: {
_chunks: {
chunk(main): {
entryModules: {
module(main.js): chunkGroup(main)
},
modules: [module(main.js)]
}
},
_modules: {
module(main.js): {
entryInChunks: [chunk(main)],
chunks: [chunk(main)]
}
}
}
- 获取 module(main.js) 的同步依赖,根据 blockModulesMap 将 module(a) 加入job
jobs: [{
action: ADD_AND_ENTER_MODULE,
block: module(main.js),
module: module(main.js),
chunk: chunk(main),
chunkGroup: chunkGroup(main),
chunkGroupInfo: chunkGroupInfo(main)
}, {
action: ADD_AND_ENTER_MODULE,
block: module(a.js),
module: module(a.js),
chunk: chunk(main),
chunkGroup: chunkGroup(main),
chunkGroupInfo: chunkGroupInfo(main)
}]
- 获取 module(main.js) 的异步依赖,根据 blockModulesMap 生成一个暂存任务数组 queueDelayed。 这一步为 block(b) 生成了一个新的 chunk 和 chunkGroup
queueDelayed: {
action: PROCESS_BLOCK,
block: block(b.js),
module: module(main.js),
chunk: chunk(b),
chunkGroup: chunkGroup(b),
chunkGroupInfo: chunkGroupInfo(b)
}
- 将 module(a) 和 chunk(main.js) 塞入 ChunkGraph
ChunkGraph: {
_chunks: {
chunk(main): {
entryModules: {
module(main): chunkGroup(main)
},
modules: [module(main.js), module(a.js)]
}
},
_modules: {
module(main): {
entryInChunks: [chunk(main)],
chunks: [chunk(main)]
}
}
}
- 用 queueDelayed 覆盖 jobs,继续遍历 jobs
jobs: [{
action: PROCESS_BLOCK,
block: block(b.js),
module: module(main.js),
chunk: chunk(b),
chunkGroup: chunkGroup(b),
chunkGroupInfo: chunkGroupInfo(b)
}]
- 获取 block(b.js) 的同步依赖,根据 blockModulesMap,将其加入 jobs
jobs: [{
action: PROCESS_BLOCK,
block: block(b.js),
module: module(main.js),
chunk: chunk(b),
chunkGroup: chunkGroup(b),
chunkGroupInfo: chunkGroupInfo(b)
}, {
action: ADD_AND_ENTER_MODULE,
block: module(b.js),
module: module(b.js),
chunk: chunk(b),
chunkGroup: chunkGroup(b),
chunkGroupInfo: chunkGroupInfo(b)
}]
- 将 module(b.js) 和 chunk(b) 塞入 ChunkGraph
ChunkGraph: {
_chunks: {
chunk(main): {
entryModules: {
module(main): chunkGroup(main)
},
modules: [module(main.js), module(a.js)]
},
chunk(b): {
entryModules: {},
modules: [module(b.js)]
}
},
_modules: {
module(main.js): {
entryInChunks: [chunk(main)],
chunks: [chunk(main)]
},
module(b.js): {
entryInChunks: [],
chunks: [chunk(b)]
}
}
}
- module(b.js) 无外部依赖,至此遍历结束 输出
ChunkGraph: {
_chunks: {
chunk(main): {
entryModules: {
module(main): chunkGroup(main)
},
modules: [module(main.js), module(a.js)]
},
chunk(b): {
entryModules: {},
modules: [module(b.js)]
}
},
_modules: {
module(main.js): {
entryInChunks: [chunk(main)],
chunks: [chunk(main)]
},
module(b.js): {
entryInChunks: [],
chunks: [chunk(b)]
}
}
}
链接 chunkGroup
我们为异步模块 block(b) 单独生成了一个 chunk 和 chunkGroup。通过 chunkGroup 的 _parents 和 _children 字段为它们建立关系
chunkGroup(main): {
chunks: [chunk(main)]
_children: [chunkGroup(b)]
}
chunkGroup(b): {
chunks: [chunk(b)]
_parents: [chunkGroup(main)]
}
处理空的 chunkGroup
遍历上一步中新生成的所有chunkGroup,若其 _parents 为空,则将其和其产生的 chunk 从 ChunkGraph 中删除
假如是如下配置
// main.js
import { a } from './a.js'
import { b } from './b.js'
import('./b.js').then(res => {
console.log(res, a)
})
// a.js
export const a = 'a'
// b.js
export const b = 'b'
会生成如下 ChunkGraph
ChunkGraph: {
_chunks: {
chunk(main): {
entryModules: {
module(main): chunkGroup(main)
},
modules: [module(main.js), module(a.js)]
},
chunk(b): {
entryModules: {},
modules: []
}
},
_modules: {
module(main.js): {
entryInChunks: [chunk(main)],
chunks: [chunk(main)]
},
module(b.js): {
entryInChunks: [],
chunks: [chunk(main)]
}
}
}
chunks(b) 是空的,代码会被直接打包进 chunk(main),chunkGroup(b) 也不回和 chunkGroup(main) 建立关联,chunks 和 chunkGroup(b) 都将被删除
去重 chunks
针对目前所有的chunks,去掉所使用的依赖全部相同的chunk,针对新生成的除入口 chunk 之外的 chunk
对于如下配置
// one.js
import('./a.js').then(res => {
console.log(res)
})
// two.js
import('./a.js').then(res => {
console.log(res, a)
})
// a.js
export const a = 'a'
经过去重,最终会生成 [chunk(one). chunk(two), chunk(a)]
合并 Module
遍历所有 modules
为 module(main) 生成
ConcatenatedModule: {
_modules: [module(main), module(a)],
block: [block(b)]
}
目前所有 modules [ConcatenatedModule(main,a), module(b)]
生成 module - code 代码映射
{
ConcatenatedModule(main,a): codeObject,
module(b): codeObject
}
生成mainfest
[{
filename: 'main.js'
render: () => { ...generate code }
},
filename: 'module_id.js'
render: () => { ...generate code }
}]
注意,
1. 如果是入口 chunk,会在这里的 render 函数中加入运行时代码
2. 根据 ChunkGraph 可以知道该 chunk 直接或间接依赖的所有 module。然后将他们的代码拼接
生成最终代码
{
main.js: code,
module_id.js: code
}
这里会使用 terser 进行 tree shake 去除无用代码
输出代码
输出文件