It was published in the department column

Introduction to Vite

1.1 What is a Vite?

Vite is a new generation of front-end building tools, which was born when Yu Yu Creek was developing Vue3.0. Similar to Webpack+ webpack-dev-server. It uses the ESM features of the browser to import organizational code, which is compiled and returned on the server side on demand, bypassing the concept of packaging completely and allowing the server to use it on demand. Rollup is used in production as a packaging tool, billed as the next generation front-end build tool.

Vite has the following features:

  • Quick cold start:No Bundle + esbuildpre-built
  • Instant module hot update: basedESMtheHMRAnd use the browser cache strategy to speed things up
  • True on-demand loading: using a browserESMSupport to achieve true on-demand loading

1.2 Comparison between Vite and traditional packaging methods

1.2.1 VS Webapck

Webpack is the most used front-end packaging build tool in recent years, and the community is the most complete. The new 5.x version has optimized the construction details, and the packaging speed has been significantly improved in some scenarios. When Webpack starts, it builds a dependency graph of the project modules, and if the code changes somewhere in the project, Webpack repackages the dependencies, which slows down as the project grows.

Vite, in contrast to Webpack, does not have a packaging process, but instead directly starts a development server, devServer. Vite hijacks HTTP requests from the browser and does the corresponding processing on the back end to simply decompose and consolidate the files used in the project and then return them to the browser (the whole process does not package or compile the files). So compilation is fast.

1.2.2 VS SnowPack

Snowpack is the first packaging tool that leverages the browser’s native ESM capabilities. The idea is to reduce or avoid entire bundle packaging. By default, applications are deployed unbundle in both dev and Production environments. But it was built to be a choice for the user, and the overall packaging experience was a bit fragmented.

Vite integrates directly with Rollup, giving users a complete, out-of-the-box solution and extending more advanced functionality thanks to these integrations.

The big difference is that Vite uses Rollup’s built-in configuration when bundles are packed, while Snowpack delegates it to Parcel/ ‘webpack’ via other plug-ins.

2. Pre-knowledge

2.1 the ESM

Before you get to know Vite, you need to know the ESM

ESM is an official standardized module system proposed by JavaScript. Different from CJS, AMD, CMD and so on before, ESM provides a more native and dynamic module loading solution. The most important thing is that it is supported by browsers natively, which means that we can execute import directly in the browser. Dynamically introduce the modules we need, rather than packing them all together.

Currently, ESM modularity supports more than 92% of browsers, and as an ECMA standard, more browsers will support the ECMA specification in the future

When we use module development, we are actually building a module dependency diagram, starting with the entry file when the module loads, and eventually generating the complete module instance diagram.

ESM execution can be divided into three steps:

  • Build: Determine where to download the module file, download, and parse all the files into module records
  • Instantiation: convert the module record into a module instance, allocate memory space for all modules, and point the module to the corresponding memory address according to the export and import statements.
  • Run: Run the code to fill the memory space

As you can see from the above instantiation, the ESM uses a real-time binding pattern, with both exported and imported modules pointing to the same memory address, known as a value reference. CJS uses value copying, that is, all exported values are copied values.

2.2 Esbuild

Vite uses Esbuild to convert.ts, JSX, and.js code files, so let’s look at es-build.

Esbuild is a JavaScript Bundler packaging and compression tool that provides resource packaging capabilities similar to Webpack, Rollup, etc. JavaScript and TypeScript code can be packaged and distributed to run on web pages. But it packs 10 to 100 times faster than other tools.

Currently it supports the following features:

  • loader
  • The compression
  • packaging
  • Tree shaking
  • Source mapgenerate

Esbuild provides four functions: Transform, Build, buildSync, and Service. Interested can refer to the official documentation.

2.3 a Rollup

In a production environment, Vite is packaged using Rollup

Rollup is an ESM-based JavaScript packaging tool. It always produces smaller, faster packages than other packaging tools like Webpack. Because Rollup is based on ESM modules, it is more efficient than the CommonJS module mechanism used by Webpack and Browserify. The beauty of Rollup is the same place, one load at a time. You can Tree Shaking source code (remove code that has been defined but is not being used), and Scope to reduce output file size to improve performance.

