When I started with Vue, I relied on building Vue projects directly in the browser. This was a great way to start simple projects, but as projects increased in complexity, I soon wanted a build environment. My first choice was obvious: the Vue CLI. This has worked exceptionally well. Recently I was introduced to Vite as an alternative to this build system. I wanted to try out this new way of building and developing Vue project and I found that I liked it quite a bit. Let's dig into how Vite works.
What is Vite?
If you've been using Vue for a while, you've likely used the Vue CLI to develop large projects. The Vue CLI has been a great tool for building your project and managing the webpack internals for you. If you don't know, Evan You is the lead developer of Vue and the Vue CLI (et al.). He's at it again. He developed a new tool called Vite (or fast, in French).
Vite isn't specific to Vue, but instead is a way to develop JavaScript/TypeScript applications. Let's look at the how the Vue CLI works and how Vite is different.
Evan You is at it again. He developed a new tool called Vite (French for fast).
Vue CLI
Many command-line interfaces for Web frameworks try to hide the configuration of webpack from you, but sometimes you still need to dig into the configuration. The purpose of the Vue CLI was to simplify converting your code into browser-proof code. It did this by often using Babel or TypeScript to convert the code to ES5 for older browsers, and then webpack packaged up the resulting transpilations into a more efficient package of code for the browser. This works well in most cases.
Vue CLI (like other solutions) relies on webpack to build the packages and then update them as you make changes during development. Webpack is specifically used to do hot-swapping of code within your codebase in the browser to try to make the development process seamless. This solution requires both transpiling and hot-swapping of the code on every change, which results in a rather long startup time, and slower than necessary re-compiling time during development.
Why Vite
In contrast, Vite doesn't rely on transpilation at all (although TypeScript code still needs to be converted into JavaScript). Instead, it relies on ECMAScript 6's Module support. This support allows the browser to retrieve modules as you import them. Because you're not building your project during development, the startup speed and compilation time is greatly reduced. Although these benefits are great, this means (in development) that you'll have to use a newer browser (e.g., Chrome, Edge, Firefox) to ensure that it has ES6 Module support.
In production, Vite uses Rollup (which can be more efficient than webpack) to package your project, much like the Vue CLI does. Because this compilation step is only required once you have your code ready for production (or test, etc.), the development experience should be a lot more seamless.
Enough talk, let's see it in action.
In production, Vite uses Rollup.
Using Vite
Before using Vite, you'll need a couple of prerequisites:
- Node.js: 12+
- Compatible browser for development
This second requirement is a browser that supports dynamic imports. You can check to see if your browser is supported by visiting: https://caniuse.com/es6-module-dynamic-import.
Most modern browsers are supported, with the exceptions being Internet Explorer, Opera Mini, and the Baidu browsers. But if you're on a somewhat recent version of Chrome, Edge, Safari, or Firefox, you should be all set.
Unlike other solutions, there isn't a global package to install or anything; you'll just create a project and go. Although I'm focusing on Vue in this article, note that Vite also works with React, Preact, Svelte, Vanilla JavaScript, and LitElements. Let's create your first project.
Creating a Project with Vite
To get stared, you'll just need to use npm install for Vite:
> npm init @vitejs/app
This walks you through the process of creating the project (as seen in Figure 1).
Once you pick these options, it's just a matter of moving into the directory and installing the requirements:
> chdir ./inviting/
> npm i
Once everything is installed, you can get started by just starting the server, like:
> npm run dev
You're ready to start developing your new Vue project. Now that you've created it, what's inside that makes this work?
What's Inside?
It should come as no surprise that the new project is really similar to a typical Vue CLI project, as seen in Figure 2:
The first big difference is that the index.html
file is no longer buried inside of the public folder; it's the starting point for your development. In fact, the magic of Vite starts inside that file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport"
content="width=device-width,
initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module"
src="/src/main.js">
</script>
</body>
</html>
Note that magic little script tag. The type of “module” specifies the starting file of the project. What is src/main.js
?
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
What is this? Just Vue, right? Plain old Vue. What's happening when you run this app? See Figure 3 and see how the individual files are being loaded, not packaged:
What's interesting here is that there isn't a single bundle. It loads your main.js
and then the Vue library, App.vue
, then HelloWorld.vue
as network requests. This means that there's little to no compiling of your project. But it also means that as you change your code, it can quickly - in fact, very quickly - hot-swap your code files with those changes. This means that development should be faster to notice changes as well as having quicker swapping.
Production Builds
While development relies on dynamic module loading, production builds are similar to the way that the Vue CLI (e.g., webpack) builds them. For example, if you build the project, you still get a packaged set of files, as seen in Figure 4.
Unlike the Vue CLI, the build is handled with the Rollup project. But the result is similar. If you were to deploy this to a server, it would work.
Now you can see it working with a new project, but what about your existing Vue projects? Although it can be a pretty easy move to using Vite, there are some changes you might need to make in your projects.
Using Vite in Existing Vue Projects
Once I started using Vite, I almost immediately wanted to apply it to my own projects. It's mostly a drop-in replacement for the CLI, but not exactly. Let's walk through the steps.
Installing Vite
First, I removed the CLI dependencies, leaving only the @vue/compiler-sfc
in place. I did this by just editing the project.json
:
"devDependencies": {
"@vitejs/plugin-vue": "^1.1.4",
"@vue/compiler-sfc": "^3.0.5",
"vite": "^2.0.0-beta.70"
}
Next, I added Vite to the dev dependencies, like so:
> npm i vite @vitejs/plugin-vue --save-dev
With that in place, I edited the scripts to use Vite instead (again, in package.json
):
"scripts": {
"dev": "vite",
v"build": "vite build"
},
For good measure, I deleted the package.json.lock
and ran the install on npm:
> npm i
Now that the packages were installed, I needed a vite.config.js
file:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()]
})
Because Vite works with a range of project types, you need to include the Vue plug-in to make it work. (There are plug-ins for the other project types, too). I need one more change, and that's to move the index.html
(or create a new one) into the root project folder. In the Vue CLI, it injects the built project into the index.html
. For Vite, you just need to include a link to the root code file (e.g., /src/main.js
or /src/main.ts
):
<script type="module" src="/src/main.js"></script>
Don't forget the script type; it's crucial to how this works. With all of that in place, you should be able to just run the project:
> npm run dev
Did it work? For my project, it didn't. The first culprit was something that I depended on quite a lot and it turns out it's a default behavior in webpack: the @ sign in paths. I found importing using the “@/...” really useful for pointing at parts of my project but because Vite doesn't use webpack, I had to decide how to do this. I found lots of workarounds, but I decided to just move on from it because I don't particularly like workarounds. This meant finding all the @/ paths and changing them to relative paths:
This meant that these imports:
import store from "@/store";
import router from "@/router";
import { computed, onMounted } from "vue";
Became these imports:
import store from "../store";
import router from "../router";
import { computed, onMounted } from "vue";
You could also use absolute paths (from the project root, not src):
import store from "/src/store";
import router from "/src/router";
import { computed, onMounted } from "vue";
Using absolute URLs can result in complaints from some add-ins for VS Code, but it's still perfectly valid.
Open up your app and it should be working like it was before. As you change files, you should see Vite telling you when a module is hot-swapped:
6:46:50 PM [vite] hmr update /src/views/Cats.vue
This is enough for most cases, but there are some common settings that some of us used in configuration that we need to move to Vite. Let's take a look at some of these.
Migrating vue.config.js
Many of us using the Vue CLI use vue.config.js
to change some defaults for our own projects. The vue.config.js
file isn't actually about configuring Vue, but instead is about configuring the Vue CLI. In moving some of my own projects, I had to find alternatives in Vite for those options. Here are some of the most common ones.
Output Directory
When you're integrating Vue with other systems, you might want to change where you output the files. This was handled in vue.config.js
and you have to do the same in vite.config.js
. Again, this applies only to production builds, so you need to add the build section of the configuration:
export default defineConfig({
plugins: [vue()],
build: {
outDir: "../app/",
}
})
By specifying the outDir
, you can tell production builds where to resolve themselves. This path is relative to the project directory.
Source Maps
Including source maps for development is the default, but for production builds, you'll want to configure Rollup to enable them (again, in vite.config.js
):
export default defineConfig({
plugins: [vue()],
build: {
outDir: "../app/",
sourcemap: true,
}
})
This enables source maps to be emitted in production builds.
Hashing Output Files
If you're building Vue to be used in a PWA or a static site, the default hashing of the names is important, as it breaks the cache. If you're not familiar with what I mean, here's the default build output names:
dist/index.html 2.85kb
dist/assets/main.54a28106.css 0.17kb
dist/assets/main.680ac628.js 4.64kb
dist/assets/vendor.3d31d365.js 86.44kb
Notice the magic number inside the file names so that the files are refreshed when the hash changes on the Web server. If you're integrating with a back-end that handles cache handling on its own (e.g., ASP.NET Core), you'd like to specify where the files end up:
export default defineConfig({
plugins: [vue()],
build: {
sourcemap: true,
outDir: "../app/",
rollupOptions: {
output: {
entryFileNames: `[name].js`,
chunkFileNames: `[name].js`,
assetFileNames: `[name].[ext]`
}
},
}
})
In this case, you need to specify the rollupOptions
. Rollup is the library that actually performs the build (instead of webpack). The output option allows you to change the pattern of the file generation. These normally are assets/[name].[hash].js so just removing the name and optionally removing the subdirectory works great. This specifies the three different kinds of files to remove the hashes for.
Pages
One feature I use quite a bit in the Vue CLI's configuration is to build multiple projects from a single codebase. I normally do this with the pages
feature of vue.config.js
. For developing in Vite, you don't need anything special, as they're just separate html files. For example, if you have a separate Vue project, you just need a different starting point:
<!DOCTYPE html>
<html lang="en">
<head>
<.../>
</head>
<body>
<div id="app"></div>
</body>
<script type="module"
src="/src/dogs.js">
</script>
</html>
But that only works for development. Because Vite uses Rollup, you'll need to configure it to create the separate packages. This all happens in the vite.config.js
file:
import { resolve } from "path";
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
dogs: resolve(__dirname, 'dogs.html')
}
}
}
})
This allows you to add more than one input that resolves into separate build files. Note that the starting point is the .html
file, not the startup script. This is the nature of Rollup.
Using an .env File
If you're using an .env
file during development to specify environment variables for this project, you used to have to opt into it with a Vue CLI plug-in. In vue.config.js
, it's included by default.
Where Are We?
Vite represents a quick, easy-to-use environment that doesn't require long builds to get started with a project. Although it's not a perfect solution for every project, it can speed up development quite a bit, especially in large projects where its ability to just hot-swap whole modules can really help. Take a look at Vite and see if it fits into your Vue projects (or even React, Preact, or just plain JavaScript).