Vite 源码(四)Vite 的 Module Graph


theme: cyanosis

highlight: monokai

每种构建工具都会有一个 Graph 用于维护模块之间的引用和模块信息,这篇文章就是分析 Vite 的 Module Graph 是什么样子的

创建 Module Graph 实例

初始化过程发生在createServer过程中 ```typescript const container = await createPluginContainer(config, watcher) const moduleGraph = new ModuleGraph(container)

const server: ViteDevServer = { moduleGraph, // ... } 创建一个 `ModuleGraph`实例,并传入创建的插件容器`container` typescript export class ModuleGraph { urlToModuleMap = new Map () idToModuleMap = new Map () fileToModulesMap = new Map >() safeModulesPath = new Set () container: PluginContainer

constructor(container: PluginContainer) {
    this.container = container
}
async getModuleByUrl(rawUrl: string): Promise<ModuleNode | undefined> {}

getModuleById(id: string): ModuleNode | undefined {}

getModulesByFile(file: string): Set<ModuleNode> | undefined {}

onFileChange(file: string): void {}

invalidateModule(): void {}

invalidateAll(): void {}
async updateModuleInfo(): Promise<Set<ModuleNode> | undefined> {}
async ensureEntryFromUrl(rawUrl: string): Promise<ModuleNode> {}
createFileOnlyEntry(file: string): ModuleNode {}
async resolveUrl(url: string): Promise<[string, string]> {}

} `` 初始化过程就是将传入的插件容器container挂载到this上,并初始化 4 个属性urlToModuleMapidToModuleMapfileToModulesMapsafeModulesPath`

接下来分别看下每个方法的作用

resolveUrl

