Every build tool has a Graph to maintain references and Module information between modules. This article examines what Vite’s Module Graph looks like
Create an instance of Module Graph
Initialization occurs during createServer
const container = await createPluginContainer(config, watcher)
const moduleGraph = new ModuleGraph(container)
const server: ViteDevServer = {
moduleGraph,
// ...
}
Copy the code
Create a ModuleGraph instance and pass in the created plug-in container container
export class ModuleGraph {
urlToModuleMap = new Map<string, ModuleNode>()
idToModuleMap = new Map<string, ModuleNode>()
fileToModulesMap = new Map<string.Set<ModuleNode>>()
safeModulesPath = new Set<string> ()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]> {}
}
Copy the code
The initialization process is to mount the plugin container to this and initialize the urlToModuleMap, idToModuleMap, fileToModulesMap, and safeModulesPath attributes
Let’s take a look at each method separately
resolveUrl
async resolveUrl(url: string) :Promise"[string.string] > {/ / remove? The import and t = XXX
url = removeImportQuery(removeTimestampQuery(url))
/ / here
const resolvedId = (await this.container.resolveId(url))? .id || urlconst ext = extname(cleanUrl(resolvedId))
const { pathname, search, hash } = parseUrl(url)
if(ext && ! pathname! .endsWith(ext)) { url = pathname + ext + (search ||' ') + (hash || ' ')}return [url, resolvedId]
}
Copy the code
This method calls the resolveId hook function of all plug-ins, retrieves the absolute path to the file based on the url of the requested module, and returns the absolute path to the file.
ensureEntryFromUrl
async ensureEntryFromUrl(rawUrl: string) :Promise<ModuleNode> {
// Get the file URL and absolute path
const [url, resolvedId] = await this.resolveUrl(rawUrl)
// Get the ModuleNode instance corresponding to the URL
let mod = this.urlToModuleMap.get(url)
if(! mod) {// Initializes the ModuleNode instance
mod = new ModuleNode(url)
// Add the mod to urlToModuleMap
this.urlToModuleMap.set(url, mod)
/ / set id
mod.id = resolvedId
/ / set idToModuleMap
this.idToModuleMap.set(resolvedId, mod)
const file = (mod.file = cleanUrl(resolvedId))
let fileMappedModules = this.fileToModulesMap.get(file)
if(! fileMappedModules) { fileMappedModules =new Set(a)/ / set fileToModulesMap
this.fileToModulesMap.set(file, fileMappedModules)
}
fileMappedModules.add(mod)
}
return mod
}
Copy the code
Create ModuleNode objects based on the module path and collect them into ModuleGraph’s properties; Finally, this object is returned
- Added to the
urlToModuleMap
In, the key is fileurl
; Value is corresponding to the moduleMoudleNode
object - Added to the
idToModuleMap
, the key is the absolute file path; Value is corresponding to the moduleMoudleNode
object - Added to the
fileToModulesMap
In, the key is removedquery
andhash
Is the absolute path of the file. The value isSet
Instance, which adds the corresponding of the moduleMoudleNode
object
Inside the object is some information about the module and the relationships between modules; Look at the object properties
- url: INDICATES the URL starting with /, for example, / SRC /assets/logo. PNG - id: indicates the absolute path of the module, which may contain query andhash- file: no query andhashModule absolute path -type: If it is a CSS file and the path has the direct parameter'css', or for'js'-lasthMRTIMESTAMP: HMR update time - a Set of modules that import the module into which the element is a ModuleNode object - importedModules: The current module imports a Set of modules Set, elements of which are ModuleNode objects - transformResult: {code: source code, map: sourcemap related, etag: Unique value, related to comparison caching} the following value is related to import.meta.hot.accept() -isselfaccepting: if the module updates itselftrue-acceptedhmrdeps: Set of modules to receive hot updates for the current module. The elements are ModuleNode objects. And the import meta. Hot. The accept ()Copy the code
Get ModuleNode
// Get the ModuleGraph object corresponding to the module based on the URL
async getModuleByUrl(rawUrl: string) :Promise<ModuleNode | undefined> {
const [url] = await this.resolveUrl(rawUrl)
return this.urlToModuleMap.get(url)
}
// Get the ModuleGraph object corresponding to the module based on the absolute path that may have arguments
getModuleById(id: string): ModuleNode | undefined {
return this.idToModuleMap.get(removeTimestampQuery(id))
}
// Get the Set of ModuleGraph objects corresponding to the module based on the absolute path with no parameters
getModulesByFile(file: string) :Set<ModuleNode> | undefined {
return this.fileToModulesMap.get(file)
}
Copy the code
Empty ModuleNode
// Clears the transformResult of the ModuleGraph object
invalidateModule(mod: ModuleNode, seen: Set<ModuleNode> = new Set()) :void {
mod.transformResult = null
}
// Clears the transformResult of all ModuleGraph objects
invalidateAll(): void {
const seen = new Set<ModuleNode>()
this.idToModuleMap.forEach((mod) = > {
this.invalidateModule(mod, seen)
})
}
Copy the code
onFileChange
onFileChange(file: string) :void {
const mods = this.getModulesByFile(file)
if (mods) {
const seen = new Set<ModuleNode>()
mods.forEach((mod) = > {
this.invalidateModule(mod, seen)
})
}
}
Copy the code
Gets and clears the transformResult property value of the corresponding ModuleNode object based on the passed file
updateModuleInfo
The most important method is to build and update reference relationships between modules
async updateModuleInfo(
mod: ModuleNode, // ModuleNode object corresponding to the current module
importedModules: Set<string | ModuleNode>, // The module imported by the current module
acceptedModules: Set<string | ModuleNode>, // The current module receives a collection of hot update modules
isSelfAccepting: boolean // True for self-updating) :Promise<Set<ModuleNode> | undefined> {
// If true, it receives hot updates from the module itself
mod.isSelfAccepting = isSelfAccepting
// Import the collection before getting the module
const prevImports = mod.importedModules
// Create a new Set
const nextImports = (mod.importedModules = new Set())
let noLongerImported: Set<ModuleNode> | undefined
// update import graph
/ / traverse importedModules
for (const imported of importedModules) {
// Create/find ModuleNode instances for dependent modules if imported is a string
const dep =
typeof imported === 'string'
? await this.ensureEntryFromUrl(imported)
: imported
// Adds the current module's ModuleNode instance to the dependent module's corresponding ModuleNode instance whose importers are the credit
dep.importers.add(mod)
// Add the dependent module's corresponding ModuleNode instance to nextImports
nextImports.add(dep)
}
prevImports.forEach((dep) = > {
// If nextImports does not have this dep
// The module corresponding to the deP is not imported in the current module
// So the mod is removed from the DEP
if(! nextImports.has(dep)) { dep.importers.delete(mod)if(! dep.importers.size) {// If no module is imported, it will be collected in noLongerImported; (noLongerImported || (noLongerImported =new Set())).add(
dep
)
}
}
})
// Add the module set in import.meta.hot.accept() to the mod.acceptedModules, excluding itself
const deps = (mod.acceptedHmrDeps = new Set())
for (const accepted of acceptedModules) {
const dep =
typeof accepted === 'string'
? await this.ensureEntryFromUrl(accepted)
: accepted
deps.add(dep)
}
// The current module was imported, and now no set of files imported by the module is returned
return noLongerImported
}
Copy the code
conclusion
Vite creates a ModuleNode object for each module, which contains references between modules and module information. Module information includes the absolute path, converted code, received hot updated modules, and so on.