The purpose of this article is to analyze the dependent pre-build features in Vite, and to attach the code addresses and official website addresses of the related features.
Depend on prebuildThe main process
-
Prebuild dependencies before starting the server.
-
Read user package-lock.json, yarn.lock, pnpm-lock.yaml, generate depHash.
-
Read the prebuilt file information from the last file cache. If there is any, compare the hash obtained with depHash in the previous step. Return if the hash is the same; otherwise, rebuild. Rebuild if there is no cache or the force parameter is set.
-
Using esbuild, the project files are scanned to obtain project dependencies.
-
Using ESBuild, transform the modular approach that the project relies on into an ES Module approach.
-
The converted modules are stored in cacheDir (node_module/.vite by default).
-
When the front end requests a resource, it determines whether the requested resource is a dependency (i.e. bare import), if so, it replaces the cache file path and loads the corresponding file.
-
After the service is started, the dependency build is redone each time a new dependency is introduced. Perform procedure 2,3,4,5.
Source code analysis
Build the start code entry
If it is not middleware mode, it is executed first before the server is startedPlugin. BuildStart hook functionTo execute the build function. Otherwise, the container is a collection of plugins that execute hook functions in sequence.
if(! middlewareMode && httpServer) {const listen = httpServer.listen.bind(httpServer)
// Override listen to ensure that it is executed before server starts.
httpServer.listen = (async(port, ... args) => {try {
// Container plugin assembly
await container.buildStart({})// plugin.buildStart
await runOptimize() / / pre-built
} catch (e) {}
returnlisten(port, ... args) }) }else {
await container.buildStart({})
await runOptimize()
}
Copy the code
RunOptimize function
_isRunningOptimizer adds build status. The optimizeDeps function returns the pre-build information returned in steps 3, 4, and 5 of the build process. _registerMissingImport returns a pre-build function that can be pre-built at any time. Rebuild when new dependencies are introduced in the running service and the _isRunningOptimizer state effectively avoids data requests at build time.
const runOptimize = async() = > {if (config.cacheDir) {
server._isRunningOptimizer = true
try {
server._optimizeDepsMetadata = await optimizeDeps(config)
} finally {
server._isRunningOptimizer = false
}
server._registerMissingImport = createMissingImporterRegisterFn(server)
}
}
Copy the code
OptimizeDeps function
OptimizeDeps is the main function that does the following
-
Obtain the previous pre-build information, compare this information, and decide whether to rebuild.
-
Scan the source code, or obtain dependencies based on parameters.
-
Leverage es-Module-Lexer flat nested source code dependencies.
-
Resolve the user dependency optimization configuration by calling the esbuild file and storing it in cacheDir.
-
Store the build information and return it
function optimizeDeps( config, force=config.server.force,asCommand=false,newDeps?) { / /... const dataPath = path.join(cacheDir, '_metadata.json') // Generate the build hash const mainHash = getDepHash(root, config) const data: DepOptimizationMetadata = { hash: mainHash, browserHash: mainHash, optimized: {}}// The user's force parameter determines whether to rebuild each time if(! force) {let prevData try { // Load the last build information prevData = JSON.parse(fs.readFileSync(dataPath, 'utf-8'))}catch (e) {} // Compare the hash before and after, if the same, return directly if (prevData && prevData.hash === data.hash) { return prevData } } / /... The newDeps parameter is the dependency information passed in when a dependency is added after the service is started. let deps if(! newDeps) {// Use esbuild to scan source code for dependencies; ({ deps, missing } =await scanImports(config)) } else { deps = newDeps missing = {} } / /... constinclude = config.optimizeDeps? .includeif (include) { / /... Add user-specified include } // Flatten dependency await init for (const id in deps) { flatIdDeps[id]=/ /... / /... } / /... / /... Add user-specified esbuildOptions const{ plugins = [], ... esbuildOptions } = config.optimizeDeps? .esbuildOptions ?? {}// Call esbuild.build to package the file const result = await build({ // ... entryPoints: Object.keys(flatIdDeps),/ / the entry format: 'esm'.// Package into ESM mode external: config.optimizeDeps? .exclude,// Exclude files outdir: cacheDir,// Output address // ... }) // Rewrite _metadata.json for (const id in deps) { const entry = deps[id] data.optimized[id] = { file: normalizePath(path.resolve(cacheDir, flattenId(id) + '.js')), src: entry, } } writeFile(dataPath, JSON.stringify(data, null.2)) return data } Copy the code
The front-end gets dependencies instead of cached dependencies
Procedure: When accessing a file with imported dependencies, match the dependency name and return the dependency path under cacheDir.
- Introduce the preAliasPlugin plug-in in plugins when parsing config
- Matching the dependent name returns the name of the path to add the cache
Plugin.resolveid replaces the dependency if a value is returned, otherwise the name is passed to the next plug-in. When the dependency name is matched, change the dependency name by returning tryOptimizedResolve. /node_modules/.vite/react.js? v=7db446d6
const bareImportRE = /^[\w@](? ! . * : \ \ / / // Match dependencies
function preAliasPlugin() {
let server: ViteDevServer
return {
name: 'vite:pre-alias'.configureServer(_server) {
server = _server
},
resolveId(id, _, __, ssr) {
// If it is a dependency, add the cache path
if(! ssr && bareImportRE.test(id)) {return tryOptimizedResolve(id, server)
}
}
}
}
function tryOptimizedResolve(
id: string,
server: ViteDevServer
) :string | undefined {
const cacheDir = server.config.cacheDir
const depData = server._optimizeDepsMetadata // Rely on build information generated in the prebuild
if (cacheDir && depData) {
const isOptimized = depData.optimized[id] // Find if there are dependencies
if (isOptimized) {
return ( // Returns the new dependency path
isOptimized.file +
`? v=${depData.browserHash}${
isOptimized.needsInterop ? `&es-interop` : ` `
}`)}}}Copy the code
Detect new dependency rebuild while running the service
The code here is too messy, and the general flow is as follows: after requesting a new dependency resource, the preAliasPlugin avoids the match and the dependency name is passed toResolvePlugin plug-inIn the judgmentWhether the file importing a dependency is also a dependencyIf yes, rebuild it.