Skip to content

ModuleGraph 源代码导览

项目结构

modulegraph-course/
├── 04-core-feature/          # 核心功能实现
│   ├── src/
│   │   └── graph.js         # 模块图实现
│   ├── test/
│   │   └── graph.test.js     # 测试文件
│   ├── package.json
│   └── README.md
├── 05-lesson-plan.md         # 课程计划
├── 01-intro.md              # 背景研究
├── 02-arch.md              # 架构分析
└── 03-code-walkthrough.md   # 源代码导览

核心文件解析

1. graph.js - 模块图

文件路径: src/graph.js

核心功能:

  • 管理模块节点
  • 维护依赖关系
  • 支持模块查询和更新

关键代码:

javascript
// 模块节点
export class ModuleNode {
  constructor(options = {}) {
    this.id = options.id
    this.url = options.url
    this.file = options.file
    
    // 依赖
    this.imports = new Set()
    this.importers = new Set()
    
    // 状态
    this.transformResult = null
    this.lastHMRTimestamp = 0
    
    // 元数据
    this.meta = options.meta || {}
  }
  
  // 添加导入
  addImport(module) {
    this.imports.add(module)
    module.importers.add(this)
  }
  
  // 移除导入
  removeImport(module) {
    this.imports.delete(module)
    module.importers.delete(this)
  }
  
  // 获取所有导入
  getImports() {
    return Array.from(this.imports)
  }
  
  // 获取所有导入者
  getImporters() {
    return Array.from(this.importers)
  }
  
  // 更新转换结果
  updateTransformResult(result) {
    this.transformResult = result
    this.lastHMRTimestamp = Date.now()
  }
}

// 模块图
export class ModuleGraph {
  constructor() {
    // 模块映射
    this.idToModuleMap = new Map()
    this.urlToModuleMap = new Map()
    this.fileToModulesMap = new Map()
    
    // 入口模块
    this.entryModules = new Set()
  }
  
  // 创建模块
  createModule(options) {
    const module = new ModuleNode(options)
    this.addModule(module)
    return module
  }
  
  // 添加模块
  addModule(module) {
    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) {
    return this.idToModuleMap.get(id)
  }
  
  getModuleByUrl(url) {
    return this.urlToModuleMap.get(url)
  }
  
  getModulesByFile(file) {
    return this.fileToModulesMap.get(file)
  }
  
  // 更新模块
  updateModule(module) {
    // 更新模块信息
  }
  
  // 删除模块
  deleteModule(module) {
    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)
    }
  }
  
  // 添加入口模块
  addEntryModule(module) {
    this.entryModules.add(module)
  }
  
  // 获取入口模块
  getEntryModules() {
    return Array.from(this.entryModules)
  }
}

设计要点:

  • 使用 Map 实现快速查找
  • 使用 Set 存储依赖关系
  • 维护双向依赖关系
  • 支持文件到模块的多对一映射

关键设计决策

1. 使用 Map 和 Set

原因:

  • Map 提供 O(1) 的查找性能
  • Set 自动去重
  • 内存效率高

实现:

javascript
this.idToModuleMap = new Map()
this.imports = new Set()

2. 双向依赖关系

原因:

  • 便于查找导入者
  • 支持 HMR 的依赖更新
  • 便于循环检测

实现:

javascript
addImport(module) {
  this.imports.add(module)
  module.importers.add(this)  // 双向关系
}

3. 文件到模块的多对一映射

原因:

  • 同一文件可能对应多个模块(如 HMR)
  • 便于按文件查找模块
  • 支持虚拟模块

实现:

javascript
this.fileToModulesMap = new Map()
this.fileToModulesMap.set(file, new Set())
this.fileToModulesMap.get(file).add(module)

测试策略

单元测试

javascript
import { describe, it } from 'node:test'
import assert from 'node:assert'
import { ModuleGraph, ModuleNode } from '../src/graph.js'

describe('ModuleGraph 测试', () => {
  it('应该创建模块图', () => {
    const graph = new ModuleGraph()
    assert.ok(graph)
  })
  
  it('应该创建模块', () => {
    const graph = new ModuleGraph()
    const module = graph.createModule({
      id: 'test',
      url: '/test.js'
    })
    assert.ok(module)
    assert.strictEqual(module.id, 'test')
  })
  
  it('应该添加依赖', () => {
    const moduleA = new ModuleNode({ id: 'a' })
    const moduleB = new ModuleNode({ id: 'b' })
    
    moduleA.addImport(moduleB)
    
    assert.ok(moduleA.imports.has(moduleB))
    assert.ok(moduleB.importers.has(moduleA))
  })
})

性能优化

1. 模块缓存

javascript
const moduleCache = new Map()

function getModule(id) {
  if (moduleCache.has(id)) {
    return moduleCache.get(id)
  }
  
  const module = createModule(id)
  moduleCache.set(id, module)
  return module
}

2. 增量更新

javascript
function updateModules(changedFiles) {
  for (const file of changedFiles) {
    const modules = graph.getModulesByFile(file)
    for (const module of modules) {
      updateModule(module)
    }
  }
}

总结

ModuleGraph 的源代码体现了以下设计原则:

  1. 高效查询:使用 Map 实现 O(1) 查找
  2. 双向关系:维护导入和导入者关系
  3. 灵活映射:支持多种查询方式
  4. 易于测试:提供清晰的接口

理解源代码有助于更好地使用和优化 ModuleGraph。

参考资源

架构师AI杜公众号二维码

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