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 is not frame specific, so you can use Vite with Vue.js, React.js, Svelte.js or even vanilla JavaScript.

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

Let us now simply create an app with Vite.

Creating an app with Vite

In connection with this demo, we create a Vue project using this command:

npm init [email protected]

(Having @latest will ensure that you always get the latest version when you do npm install inside this newly created project.)

As a side note, you may have seen an outdated version of init command.

As you can see, the rejection warning tells us to use it 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 notifies you of the type of project you are creating. You choose a name and a template.

Choose a name you like for your project.

Select a project name

And select a template to use.

Choose a template

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

Next, we examine this create-vite tool through its source code on GitHub.

Study of Vite Source Code

First go to the Vites GitHub page at github.com/vitejs/vite.

Vite's GitHub repo

Then go into packages folder.

Inside the package folder

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

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

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

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

Templates

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

Inside 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 different frames (and their TypeScript counterparts).

You can choose vanilla from create-vite fast.

Choose a template

If you choose vanilla, it pretty much takes the files in packages/template-vanilla folder and clone them as your new project.

📁 / packer / 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 packages/template-vue folder as your new project.

📁 / packages / template-view

Inside the template-vue folder

The project generated from the vue template contains the default folder structure that you would expect from a Vue project.

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

Plugins

As I mentioned, Vite is not frame specific. It is able to create projects for different frameworks due to its plugin system.

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

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

📁 / packages

Different plugins

Note: plugin-legacy is for older browsers that do not support native ESM.

The most common way these plugins are used is through their corresponding templates. Eg. The Vue template requires the use of the Vue plugin, and the React template requires the use of the React plugin.

As a bare-bones option, a project created with the vanilla template has no idea how to operate Vue’s single-file component (SFC) files. However, a Vue project created with Vite will be able to handle the SFC file extension. And it also knows how to bundle the entire Vue project into 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 pack.  Json

📁 /packages/template-vue/package.json

Template view package.  Json

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

📁 /packages/template-vue/package.json

"dependencies": {
    "vue": "^3.2.6" 
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^1.6.1", 
    "@vue/compiler-sfc": "^3.2.6", 
    "vite": "^2.5.4"
  }
  • vue is the main library that runs during runtime
  • @vitejs/plugin-vue is the plugin that is responsible for operating and bundling a Vue project
  • @vue/compiler-sfc is required to compile an SFC file

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

In Evan You’s own words …

In the rest of the article, we continue our exploration with the Vue template. But if you want to see more cool stuff with the Vanilla Template, check out this tutorial from Evan You’s Fast build with Vite Route. (You can watch the entire course for free by signing up for Vite Weekend at VueMastery.com.)


Vue plugin

As we have seen in Vue plugins package.json, that @vitejs/plugin-vue the package is responsible for bundling a Vue project.

Vite delegates the bundling work to Rollup, which is another very popular construction tool. The plugin ratio depends on vite core to call plugin package code at certain times. These specific points are called “hooks”. The plugin developer must decide which code to execute 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) {
  
  if (id === EXPORT_HELPER_ID) {
    return id
  }
  
  if (parseVueRequest(id).query.vue) {
    return id
  }
},

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

  const { filename, query } = parseVueRequest(id)
  
  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') {
      
      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/[email protected]^3.2.5.')
      } else if (shouldTransformRef(code)) {
        return transformRef(code, {
          filename,
          sourceMap: true
        })
      }
    }
    return
  }
    if (!query.vue) {
    
    return transformMain(
      code,
      filename,
      options,
      this,
      ssr,
      customElementFilter(filename)
    )
  } else {
    
    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, assembly update will be used to call the above plugin hooks.

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


const plugins = (
  ssr ? config.plugins.map((p) => injectSsrFlagToHooks(p)) : config.plugins
) as Plugin[]

...


const rollupOptions: RollupOptions = {
  input,
  preserveEntrySignatures: ssr
    ? 'allow-extension'
    : libOptions
    ? 'strict'
    : false,
  ...options.rollupOptions,
  plugins,
  external,
  onwarn(warning, warn) {
    onRollupWarning(warning, warn, config)
  }
}

...


const bundle = await rollup.rollup(rollupOptions)

A rollup plugin is very similar to a Vite plugin. However, since Rollup is not intended to be used as an out-of-the-box development tool, a Vite plugin will have additional options and hooks not available in a classic Rollup plugin.

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

White commands

Come back to the Vue template, let’s pay some attention scripts possibility.

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

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

These are the configurations that allow us to execute the following commands in a Vite project:

  • npm run dev to start a development server
  • npm run build to create a production structure
  • npm run serve for previewing the said production structure locally

The above commands are associated with the following commands:

  • vite
  • vite build
  • vite preview

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

You can get an idea of ​​what other third-party tools are involved by looking inside package.json the file of 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 actually uses two different bundles behind the scenes: Rollup and esbuild.

Rollup vs esbuild

Vite uses both of these bundles for different types of activities.

Assembly update is used by Vite for the most important bundling needs. And esbuild is used for module compatibility and optimization. These steps are known as the “Addiction Bonding” process. This process is considered “heavy duty” because it must be performed per. Module, and there are usually many modules used in a project.

Module compatibility means converting various formats (UMD or CommonJS modules) to standard ESM format.

Optimization is to gather all the different files from a single dependent package into a single “thing”, which then only needs to be downloaded once.

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

Here is a comparison shown on the official documentation website.

Bundler benchmark

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

Summary

In this article, we have reviewed the source and learned that:

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

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

These topics and more are all covered by Evan You’s Fast build with Vite Route. You can watch it for free at VueMastery.com below Vite Weekend. The course is only unlocked 24-26. September. You can register for your available place here.

William

Leave a Reply

Your email address will not be published.