Rollup is divided into build stage and output generate stage. The main process is as follows:

  • Get the contents of the import file, wrapped intomoduleGenerate an abstract syntax tree
  • Dependency parsing of the entry file abstract syntax tree
  • Generate the final code
  • Write to target file

If your project (especially the class library) only has JavaScript and no other static resource files, using Webpack is a bit overkill. Because Webpack packages files that are slightly larger, slower, and less readable. Rollup is also a good choice.

If you want to learn more about Rollp, check out the Rollp website.

3 Core Principles

To elaborate:

  1. When you declare ascriptThe label type ismoduleWhen, if the
  <script type= module  src= /src/main.js ></script>
Copy the code
  1. When the browser parses a resource, it initiates one to the current domain nameGETrequestmain.jsfile
// main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
Copy the code
  1. The request to themain.jsFile, will detect internal containsimportThe imported package will againimportReference to aHTTPRequest the contents file of the module, as shown inApp.vue,vuefile

The core principle of Vite is that the browser already supports import of ES6. When encountering import, it will send an HTTP request to load the file. Vite starts a KOA server to intercept these requests, and performs corresponding processing on the back end to decompose and integrate the files used in the project. It is then returned to the browser in ESM format. Instead of packing and compiling files, Vite truly loads on demand, so it runs much faster than the original WebPack compiler!

3.1 EsM-based Dev Server

inViteBefore coming out, traditional packaging tools such asWebpackIs it to resolve the dependencies, package the build and then start the development server,Dev ServerMust wait for all modules to be built while we modifybundleA submodule of a module, the wholebundleThe files are repackaged and exported. The larger the application, the longer the startup time.

Vite takes advantage of the browser’s ESM support, and when a module is imported, the browser downloads it. Start the development server first, and then request the file of the corresponding module when the code is executed until the module is loaded, essentially realizing dynamic loading. The gray sections are routes that are not used for the time being, and all of these sections will not participate in the build process. With more and more applications in the project, adding route will not affect the speed of its construction.

3.2 HMR Hot Update based on ESM

At present, all the packaging tools to achieve hot update ideas are similar with minor differences: mainly through WebSocket to create browser and server communication monitoring file changes, when the file is modified, the server sends a message to inform the client to modify the corresponding code, the client corresponding to different files for different operations update.

3.2.1 VS Webpack

Webpack: recompile, request changed module code, client reload

Vite: A module that requests changes and then reloads

Vite uses Chokidar to listen for file system changes and only reload the modules that have changed, precisely invalidating the relevant modules with their adjacent HMR boundary connections, so that HMR updates don’t slow down as the application size increases and Webpack goes through a pack build. So Vite also performs better than Webpack in HMR scenarios.

3.2.2 Core process

The whole Vite hot update process can be broken down into four steps

  1. To create awebsocketThe service side andclientFile to start the service
  2. throughchokidarListening for file changes
  3. When the code changes, the server determines and pushes it to the client
  4. The client performs updates based on the pushed information

Overall flow chart:

3.2.2.1 Start hot Update: createWebSocketServer

Before Vite dev Server starts, Vite does some preparation work for HMR. For example, create webSocket service, use Chokidar to create a listener object watcher to listen for file changes, etc.

Source location: packages/vite/SRC/node/server/index. The ts

export async function createServer( inlineConfig: InlineConfig = {} ): Promise<ViteDevServer> { .... const ws = createWebSocketServer(httpServer, config, httpsOptions) const { ignored = [], ... watchOptions } = serverConfig.watch || {} const watcher = chokidar.watch(path.resolve(root), { ignored: [ '**/node_modules/**', '**/.git/**', ...(Array.isArray(ignored) ? ignored : [ignored]) ], ignoreInitial: true, ignorePermissionErrors: true, disableGlobbing: true, ... watchOptions }) as FSWatcher .... watcher.on('change', async (file) => { }) watcher.on('add', (file) => { }) watcher.on('unlink', (file) => { }) ... return server }Copy the code

The createWebSocketServer method creates the WebSocket service, performs some error handling, and returns the encapsulated on, OFF, Send, and close methods for the server to push messages and shut down the service.

Source location: packages/vite/SRC/node/server/ws. The ts

