brief introduction
Vite has four commands: dev | build | optimize | preview. This paper combs the whole process of vite and briefly splits the main steps of dev | build, so as to facilitate everyone's understanding of vite source code as a whole. You are welcome to correct any mistakes.
start-up
The two default commands of vite project npm run serve npm run build start the vite command and open / node_modules/.bin directory, find the vite file, and find that the main process is to execute the start function and load the node/cli file
// The source code here is as follows ... function start() { require('../dist/node/cli') } ... start() ...
Then go to the CLI file vite / SRC / node / cli ts
vite scaffold / SRC / node / cli ts
It can be seen that vite scaffold includes four commands, dev | build | optimize | preview. This paper mainly combs dev | build
dev development environment build structure preview vite preview optimize optimization
vite dev
cli.ts
cli.ts refers to the dev command/ server and trigger listen() to listen
/node/server/index.ts
createServer is to create and return a server. Specifically, it does the following:
- Consolidate profile vite config. JS and the configuration in the command line into config
const config = await resolveConfig(inlineConfig, "serve", "development");
- Start an http(s)server and upgrade it to websocket (of course, the relevant configuration parameters of the HTTP server should be handled first in the previous step)
const httpsOptions = await resolveHttpsConfig(config); const middlewares = connect() as Connect.Server; const httpServer = middlewareMode ? null : await resolveHttpServer(serverConfig, middlewares, httpsOptions); const ws = createWebSocketServer(httpServer, config, httpsOptions);
- Use chokidar to listen for file changes (this is the basis for hot updates)
const watcher = chokidar.watch(path.resolve(root), { ignored: ["**/node_modules/**", "**/.git/**", ...ignored], ignoreInitial: true, ignorePermissionErrors: true, disableGlobbing: true, ...watchOptions, }) as FSWatcher;
- All plugin s are processed uniformly and saved in the container
const container = await createPluginContainer(config, watcher);
- Generate a moduleGraph according to the container (it has not been carefully read here. The explanation in vite is that moduleGraph is used to record the relationship between import, url to file mapping and hot update
- and hmr state`)
const moduleGraph = new ModuleGraph(container); // The following is the ts definition of moduleGraph /** * Module graph that tracks the import relationships, url to file mapping * and hmr state. */
- Initialize the vite dev server to be returned later, and bind some properties and methods
const server: ViteDevServer = { ... }
- When the watcher changes, carry out corresponding heat renewal treatment
watcher.on('change', fn) watcher.on('add', fn) watcher.on('unlink', fn)
- Execute the vite hook configureServer. Here, postHooks only collects the plugin s of configureServer
const postHooks: ((() => void) | void)[] = []; for (const plugin of plugins) { if (plugin.configureServer) { // WK executes the plugin configured with configureServer postHooks.push(await plugin.configureServer(server)); } }
- Use of internal Middleware
... middlewares.use(corsMiddleware(typeof cors === "boolean" ? {} : cors)); middlewares.use(proxyMiddleware(httpServer, config)); ...
- Execute plugins in posHooks
postHooks.forEach((fn) => fn && fn());
- Convert index html
middlewares.use(indexHtmlMiddleware(server));
- Before listen()
- Execute vite hook buildStart
- Execute runOptimize() to optimize before startup
if (!middlewareMode && httpServer) { // overwrite listen to run optimizer before server start const listen = httpServer.listen.bind(httpServer); httpServer.listen = (async (port: number, ...args: any[]) => { try { await container.buildStart({}); await runOptimize(); } catch (e) { httpServer.emit("error", e); return; } return listen(port, ...args); }) as any; httpServer.once("listening", () => { // update actual port since this may be different from initial value serverConfig.port = (httpServer.address() as AddressInfo).port; }); } else { await container.buildStart({}); await runOptimize(); }
- Return to server
vite build
cli.ts
The build command is introduced/ Build file and execute build()
rollup packaging
vite is packaged with rollup. Before reading the relevant methods, it is necessary to have a basic understanding of rollup. The following is the code description of its packaging process on the rollup official website. rollup javascript API
It can be seen that rollup generally needs to be packaged
- Packaged configuration parameter inputOptions
- Configuration parameter outputOptions of package generation file
- Call rollup Rollup () returns a bundle object
- Call bundle Generate() or bundle Write() completes the packaging
So vite's build should follow this process
build.ts
build. The build method in TS mainly refers to the dobuild method. Dobuild does the following things (ssr related will not be considered first):
- Collation configuration parameter = > config
const config = await resolveConfig(inlineConfig, "build", "production");
- rollup packaging input parameter = > RollupOptions. Before that, I handled the input parameters and external parameters that are important to the RollupOptions object
const RollupOptions: RollupOptions = { input, preserveEntrySignatures: ssr ? "allow-extension" : libOptions ? "strict" : false, ...options.rollupOptions, plugins, external, onwarn(warning, warn) { onRollupWarning(warning, warn, config); }, };
- rollup package output parameter outputs (generally, in project development, outputs is an obj, but packages of different formats may need to be generated when building the library, so outputs may also be an array)
const outputs = resolveBuildOutputs( options.rollupOptions?.output, libOptions, config.logger );
- rollup also provides a watch function, and vite also implements it here Direct rollup Watch
if (config.build.watch) { config.logger.info(chalk.cyanBright(`\nwatching for file changes...`)); const output: OutputOptions[] = []; if (Array.isArray(outputs)) { for (const resolvedOutput of outputs) { output.push(buildOuputOptions(resolvedOutput)); } } else { output.push(buildOuputOptions(outputs)); } const watcherOptions = config.build.watch; const watcher = rollup.watch({ ...rollupOptions, output, watch: { ...watcherOptions, chokidar: { ignored: [ "**/node_modules/**", "**/.git/**", ...(watcherOptions?.chokidar?.ignored || []), ], ignoreInitial: true, ignorePermissionErrors: true, ...watcherOptions.chokidar, }, }, }); watcher.on("event", (event) => { if (event.code === "BUNDLE_START") { config.logger.info(chalk.cyanBright(`\nbuild started...`)); if (options.write) { prepareOutDir(outDir, options.emptyOutDir, config); } } else if (event.code === "BUNDLE_END") { event.result.close(); config.logger.info(chalk.cyanBright(`built in ${event.duration}ms.`)); } else if (event.code === "ERROR") { outputBuildError(event.error); } }); // stop watching watcher.close(); return watcher; }
- Generate bundle object
const bundle = await rollup.rollup(rollupOptions);
- Call bundle Write method, write to the file, and you're done. Before that, the prepareOutDir method is also called (confirm whether the packaged directory exists and clean it)
if (options.write) { prepareOutDir(outDir, options.emptyOutDir, config); } if (Array.isArray(outputs)) { const res = []; for (const output of outputs) { res.push(await generate(output)); } return res; } else { return await generate(outputs); }
Follow up plan
Read vite carefully and see the implementation of some details, such as the processing of import {createapp} from 'Vue'
other
The notes related to this reading are in vite source code sorting , you can use the vscode plug-in todo tree. The prefix 'WK' is all the comments I added in the process of reading