Skip to content

ModuleGraph 架构分析

整体架构

ModuleGraph 使用图数据结构表示模块之间的依赖关系。

┌─────────────────────────────────────────────┐
│              ModuleGraph                  │
├─────────────────────────────────────────────┤
│  1. 模块节点(ModuleNode)              │
│     - id: 模块标识符                      │
│     - url: 模块 URL                       │
│     - file: 模块文件路径                     │
│     - imports: 导入的模块                   │
│     - importers: 导入此模块的模块              │
│     - transformResult: 转换结果              │
├─────────────────────────────────────────────┤
│  2. 模块映射(ModuleMap)                │
│     - idToModuleMap: ID 到模块的映射          │
│     - urlToModuleMap: URL 到模块的映射         │
│     - fileToModulesMap: 文件到模块的映射        │
├─────────────────────────────────────────────┤
│  3. 入口模块(EntryModules)             │
│     - Set<ModuleNode>: 入口模块集合           │
└─────────────────────────────────────────────┘

核心组件

1. ModuleNode

模块节点表示单个模块。

属性:

typescript
class ModuleNode {
  id: string              // 模块 ID
  url: string             // 模块 URL
  file: string            // 模块文件路径
  imports: Set<ModuleNode>  // 导入的模块
  importers: Set<ModuleNode> // 导入此模块的模块
  transformResult: TransformResult | null  // 转换结果
  lastHMRTimestamp: number  // 最后 HMR 时间戳
  meta: Record<string, any>  // 元数据
}

方法:

typescript
// 添加导入
addImport(module: ModuleNode): void {
  this.imports.add(module)
  module.importers.add(this)
}

// 移除导入
removeImport(module: ModuleNode): void {
  this.imports.delete(module)
  module.importers.delete(this)
}

// 获取所有导入
getImports(): ModuleNode[] {
  return Array.from(this.imports)
}

// 获取所有导入者
getImporters(): ModuleNode[] {
  return Array.from(this.importers)
}

2. ModuleGraph

模块图管理所有模块和依赖关系。

属性:

typescript
class ModuleGraph {
  idToModuleMap: Map<string, ModuleNode>  // ID 到模块的映射
  urlToModuleMap: Map<string, ModuleNode> // URL 到模块的映射
  fileToModulesMap: Map<string, Set<ModuleNode>>  // 文件到模块的映射
  entryModules: Set<ModuleNode>  // 入口模块集合
}

方法:

typescript
// 创建模块
createModule(options: ModuleOptions): ModuleNode {
  const module = new ModuleNode(options)
  this.addModule(module)
  return module
}

// 添加模块
addModule(module: ModuleNode): void {
  this.idToModuleMap.set(module.id, module)
  this.urlToModuleMap.set(module.url, module)
  
  if (module.file) {
    if (!this.fileToModulesMap.has(module.file)) {
      this.fileToModulesMap.set(module.file, new Set())
    }
    this.fileToModulesMap.get(module.file)!.add(module)
  }
}

// 获取模块
getModuleById(id: string): ModuleNode | undefined {
  return this.idToModuleMap.get(id)
}

getModuleByUrl(url: string): ModuleNode | undefined {
  return this.urlToModuleMap.get(url)
}

getModulesByFile(file: string): Set<ModuleNode> | undefined {
  return this.fileToModulesMap.get(file)
}

// 更新模块
updateModule(module: ModuleNode): void {
  // 更新模块信息
}

// 删除模块
deleteModule(module: ModuleNode): void {
  this.idToModuleMap.delete(module.id)
  this.urlToModuleMap.delete(module.url)
  
  if (module.file) {
    this.fileToModulesMap.get(module.file)?.delete(module)
  }
  
  // 清理依赖关系
  for (const imp of module.imports) {
    imp.importers.delete(module)
  }
  for (const imp of module.importers) {
    imp.imports.delete(module)
  }
}

依赖关系

导入关系

Module A
  ↓ imports
Module B
  ↓ imports
Module C

实现:

typescript
// A 导入 B
moduleA.addImport(moduleB)

// B 导入 C
moduleB.addImport(moduleC)

双向关系

Module A ← importers ← Module B ← importers ← Module C
  ↓ imports              ↓ imports
Module B                Module C

实现:

typescript
// 查找导入者
const importers = moduleA.getImporters()  // []

// 查找导入
const imports = moduleA.getImports()  // [ModuleB]

HMR 支持

模块更新

当文件变化时,更新模块:

typescript
async function invalidateModule(file: string) {
  const modules = moduleGraph.getModulesByFile(file)
  
  if (modules) {
    for (const module of modules) {
      // 清除转换结果
      module.transformResult = null
      
      // 通知 HMR 客户端
      hmrClient.send({
        type: 'update',
        path: module.url
      })
    }
  }
}

依赖更新

更新受影响的模块:

typescript
async function updateDependents(module: ModuleNode) {
  const dependents = module.getImporters()
  
  for (const dependent of dependents) {
    // 重新转换
    dependent.transformResult = await transform(dependent)
    
    // 递归更新
    await updateDependents(dependent)
  }
}

循环检测

检测算法

使用深度优先搜索(DFS)检测循环:

typescript
function detectCycle(module: ModuleNode, visited = new Set()): boolean {
  if (visited.has(module)) {
    return true  // 检测到循环
  }
  
  visited.add(module)
  
  for (const imp of module.imports) {
    if (detectCycle(imp, new Set(visited))) {
      return true
    }
  }
  
  return false
}

循环处理

检测到循环时,记录警告:

typescript
if (detectCycle(module)) {
  console.warn(`检测到循环依赖: ${module.id}`)
}

性能优化

1. 使用 Map 和 Set

typescript
// 快速查找
const module = idToModuleMap.get(id)

// 快速判断
if (imports.has(module)) {
  // 已存在
}

2. 增量更新

typescript
// 只更新变化的模块
for (const file of changedFiles) {
  const modules = getModulesByFile(file)
  for (const module of modules) {
    updateModule(module)
  }
}

3. 缓存策略

typescript
// 缓存转换结果
if (!module.transformResult) {
  module.transformResult = await transform(module)
}

总结

ModuleGraph 的架构体现了以下特点:

  1. 图结构:使用图表示依赖关系
  2. 双向关系:维护导入和导入者关系
  3. 高效查询:使用 Map 和 Set 实现快速查找
  4. HMR 支持:支持热模块替换

理解 ModuleGraph 的架构有助于更好地使用和优化构建工具。

参考资源

架构师AI杜公众号二维码

扫描二维码关注"架构师AI杜"公众号,获取更多技术内容和最新动态