Exploring Vite Through its Source Code

Share this article

Exploring Vite Through its Source Code

As you’ve probably heard, the front-end ecosystem has a new cool kid on the block: a build tool called Vite. Although it was created by Evan You (who also created Vue.js), it’s not framework-specific, so you can use Vite with Vue.js, React.js, Svelte.js, or even vanilla JavaScript.

In this article, we’ll expand upon the overview that was already published here and examine Vite’s source code to extract some insights about its internal architecture. In particular, we’ll explore Vite’s template and plugin systems. By the end, you’ll have a better understanding of the difference between templates and plugins, and how Vite’s core system is connected to a plugin.

Now without further ado, let’s create an app with Vite.

Creating an App with Vite

For the purposes of this demo, we’ll be creating a Vue project, using this command:

npm init vite@latest

(Having the @latest will make sure you always get the latest version whenever you do npm install inside this newly created project.)

As a side note, you might have seen a deprecated version of the init command.

@vitejs/create-app is deprecated

As you can see, the deprecation warning tells us to use npm init vite instead.

This new command is basically a shorthand for:

npx create-vite

This will install and run a tool called create-vite, which gives you prompts about what kind of project you’re creating. You’ll select a name and a template.

Select a name you like for your project.

Select a project name

And select a template to use.

Select a template

For exploration purposes, you can go with either vanilla or vue.

Next, we’ll explore this create-vite tool through its source code on GitHub.

Exploring the Vite Source Code

First, go to Vite’s GitHub page at github.com/vitejs/vite.

Vite's GitHub repo

Then head inside the packages folder.

Inside the packages folder

Here, you can see create-app and create-vite.

create-app was responsible for the original command that says “deprecated”. What we’re interested in here is the create-vite folder. It hosts all the built-in templates for project creation.

Inside the packages folder, we can also see some plugin folders for a few built-in plugins.

Now it’s a good time to explore the differences between templates and plugins, and how they work together in the build tool workflow.

Templates

Template should be an easy concept to understand: it’s the starter code for a new project.

Inside the packages/create-vite folder, you should see a dozen template-* folders.

📁 /packages/create-vite

Inside the create-vite folder

As you can see, Vite supports templates for various different frameworks (and their TypeScript counterparts).

You can choose vanilla from the create-vite prompt.

Select a template

If you choose vanilla, it will basically take the files in the packages/template-vanilla folder and clone them as your new project.

📁 /packages/template-vanilla

Inside the template-vanilla folder

You can also choose vue from the prompt:

Select vue from the prompt

If you choose vue, it will clone the files in the packages/template-vue folder as your new project.

📁 /packages/template-vue

Inside the template-vue folder

The generated project from the vue template will feature the standard folder structure that you would expect from a Vue project.

So that’s template. Now let’s talk about plugin.

Plugins

As I mentioned, Vite isn’t framework-specific. It’s able to create projects for various frameworks because of its plugin system.

Out of the box, Vite provides plugins for Vue, Vue with JSX, and React.

You can examine the code for each built-in plugin in the packages folder:

📁 /packages

Various plugins

Note: plugin-legacy is for legacy browsers that don’t support native ESM.

The most common way that these plugins are used is through their corresponding templates. For example, the Vue template will require the use of the Vue plugin, and the React template will require the use of the React plugin.

As the bare-bones option, a project created with the vanilla template has no idea how to serve Vue’s single-file component (SFC) files. But a Vue project created with Vite will be able to process the SFC file type. And it also knows how to bundle the entire Vue project for production.

If we compare the respective package.json files from the Vue template and the vanilla template, we can easily see why that is.

📁 /packages/template-vanilla/package.json

Vanilla package.json

📁 /packages/template-vue/package.json

Template-vue package.json

template-vue contains everything that template-vanilla has, plus three additional packages.

📁 /packages/template-vue/package.json

"dependencies": {
    "vue": "^3.2.6" // 1
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.6.1", // 2
    "@vue/compiler-sfc": "^3.2.6", // 3
    "vite": "^2.5.4"
  }
  • vue is the main library that runs during runtime
  • @vitejs/plugin-vue is the plugin that’s responsible for serving and bundling a Vue project
  • @vue/compiler-sfc is needed for compiling an SFC file

So it’s safe to say that these three packages give a Vite project the ability to understand Vue code. The @vitejs/plugin-vue package is the “bridge” connecting Vite’s core system to the Vue.js framework.

In Evan You’s own words…

