Hello, I’m glad you can click on this blog. This blog is a series of articles on the interpretation of Vite source code. After reading it carefully, I believe you can have a simple understanding of the workflow and principle of Vite.

Vite is a new front-end build tool that can significantly improve the front-end development experience.

I’m going to use a combination of graphics and text to make this article as boring as possible (obviously not an easy task for a source code reading article).

If you haven’t used Vite yet, you can check out my first two articles. I’ve only been playing it for two days. (below)

  • First experience of Vite + Vue3 — Vite chapter
  • First experience of Vite + Vue3 — Chapter Vue3

This article mainly interprets vite source code ontology. Vite provides a development server through the Connect library and implements a number of development server configurations through the middleware mechanism. Vite does not use packaging tools such as WebPack or rollup for local development. Instead, it schedules internal plugins to translate files so that the results are small and fast.

All right, without further ado, let’s get started!

vite dev

Project directory

The Vite source code for this article is 2.8.0-beta. If you want to read it with me, you can download the Vite source code here.

Let’s start by looking at the project directory for the Vite package. (As shown below)

This is an integrated management project, the core of which is a few packages in the package. (below)

The package name role
vite ViteMaster library, in chargeViteLocal development of the project (plug-in scheduling) and production build (Rollup scheduling)
create-vite Used to create a newViteProjects that house multiple frameworks internally (e.gThe react, vue) initialization template
plugin-vue ViteVue 3 single-file component support
plugin-vue-jsx ViteOfficial plug-in to provide Vue 3 JSX support (through a dedicated Babel conversion plug-in).
plugin-react ViteReact support is an official plugin to provide full React support
plugin-legacy ViteOfficial plugin for traditional browser compatibility support for packaged files
playground ViteBuilt-in test cases and demos

These repositories are actually worth reading, but let’s focus on the main thread of this issue — Vite, and start with Vite.

Vite dev/vite serve: vite dev/vite serve

vite dev

Let’s take a look at the internal workflow of the vite dev command, the local development service.

Vite Dev calls the internal createServer method to create a service that uses middleware (third-party) to support multiple capabilities (cross-domain, static file server, etc.) and internally creates watcher to constantly listen for changes to files, compile in real time, and hot reload.

What createServer does is the core logic we need to focus on.

In the createServer method, the configuration is first collected — resolveConfig.

Vite supported configurations

We can look at the vite project through the source code support configuration, you can also refer to the official Vite documentation. (below)

The name of the configuration Configuration instructions
configFile Configuration file, read in the root directory by defaultvite.config.jsThe configuration file
envFile Environment variable configuration file, read the root directory by default.envEnvironment variable configuration file
root The root directory of the project. The default is the directory where the command is executed —process.cwd()
base Similar to thewebpackIn thepublicPath, which is the common base path of the resource
server Local runtime service Settings, such as setting host, port… For details, seeVite document
build Options when building the production product, refer toVite document
preview Preview option in usebuildAfter the command is executed, you can run itvite previewPreview the product and refer to the specific configurationVite document
publicDir Static resource directory for static resources that do not need to be compiled. The default value ispublicdirectory
cacheDir Cache folder for placingviteSome cache dependencies are precompiled to speed upviteCompilation speed
mode Compile mode, local runtime default isdevelopmentIs the default when building the productionproduction
define Define global variables, where each item in the development environment is defined globally and the production environment is statically replaced
plugins configurationviteProject plug-ins
resolve resolveThere are many configurations supported for your referenceVite document
css aboutcssFile compilation options, you can refer toVite document
json aboutjsonFile compilation options, you can refer toVite document
esbuild Look at the official document is used to convert files, but not quite clear about the specific work is to do what, understand the trouble in the comment area to clarify the confusion
assetsInclude Settings need to bepicomatchPattern (a file matching pattern) a file type that is processed independently
optimizeDeps Depending on optimization options, please refer toVite document
ssr ssrFor details, please refer toVite document
logLevel Adjust the level of console output, default isinfo
customLogger The customloggerThis option is not exposed and is an internal option
clearScreen The default istrue, the configuration offalseAfter each recompilation does not empty the previous content
envDir Used to load environment variable configuration files.envIs the current root directory by default
envPrefix The prefix of the environment variable that will be injected into the project
worker configurationbundleOutput type,pluginsAs well asRollupConfiguration items

Some of these configurations can be added at startup with command-line arguments, such as vite –base / –mode development.

If you want the configuration to be readable via configuration, you can also configure it all via vite.config.js.

Configuring breakpoint debugging

After a cursory look at the configurations supported by Vite, we went back to the createServer function and were ready to read.

Before that, it would be much easier to read the source code if we could just run the vite dev command and set a breakpoint, so let’s configure it first.

We need to go to vite/ Packages/Vite first, install the dependencies, and then run NPM run build in scripts to build vite into the dist directory.

We then use vscode’s debugging capabilities to create a launch.json (below) and run one of our vite projects.

// launch.json
{
  "version": "0.2.0"."configurations": [{"type": "pwa-node"."request": "launch"."name": "Launch Program"."skipFiles": [
        "<node_internals>/**"]."program": "packages/vite/bin/vite.js"."args": ["/Users/Macxdouble/Desktop/ttt/vite-try"]]}}Copy the code

After debugging the configuration, we can break a breakpoint in resolveConfig to see the effect (the file location is in the dist directory, you need to find the corresponding file according to your own reference).

Loading a Configuration File

The first step of resolveConfig is to load the configuration file of the project directory. If you do not specify the location of the configuration file, the root directory will automatically look for vite. Config. js, vite.

