Current engineering pain points

Before browsers supported ES Modules, JavaScript did not provide native mechanisms for developers to develop in a modular manner. This is where packaging tools come in: tools to grab, process, and concatenate source code modules into files that can be run in a browser.

There are tools like Webpack, Rollup, and so on that have greatly improved the front-end developer experience. But as you build bigger and bigger applications, the amount of JavaScript code you need to process grows exponentially. It is quite common for a project to contain thousands of modules. Developers are starting to hit performance bottlenecks. Tools developed using JavaScript often take a long time to start up the development server, and even with HMR, the effect of the file changes takes a while to appear in the browser, and so on and so forth, which can greatly affect the development experience.

Slow service startup

A common package build tool, such as WebPack, basically crawl-compile-builds the entire application code, and then produces a compilation that is optimized to work well with all browsers. In the development environment, the process is basically the same, the entire application build needs to be packaged first, and then the packaged code is handed over to dev Server. The initial startup is very slow. With the complexity of front-end business and the large-scale of projects, the amount of JavaScript code increases exponentially, and the packaging and construction time is getting longer and longer. The dev Server startup time of large projects reaches tens of seconds or even minutes.

Common packager dev Server procedure:

Slow update

Rebuilding the entire package is inefficient when the packager-based startup is in place. Although the packer now supports HMR hot updates for modules, when files in a project change, the packer repackages the associated dependencies (not just the current file). The rate of hot updates decreases significantly as the program size increases, reaching a performance bottleneck that leaves little room for optimization.

Why Vite?

Vite takes advantage of new advances in the front-end ecosystem to address these issues. Browsers are starting to support ES Modules natively, and more front-end tools are written in compiled languages such as Go (ESBuild) and Rust (SWC).

Fix slow starts

Vite directly converts the ES Module code, throw to the browser that supports ES Module, use the browser to support ES Module (script tag with attribute type=” Module “can be), let the browser to load. The browser requests individual modules directly to dev Server, without having to package all the files in advance, allowing the project to start up quickly.

Vite improves the startup time of the development server by initially separating application modules into dependencies and source code.

  • The dependencies are mostly pure JavaScript that doesn’t change at development time. Some large dependencies, such as component libraries with hundreds of modules, are expensive to handle. Dependencies also often exist in multiple modular formats such as CommonJS, ES Module, etc. Vite uses ESBuild to pre-build dependencies. Build third-party libraries with ESBuild’s super fast compilation speed. On the one hand, package scattered files together to reduce network requests, and on the other hand, fully convert ES Module syntax to fit the browser’s built-in ES Module support. ESBuild is written in Go and is 10 to 100 times faster than the pre-built dependencies of packaging tools written in pure JavaScript.
  • Source code often contains files that are not directly JavaScript, such as CSS. These files need to be converted and are frequently edited. Not all source modules need to be loaded at the same time. Vite provides the source code as a native ES Module. This essentially lets the browser take over some of the packaging, and Vite simply transforms the source code when the browser requests it and makes it available on demand.

Vite dev Server process:

Fix slow updates

In Vite, HMR is executed on the native ES Module. When editing a file, Vite just needs to precisely deactivate the connection between the edited module and its nearest HMR boundary (mostly just the module itself), and thermal update of the module can be done without rebuilding the entire application, so that HMR can always be updated quickly, regardless of the size of the application. Vite also uses browser caching to speed up entire page reloading: requests from source modules are negotiated through 304 Not Modified, while dependent modules are forced to Cache through cache-control, and will not be requested again once cached.

Introduction of Vite

Vite is a new front-end build tool based on ESBuild and Rollup. In the development environment, we rely on the browser’s ES Module compilation function to parse the import. Dev Server compiles the Module and returns it on demand, completely removing the packaging step. At the same time, the development environment has efficient module hot update (HMR), and the speed of hot update does not slow down with the increase of modules, which significantly improves the development experience.