In the rest of the article, we’ll continue our exploration with the Vue template. But if you want to see more cool things with the vanilla template, you can check out this tutorial from Evan You’s Lightning Fast Builds with Vite course.

Vue Plugin

As we’ve seen in the Vue plugin’s package.json, the @vitejs/plugin-vue package is responsible for bundling a Vue project.

Vite delegates the bundling work to Rollup, which is another very popular build tool. The plugin relationship relies on the vite core to call the plugin package code at some specific points in time. These specific points are called “hooks”. The plugin developer has to decide what code gets executed in each hook.

For example, in the Vue plugin source, you can see some of these hooks implemented.

📁 /packages/plugin-vue/src/index.ts

async resolveId(id) {
  // component export helper
  if (id === EXPORT_HELPER_ID) {
    return id
  }
  // serve sub-part requests (*?vue) as virtual modules
  if (parseVueRequest(id).query.vue) {
    return id
  }
},

load(id, ssr = !!options.ssr) {
  if (id === EXPORT_HELPER_ID) {
    return helperCode
  }

  const { filename, query } = parseVueRequest(id)
  // select corresponding block for sub-part virtual modules
  if (query.vue) {
    if (query.src) {
      return fs.readFileSync(filename, 'utf-8')
    }
    const descriptor = getDescriptor(filename, options)!
    let block: SFCBlock | null | undefined
    if (query.type === 'script') {
      // handle <scrip> + <script setup> merge via compileScript()
      block = getResolvedScript(descriptor, ssr)
    } else if (query.type === 'template') {
      block = descriptor.template!
    } else if (query.type === 'style') {
      block = descriptor.styles[query.index!]
    } else if (query.index != null) {
      block = descriptor.customBlocks[query.index]
    }
    if (block) {
      return {
        code: block.content,
        map: block.map as any
      }
    }
  }
},

transform(code, id, ssr = !!options.ssr) {
  const { filename, query } = parseVueRequest(id)
  if (query.raw) {
    return
  }
  if (!filter(filename) && !query.vue) {
    if (!query.vue && refTransformFilter(filename)) {
      if (!canUseRefTransform) {
        this.warn('refTransform requires @vue/compiler-sfc@^3.2.5.')
      } else if (shouldTransformRef(code)) {
        return transformRef(code, {
          filename,
          sourceMap: true
        })
      }
    }
    return
  }
    if (!query.vue) {
    // main request
    return transformMain(
      code,
      filename,
      options,
      this,
      ssr,
      customElementFilter(filename)
    )
  } else {
    // sub block request
    const descriptor = getDescriptor(filename, options)!
    if (query.type === 'template') {
      return transformTemplateAsModule(code, descriptor, options, this, ssr)
    } else if (query.type === 'style') {
      return transformStyle(
        code,
        descriptor,
        Number(query.index),
        options,
        this
      )
    }
  }
}

And in the main vite package, Rollup will be used to call on the above plugin hooks.

📁 /packages/vite/src/node/build.ts

// first, gathers all the plugins used
const plugins = (
  ssr ? config.plugins.map((p) => injectSsrFlagToHooks(p)) : config.plugins
) as Plugin[]

...

// then, put the plugins and everything else in an options object
const rollupOptions: RollupOptions = {
  input,
  preserveEntrySignatures: ssr
    ? 'allow-extension'
    : libOptions
    ? 'strict'
    : false,
  ...options.rollupOptions,
  plugins,
  external,
  onwarn(warning, warn) {
    onRollupWarning(warning, warn, config)
  }
}

...

// lastly, delegate to rollup
const bundle = await rollup.rollup(rollupOptions)

A Rollup plugin is very similar to a Vite plugin. But since Rollup isn’t intended to be used as a development build tool out of the box, a Vite plugin will have extra options and hooks that aren’t available in a classic Rollup plugin.

In other words, a Vite plugin is an extension of a Rollup plugin.

Vite Commands

Getting back to the Vue template, let’s put some attention on the scripts option.

📁 /packages/create-vite/template-vue/package.json

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "serve": "vite preview"
},

These are the configurations that enable us to do the following commands inside a Vite project:

  • npm run dev for starting a development server
  • npm run build for creating a production build
  • npm run serve for previewing the said production build locally

The above commands are mapped to the following commands:

  • vite
  • vite build
  • vite preview

As you can see, the vite package is where everything starts.

You can get an idea of what other third-party tools are involved by looking inside the package.json file of the vite package.