If no configuration file is found, the program is aborted.

When a Vite project is initialized, the vite.config.js configuration file is automatically generated in the root directory of the project.

After reading the configuration file, the configuration file is combined with the initial configuration (with higher priority and some configuration from command line parameters) to obtain a configuration. (As shown below)

Configuration collection –resolveConfig

At the beginning of createServer, the resolveConfig function is called for configuration collection.

Let’s take a look at what resolveConfig does.

Handles the plug-in execution order

First, resolveConfig handles the plug-in collation internally, corresponding to the following collation.

In subsequent processing, plug-ins are executed in a sequence that allows them to work better at each lifecycle node.

Merge plug-in configuration

After the plug-in sorting is complete, the Vite plug-in exposes a config field. You can set this property to enable the plug-in to add or rewrite some configurations of vite. (As shown below)

Processing alias

ResolveConfig then handles the alias logic internally, replacing the specified alias with the corresponding path.

Read the environment variable configuration

Next, resolveConfig finds the env configuration directory internally (root by default) and reads the corresponding env environment variable configuration file in that directory. We can look at the internal read rule priority (figure below)

Env.[mode]. Local,.env.[mode]. If no corresponding mode configuration file exists, the system tries to find.env.local and. Env configuration files. After reading the configuration file, use doteenV to write environment variables into the project. If none of these environment variable profiles exist, an empty object is returned.

The environment variable configuration file does not affect the project run, so there is no impact if it is not configured.

Export the configuration

Next, Vite initializes the build configuration, which is the Build attribute in the documentation, as described in the Build Options documentation

Finally, the following configuration is exported after resolveConfig handles some publicDir and cacheDir directories.

constresolved: ResolvedConfig = { ... config,configFile: configFile ? normalizePath(configFile) : undefined,
    configFileDependencies,
    inlineConfig,
    root: resolvedRoot,
    base: BASE_URL,
    resolve: resolveOptions,
    publicDir: resolvedPublicDir,
    cacheDir,
    command,
    mode,
    isProduction,
    plugins: userPlugins,
    server,
    build: resolvedBuildOptions,
    preview: resolvePreviewOptions(config.preview, server),
    env: {
      ...userEnv,
      BASE_URL,
      MODE: mode,
      DEV: !isProduction,
      PROD: isProduction
    },
    assetsInclude(file: string) {
      return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file)
    },
    logger,
    packageCache: new Map(),
    createResolver,
    optimizeDeps: {
      ...config.optimizeDeps,
      esbuildOptions: {
        keepNames: config.optimizeDeps? .keepNames,preserveSymlinks: config.resolve? .preserveSymlinks, ... config.optimizeDeps? .esbuildOptions } },worker: resolvedWorkerOptions
  }
Copy the code

ResolveConfig also does some additional work inside the resolveConfig to collect the internal plug-in collection (as shown below) and to configure warnings for deprecated options.

Local development services –createServer

Back to the createServer method, this method handles the SSR (server-side rendering) logic as soon as it gets the configuration from resolveConfig.

If server-side rendering is used, local development debugging is done in a different way.

If it is not server-side rendering, an HTTP Server is created for local development debugging and a Websocket service is created for hot overloading. (As shown below)

File listening + hot overloading

Vite then creates a FSWatcher object that listens for changes to local project files. (The Chokidar library is used here)

  const watcher = chokidar.watch(path.resolve(root), {
    ignored: [
      // Ignore file changes in the node_modules directory
      '**/node_modules/**'.// Ignore file changes in the.git directory
      '**/.git/**'.// Ignore changes to the directory file passed in by the user. (Array.isArray(ignored) ? ignored : [ignored])
    ],
    ignoreInitial: true.ignorePermissionErrors: true.disableGlobbing: true. watchOptions })as FSWatcher
Copy the code

Vite then organizes the properties and methods into a single Server object, which is responsible for starting the local development service and for the subsequent development hot reload of the service.

Let’s take a look at how Watcher does hot page reloading by listening for file changes, retriggering plug-in compilation, and then sending the update message to the client. (As shown below)

Plug-in container

Next, Vite creates pluginContainers that call the plug-in’s hooks at various stages of the build. (As shown below)

In fact, the plug-in container was created before the hot reload, and for ease of reading, the article puts the hot reload content together.

Middleware mechanisms

Next comes some handling of internal middleware, which is supported internally by the middleware capabilities of the Connect framework when configuring development server options. (As shown below)

Among them, the public directory, public path and other configurations are realized through connect + middleware, making full use of the capabilities of third-party libraries, rather than repeating the wheel.

Prebuilt dependencies

Next, the dependencies used in the project were pre-built within Vite, both for compatibility with different ES module specifications and to improve load performance. (As shown below)

When all is ready, vite calls startServer internally to start the local development server. (below)

// ...
httpServer.listen(port, host, () = > {
  httpServer.removeListener('error', onError)
  resolve(port)
})
Copy the code

summary

At this point, the source code part of vite itself is parsed.

As you can see, Vite relies on the plug-in + middleware architecture to provide capability support for local development. Because only a little compilation work is involved in native development, it is very fast. Vite is only built with rollup when the production is built.

Let’s take a final look at the internal workflow of the Vite native development service with a flow chart.

So that’s the end of this article. In the next article, I’ll pick one or two typical plug-ins or builds for source code parsing.

One last thing

If you’ve already seen it, please give it a thumbs up

Your likes are the greatest encouragement to the author, and can also let more people see this article!

If you find this article helpful, please help to light up the star on Github.