typescript async resolveUrl(url: string): Promise<[string, string]> { // 去掉 ?import 和 t=xxx url = removeImportQuery(removeTimestampQuery(url)) // 这里 const resolvedId = (await this.container.resolveId(url))?.id || url const ext = extname(cleanUrl(resolvedId)) const { pathname, search, hash } = parseUrl(url) if (ext && !pathname!.endsWith(ext)) { url = pathname + ext + (search || '') + (hash || '') } return [url, resolvedId] } 这个方法的作用是调用所有插件的resolveId钩子函数,根据当前被请求模块的url,获取该文件的绝对路径,最后返回[url, 文件绝对路径]

ensureEntryFromUrl

typescript async ensureEntryFromUrl(rawUrl: string): Promise<ModuleNode> { // 获取文件 url 和 绝对路径 const [url, resolvedId] = await this.resolveUrl(rawUrl) // 根据 url 获取该url对应的 ModuleNode 实例 let mod = this.urlToModuleMap.get(url) if (!mod) { // 初始化 ModuleNode 实例 mod = new ModuleNode(url) // 将 mod 添加到 urlToModuleMap 中 this.urlToModuleMap.set(url, mod) // 设置 id mod.id = resolvedId // 设置 idToModuleMap this.idToModuleMap.set(resolvedId, mod) const file = (mod.file = cleanUrl(resolvedId)) let fileMappedModules = this.fileToModulesMap.get(file) if (!fileMappedModules) { fileMappedModules = new Set() // 设置 fileToModulesMap this.fileToModulesMap.set(file, fileMappedModules) } fileMappedModules.add(mod) } return mod } 根据模块路径创建ModuleNode对象,将对象收集到ModuleGraph的属性中;最后返回这个对象 - 添加到urlToModuleMap中,键是文件url;值是模块对应的MoudleNode对象 - 添加到idToModuleMap中,键是文件绝对路径;值是模块对应的MoudleNode对象 - 添加到fileToModulesMap中,键是去掉queryhash的文件绝对路径;值是Set实例,里面添加的是模块对应的MoudleNode对象

对象内就是关于这个模块的一些信息和模块之间的关系;看下对象属性 ```bash - url: 以 / 开头的 url,比如 /src/assets/logo.png - id: 模块绝对路径,可能带有 query 和 hash - file: 不带 query 和 hash 的模块绝对路径 - type: 如果是 css 文件并且路径上带有 direct 参数则为'css',否则为 'js' - lastHMRTimestamp: HMR 更新时间 - importers: 导入该模块的模块合集 Set,元素是 ModuleNode 对象 - importedModules: 当前模块的导入模块合集 Set,元素是 ModuleNode 对象 - transformResult: { code: 源码, map: sourcemap 相关, etag: 唯一值,和对比缓存有关 }

下面的和 import.meta.hot.accept() 有关 - isSelfAccepting: 如果是模块自更新,则为 true - acceptedHmrDeps: 当前模块接收热更新的模块合集 Set,元素是 ModuleNode 对象;和import.meta.hot.accept() 有关 ```

获取 ModuleNode

typescript // 根据 url 获取模块对应的 ModuleGraph 对象 async getModuleByUrl(rawUrl: string): Promise<ModuleNode | undefined> { const [url] = await this.resolveUrl(rawUrl) return this.urlToModuleMap.get(url) } // 根据 可能有参数的绝对路径 获取模块对应的 ModuleGraph 对象 getModuleById(id: string): ModuleNode | undefined { return this.idToModuleMap.get(removeTimestampQuery(id)) } // 根据 没有参数的绝对路径 获取模块对应的 ModuleGraph 对象集合 Set getModulesByFile(file: string): Set<ModuleNode> | undefined { return this.fileToModulesMap.get(file) }

清空 ModuleNode

typescript // 清空 ModuleGraph 对象的 transformResult invalidateModule(mod: ModuleNode, seen: Set<ModuleNode> = new Set()): void { mod.transformResult = null } // 清空所有 ModuleGraph 对象的 transformResult invalidateAll(): void { const seen = new Set<ModuleNode>() this.idToModuleMap.forEach((mod) => { this.invalidateModule(mod, seen) }) }

onFileChange

typescript onFileChange(file: string): void { const mods = this.getModulesByFile(file) if (mods) { const seen = new Set<ModuleNode>() mods.forEach((mod) => { this.invalidateModule(mod, seen) }) } } 根据传入的file获取并清空对应ModuleNode对象的transformResult属性值

updateModuleInfo

最重要的一个方法,用于构建和更新模块之间的引用关系 typescript async updateModuleInfo( mod: ModuleNode, // 当前模块对应的 ModuleNode 对象 importedModules: Set<string | ModuleNode>, // 当前模块导入的模块 acceptedModules: Set<string | ModuleNode>, // 当前模块接收热更新模块的合集 isSelfAccepting: boolean // 如果是自身更新则为 true ): Promise<Set<ModuleNode> | undefined> { // 如果为 true,表示接收模块自身的热更新 mod.isSelfAccepting = isSelfAccepting // 获取该模块之前导入集合 const prevImports = mod.importedModules // 创建新的 Set const nextImports = (mod.importedModules = new Set()) let noLongerImported: Set<ModuleNode> | undefined // update import graph // 遍历 importedModules for (const imported of importedModules) { // 如果 imported 是字符串则为依赖模块创建/查找 ModuleNode 实例 const dep = typeof imported === 'string' ? await this.ensureEntryFromUrl(imported) : imported // 将当前模块的 ModuleNode 实例添加到依赖模块对应的 ModuleNode 实例的 importers 上 dep.importers.add(mod) // 将这个依赖模块对应的 ModuleNode 实例添加到 nextImports 中 nextImports.add(dep) } prevImports.forEach((dep) => { // 如果 nextImports 中没有这个 dep // 说明这个 dep 对应的模块没在当前模块中导入 // 所以将 mod 从 dep 的 dep.importers 中删除 if (!nextImports.has(dep)) { dep.importers.delete(mod) if (!dep.importers.size) { // 如果没有模块导入 dep 对应的模块,则收集到 noLongerImported 中 ;(noLongerImported || (noLongerImported = new Set())).add( dep ) } } }) // 将 import.meta.hot.accept() 中设置的模块添加到 mod.acceptedModules 里面,不包含自身 const deps = (mod.acceptedHmrDeps = new Set()) for (const accepted of acceptedModules) { const dep = typeof accepted === 'string' ? await this.ensureEntryFromUrl(accepted) : accepted deps.add(dep) } // 将当前模块导入过,现在没有任何模块导入的文件集合返回 return noLongerImported }

总结

Vite 为每个模块创建一个ModuleNode对象,对象内包含模块间的引用关系以及模块信息。模块信息包含绝对路径、转换后的代码、接收的热更新模块等。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值