📁 /packages/vite/package.json

"dependencies": {
  "esbuild": "^0.12.17",
  "postcss": "^8.3.6",
  "resolve": "^1.20.0",
  "rollup": "^2.38.5"
},

As you can see, vite is actually using two different bundlers behind the scene: Rollup and esbuild.

Rollup vs esbuild

Vite is using both of these bundlers for different types of activities.

Rollup is used by Vite for the main bundling needs. And esbuild is used for module compatibility and optimization. These steps are known as the “Dependency Pre-bundling” process. This process is considered “heavy duty” because it’s needed to be done on a per-module basis, and there are usually many modules used in a project.

Module compatibility means converting different formats (UMD or CommonJS modules) into the standard ESM format.

Optimization is for bundling all the various files from a single depended package into a single “thing”, which then only needs to be fetched once.

Rollup would be too slow to handle these heavy-duty things in comparison to esbuild. Esbuild is actually the fastest build tool out there. It’s fast because it’s developed in Go (the programming language).

Here’s a comparison shown on the official documentation website.

Bundler benchmark

As you can see, esbuild isn’t just fast; it’s on a whole other level. And that’s why Vite is lightning fast. ⚡

Summary

In this article we’ve gone through the source and learned that:

  • the npm init vite command is using the create-vite tool
  • the create-vite package contains all the built-in templates
  • a framework-specific template depends on its corresponding framework-specific plugin
  • plugins are implemented in a hooks-based architecture
  • Vite is using both Rollup and esbuild behind the scenes

Now you should have a solid understanding of the Vite system. But, in practice, you’d need other common features that we haven’t covered here. The most common ones would be TypeScript and CSS preprocessor supports.

To learn about these topics and more, you can check out Evan You’s Lightning Fast Builds with Vite course available on VueMastery.com.

Frequently Asked Questions (FAQs) about Vite Source Code

What is Vite and why is it important in web development?

Vite is a modern front-end build tool developed by Evan You, the creator of Vue.js. It offers a faster and leaner development experience for modern web projects. Vite provides features like hot module replacement and esbuild-powered fast production builds. It’s designed to be easy to use, highly efficient, and extremely fast, which makes it a valuable tool for web developers.

How does Vite differ from traditional build tools?

Traditional build tools like Webpack or Rollup rebuild and reload the entire app whenever a file is saved. Vite, on the other hand, only needs to update the code that has changed, making it significantly faster. This is particularly beneficial for large-scale projects where build times can become a bottleneck.

How can I get started with Vite?

To get started with Vite, you need to install it globally using npm or yarn. Once installed, you can create a new project using the ‘create-vite’ command followed by the project name and the template you want to use. Vite supports various templates including vanilla, Vue, React, Preact, and LitElement.

How can I configure Vite for my project?

Vite allows you to customize its behavior via a configuration file named ‘vite.config.js’. This file exports a configuration object where you can specify various options like base public path, build options, CSS options, server options, and plugins.

How does Vite handle CSS?

Vite has built-in support for CSS, PostCSS, and CSS modules. It also supports CSS pre-processors like Less, Sass, and Stylus. You can import CSS directly in your JavaScript or TypeScript files, and Vite will handle them appropriately.

How can I deploy a Vite project?

To deploy a Vite project, you first need to build it using the ‘vite build’ command. This will generate a production-ready version of your project in the ‘dist’ directory. You can then deploy this directory to any static file server or CDN.

Can I use Vite with TypeScript?

Yes, Vite has out-of-the-box support for TypeScript. You can use TypeScript for both your source code and your Vite configuration file. Vite uses esbuild to transpile TypeScript, which is significantly faster than tsc.

How can I use plugins with Vite?

Vite uses the Rollup plugin interface, so you can use any Rollup plugin by adding it to the ‘plugins’ array in your Vite configuration file. Vite also provides its own plugin API for handling custom file types and applying transformations.

How does Vite handle assets?

Vite treats any imported file that is not JavaScript or CSS as an asset. It uses the new URL import syntax to handle assets, which allows for better performance and compatibility compared to traditional loaders and file emit strategies.

Can I use Vite for server-side rendering (SSR)?

Yes, Vite has experimental support for server-side rendering. It provides an SSR-specific plugin API and utilities for handling module-to-module dependencies, CSS management, and asset handling in an SSR context.

Andy Li is a developer, technical writer and educational content creator, working with learning platforms such as VueMastery.com.

Vite
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week