It mainly consists of two parts:

  • Development build: Based on the browser’s native ES Module capabilities to provide source files, with efficient HMR built in.
  • Production build: Production environments are packaged using Rollup, and Vite provides instructions to optimize the build packaging process.

Vite characteristics

  • Quick cold start: No Bundle + ESBuild prebuild.
  • Instant Module hot update: ES Module based HMR, while leveraging browser caching strategy for speed.
  • True on-demand loading: Use the browser’s ES Module support to achieve true on-demand loading.

Browser support

  • Development environment: Vite needs to be used on browsers that support native ES Module imports.
  • Production environment: Browsers need to support the introduction of native ES Modules via script tags. Older browsers can be supported via the official plugin @vitejs/ plugin-Legacy.

Vite body flow

Vite principle

When declaring a script tag of type module, for example:

 <script type="module" src="/src/main.js"></script>
Copy the code

When the browser parses the resource, it makes an HTTP request to the current domain name to get the main.js file. Then it finds that main.js contains the module imported by import, so it makes an HTTP request to retrieve the contents of the module. In the startup project phase, Vite will start a Koa Dev Server to intercept the browser’s request for ES Module. After dev Server processes the Module, it will load the Module into the browser. So the whole packaging phase is skipped and loaded on demand.

Principle of Request interception

When an import is encountered, the browser makes an HTTP request for the module content. Dev Server, upon receiving the request, intercepts the request, processes the module, and returns the result.

Rewrite module path

Since browsers only recognize./,.. The relative path starting with/and the absolute path starting with /, while the development process often introduces modules under node_modules that the browser cannot recognize and load. So Vite’s Dev Server overrides the module’s path through the es-module-lexer and magic-String libraries when requesting interception.

HMR principle

The hot update principle of Vite is that a websocket link is established between the client and the server to listen for changes in the file. When the file is changed, dev Server sends a message to inform the client to change the corresponding code.

Hot update main process:

  • Create a WebSocket server and client file to start the service.

    After the project starts, Vite will inject the client while processing the HTML.

  • Use chokidar’s watch method to create a listener, watcher, that listens for file changes.

    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) => { })Copy the code
  • When the code changes, the server makes a judgment and pushes it to the client.

    The client performs different update operations depending on the type of push message.

Client processing of push message types

The optimization of Vite

Depend on prebuild

Vite prebuilds dependencies for two reasons:

  • CommonJS and UMD compatibility: Since Vite is implemented based on the browser’s ability to support ES Module natively, but in the development phase, Vite treats all code as native ES Module modules, so Vite must convert CommonJS and UMD published dependencies to ES Module in advance. And cache node_modules/.vite.
  • Reduce the number of modules and requests: Vite improves page load performance by converting ESM dependencies that have many internal modules into a single module. Some packages import their ES Module builds to each other as many separate files. Such aslodash-esThere are more than600Two built-in modules. When parsingimport { debounce } from 'lodash-es'When the browser makes more than 600 HTTP requests simultaneously! These requests can cause network congestion and affect page loading. So Vite through pre-build willlodash-esBuilt as a module, so only one HTTP request is required.

The cache

File system caching

Vite caches pre-built dependencies to node_modules/.vite. It determines whether the pre-build step needs to be rerun based on several sources:

  • List of Dependencies in package.json.
  • Lockfile for package manager, for examplepackage-lock.json.yarn.lockOr,pnpm-lock.yaml.
  • May be invite.config.jsThe value has been configured in related fields.

You only need to rerun the prebuild if one of the above changes.

If for some reason you want to force Vite to rebuild dependencies, you can either start the development server with the –force command-line option, or manually delete the node_modules/.vite directory.

Browser cache

Requests from source modules are cached by negotiation 304 not Modified, while dependent modules are cached by cache-control: max-age=3253600,immutable. Once cached, they do not need to be requested again.

Current problems

  • React support is not as comprehensive as Vue support.
  • Compatibility problems with mobile browsers.
  • Vite ecology is not as powerful as WebPack.