export function createWebSocketServer( server: Server | null, config: ResolvedConfig, httpsOptions? : HttpsServerOptions ): WebSocketServer { let wss: WebSocket let httpsServer: Server | undefined undefined = const HMR = isObject / / hot update configuration (config. Server. HMR) && config. The Server HMR const wsServer = (HMR && HMR. Server) | | server / / normal mode if (wsServer) {WSS = new WebSocket ({noServer: True}) wsServer.on('upgrade', (req, socket, head) => {// Listen for websocket messages sent via vite client, If (req. Headers ['sec-websocket-protocol'] === HMR_HEADER) {wss.handleUpgrade(req, socket as socket, head, (ws) => { wss.emit('connection', ws, Req)})})} else {// Middleware mode // vite dev Server in Middleware mode WSS = new WebSocket(websocketServerOptions)} wss.on('connection', (socket) => { ... }) / / error handling WSS) on (' error '(e: error & {code: string}) = > {... }) // Return {on: wss.on.bind(WSS), off: wss.off. Bind (WSS), send(payload: HMRPayload) {... }, close() { ... }}}Copy the code
3.2.2.2 Performing hot Update: moduleGraph+handleHMRUpdate module

The main two operations are moduleGraph.onFileChange to modify the cache of the file and handleHMRUpdate to perform hot updates

Source location: packages/vite/SRC/node/server/index. The ts

watcher.on('change', async (file) => { file = normalizePath(file) if (file.endsWith('/package.json')) { return invalidatePackageData(packageCache, file) } // invalidate module graph cache on file change moduleGraph.onFileChange(file) if (serverConfig.hmr ! == false) { try { await handleHMRUpdate(file, server) } catch (err) { ws.send({ type: 'error', err: prepareError(err) }) } } })Copy the code

3.2.2.2.1 moduleGraph

ModuleGraph is a class defined by Vite to document a module dependency graph for the entire application, in addition to moduleNode.

Source location: packages/vite/SRC/node/server/moduleGraph ts

The moduleGraph consists of a series of maps that map urls, IDS, files, etc., to the ModuleNode, which is the smallest module unit defined in Vite. From these two classes you can build the following module dependency diagram:

The moduleGraph. ‘ ‘onFileChange function is used to clear the transformResult property of the ModuleNode object corresponding to the modified file, invalidating the conversion cache of the previous module. This is Vite’s caching mechanism for hot updates. You can see the introduction on the official website.

Source location: packages/vite/SRC/node/server/moduleGraph ts

onFileChange(file: string): void {
    const mods = this.getModulesByFile(file)
    if (mods) {
      const seen = new Set<ModuleNode>()
      mods.forEach((mod) => {
        this.invalidateModule(mod, seen)
      })
    }
  }

  invalidateModule(mod: ModuleNode, seen: Set<ModuleNode> = new Set()): void {
    mod.info = undefined
    mod.transformResult = null
    mod.ssrTransformResult = null
    invalidateSSRModule(mod, seen)
  }
Copy the code

3.2.2.2.2 handleHMRUpdate

The handleHMRUpdate module mainly listens for file changes, processing and judgment through WebSocket to send a message to the client to request the new module code.

Source location: packages/vite/packages/vite/SRC/node/server/HMR ts

3.2.2.3 Client: Websocket communication and update processing

Client: When we configure hot update and not SSR, the Vite layer will write HMR related client code in our code when processing HTML, as follows:

When receiving the message pushed by the server, it processes the message according to different message types. For example, (connected,update,custom…). , which is most frequently used in actual development hot updatesupdate(Dynamic loading hot update module) andfull-reload(Refresh the entire page) event.

Source location: packages/vite/packages/vite/SRC/client/client. The ts

Core code implementation

3.2.2.4 Optimization: The browser cache policy improves the response speed

At the same time,ViteAlso usedHTTPSpeed up the entire page reload. Set the response header so that the dependent module (dependency module) for strong caching, while source files through Settings304 Not ModifiedInstead, it becomes updatable according to conditions.

If you need to make changes to the dependent code module, you can manually invalidate the cache:

vite --force
Copy the code

Or manually delete cache files in node_modules/. ‘ ‘vite.

3.3 EsBuild-based dependent precompilation optimization

3.3.1 Why is pre-build required?
  1. supportcommonJSRely on
  1. The above mentionedViteIs based on browser native supportESMThe ability to implement, but requires that the user’s code module must beESMModule, therefore must becommonJsThe files are processed in advance and converted intoESMModule and cachenode_modules/.vite
  1. Reduce module and request numbers

In addition to the loDash library, which we often use to import packages into each other in separate files, lodash-es packages can have hundreds of submodules and make hundreds of HTTP requests when import {debounce} from ‘lodash-es’ appears in the code. These requests can clog the network and affect page loading.

Vite transforms ESM dependencies that have many internal modules into a single module to improve subsequent page loading performance.

By pre-building LoDash-es into a module, you only need one HTTP request!

3.3.2 WhyEsbuild?

To quote a quote from University of Utah: “Fast” is a word

This is the image of the Esbuild home page. The new generation of packaging tools provide the same resource packaging capability as Webpack, Rollup, and Parcel, but it is 10 to 100 times faster and takes 2% to 3% less time than Webpack

  1. Compile run VS explain run
  • Most front-end packaging tools are based onJavaScriptRealized, you knowJavaScriptIt’s an interpreted language, explaining as it goes along. whileEsbuildSelect useGoLanguage preparation, the language can be compiled into native code, at the time of compilation will be language into machine language, at the time of startup can be directly executed, inCPUIn an intensive scenario,GoMore performance advantages.
  1. Multithreading VS single threading
  1. JavaScriptEssentially a single-threaded language, until introducedWebWorkerBefore it is possible to use the browser,NodeTo achieve multithreaded operation. As far as I’m onWebpackSource code understanding, its source code is not usedWebWorkerProvides multithreading capabilities. whileGONatural multithreading advantage.
  2. The build process is optimized and fully utilizedCPUresources
3.3.3 Implementation Principle?

After Vite is precompiled, the file is cached in the node_modules/. Vite/folder. Consider the following to determine if you need to re-perform the prebuild.

  • package.jsonIn:dependencieschange
  • Package managerlockfile

If you want to force Vite to re-pre-build dependencies, you can either start the development server with –force or just drop the node_modules/.vite/ folder.

3.3.3.1 Core code implementation
  1. throughcreateServercreateserverObject is executed when the server startshttpServer.listenmethods
  1. In the implementationcreateServerWhen,ViteThe bottom layer will rewriteserver.listenMethod: First call the plug-in’sbuildStartTo performrunOptimize()methods
  1. runOptimize()calloptimizeDeps()andcreateMissingImporterRegisterFn()methods
const runOptimize = async () => { if (config.cacheDir) { server._isRunningOptimizer = true try { server._optimizeDepsMetadata = await optimizeDeps( config, config.server.force || server._forceOptimizeOnRestart ) } finally { server._isRunningOptimizer = false } server._registerMissingImport = createMissingImporterRegisterFn(server) } } if (! middlewareMode && httpServer) { let isOptimized = false // overwrite listen to run optimizer before server start const listen = httpServer.listen.bind(httpServer) httpServer.listen = (async (port: number, ... args: any[]) => { if (! isOptimized) { try { await container.buildStart({}) await runOptimize() isOptimized = true } catch (e) { httpServer.emit('error', e) return } } return listen(port, ... args) }) as any } else { await container.buildStart({}) await runOptimize() }Copy the code
  1. optimizeDeps()It is generated mainly from the configuration filehash, get the content of the last pre-purchase build (stored_metadata.jsonFile). Compare if it’s not strong prebuild_metadata.jsonOf the filehashAnd newly generatedhash: Returns if yes_metadata.jsonFile contents, otherwise empty the cache file callEsbuildThe building blocks are saved again_metadata.jsonfile
3.3.3.2 Overall flow chart

The core code is in packages/ vite/SRC/node /optimizer/index.ts

  • Automatic search depends on the main modules:esbuildScanPlugin
  • Pre-build and compile main modules:esbuildDepPlugin
3.3.3.2 Overall flow chart

The core code is in packages/ vite/SRC/node /optimizer/index.ts

  • Automatic search depends on the main modules:esbuildScanPlugin
  • Pre-build and compile main modules:esbuildDepPlugin

3.4 Rollup-based Plugins

Vite takes its cues from Preact’s WMR, inheriting Vite Plugins from the Rollup ‘ ‘Plugins “ “API with some extensions (such as vite-specific hooks). Vite also provides a powerful plug-in API based on the Rollup plugins mechanism. For plugins that are currently compatible with Vite or built in, see Viet-rollup-plugins

3.4.1 What is a Vite plugin

The Vite plug-in extends Vite’s capabilities by exposing some of the timing of the build packaging process in conjunction with utility functions, allowing users to write custom configuration code that is executed during the packaging process. Such as parsing user-defined file input, translating code before packaging it, or searching.

In practice, Vite only needs to be extended based on the Rollup interface, with some Vite specific hooks and attributes added to ensure compatibility with Rollup.

3.4.2 Vite exclusive hooks

The specific use of each hook can be found here

  • config: can be inViteModified before parsingViteRelated configuration of. The hook receives the original user configurationconfigAnd a variable that describes the configuration environmentenv
  • configResolved: parsingViteCall after configuration, confirm configuration
  • configureserver: is used to configure the development serverdev-serverAdd custom middleware
  • transformindexhtml: Mainly used for conversionindex.html, the hook receives the currentHTMLString and transformation context
  • handlehotupdate: Performs customizationHMRUpdates are available throughwsSends custom events to the client
3.4.3 Generic hooks

Here are some common generic hooks. The rest of the common hooks can be moved here

  • Called once when the service starts

    • options: Get, manipulateRollupoptions
    • buildstart: Start creating
  • Called on each incoming module request

    • resolveId: Creates custom validation functions that can be used to locate third-party dependencies
    • load: a custom loader can be used to return custom content
    • transform: is called on each incoming module request, primarily to transform a single module
  • Called once when the service is closed

    • buildend: is called when the server is shut down
    • closeBundle
    3.4.4 Call sequence of hooks

To quote a picture from village chief @Young:

As you can see from the Vite website, the Vite plugin can use a Enforce attribute (similar to the Webpack loader) to adjust its application order. Enforce can be pre or Post. The parsed plug-ins are sorted in the following order:

  • Alias
  • enforce:'pre'Custom plug-in for
  • ViteThe core plug-in
  • There is noenforceCustom plug-in for
  • ViteBuild plug-ins
  • enforce:'post'Custom plug-in for
  • VitePost-build plug-ins
3.4.5 Custom Plug-ins
  • Write plug-in code
Export default function myVitePlugin () {const virtualFileId = '@my-vite-plugin' const virtualFileId = '@my-vite-plugin' Name: 'vite-plugin', // hook // config config: (config, env) => ({console.log('config',config) return {}}), // confirm config configResolved: config => ({}), options: Options => ({}), buildStart: options => ({}), transformIndexHtml: (HTML, CTX) => ({return HTML}), // confirm resolveId: (the source, the importer) = > ({}), / / transformation transform: (code, id) = > ({})}}Copy the code
  • Introducing plug-ins:vite.config.js/tsReferenced in the
// vite.config.js/ts import myVitePlugin from '... ' export default defineConfig{ plugins:[vue(),myVitePlugin()] }Copy the code

4 summarizes

Finally, summarize the pros and cons of Vite:

  • Advantages:

    • Fast cold start: adoptedNo BundleandesbuildPre-built, much faster thanWebpack
    • Efficient hot update: BasedESMRealize and utilizeHTTPHeader to speed up the reload of the entire page and increase caching strategies
    • True on-demand loading: browser-basedESMSupport to achieve true on-demand loading
  • disadvantages

    • Ecology: For nowViteIs less ecological thanWebapckBut I think ecology is just a matter of time.
    • Production environment due toesbuildrightcssAnd code segmentation is not friendly to useRolluppackage

Viet.js is just emerging in the build packaging scene, but in many cases it is better than existing solutions. Consider a full-fledged Webpack if you have ecological, rich loaders and plugins requirements. In other cases, viet.js is a good choice for packaging build tools.

5 appendix

  • The official documentation
  • Esbuild website
  • Vite plug-in apis
  • Rollup official documentation
  • Rollup – Next-generation ES6 module bundler – Interview with Rich Harris
  • Vite source
  • Vite2 plug-in development guide