A note on Vite, a very fast dev build tool (I)
In this article we will present Vite's main features and givea some hints on how to "migrate" to Vite.


[Warning: this article may save you time and improve your developer front-end experience.]
Vite is the brand new development and build server proposed by Evan You, which is fast. Very fast. And it is framework-agnostic, so its scope is not reduced to Vue apps. Actually, it supports Vue.js, React (+ Preact) and Svelte.
Indeed, Vite takes advantage of the browser implementation of ES modules to serve modularized applications instead of using bundles.
Nb: Vite still serves bundles for production. Since production build is based on Rollup, which itself is based on ES modules, it can produce highly optimized builds.
Vite also supports HMR (Hot Module Reload) for the frameworks noted above. So this, combined with 'unbundled development', enables Vite to instantaneously update a running app with any code changes, and to free the front-end developer from the for the compilation wait.
Nb: Vite means 'fast' in French
So how does this technological wonder works (part II)? And, more important, can this be used from today (part I)?
In this first part, we will present Vite's main features and give some hints on how to "migrate" to Vite. In the second part, we go a bit deeper in Vite's logic and try to give an overview of how it works.
For a complementary demo of Vite, you can have a look on this excellent video by Tim Benniks.
I. [Reminders] Core concepts:
A. Bundled vs unbundled development:
You can find here propper definitions by @FredKSchott, which I summarize here:
- 'bundled development' is the dev build process that most of front-end developers use: the source code, which is not directly understandable by browsers, is compiled and bundled. The main reason for bundling is that browsers do not understand modules formats used in the source code (cf B.), so we have to concatenate everything into huge scripts. The pain point here is that, for any modification done in the source code, part or all of the application has to be rebuilt, which takes time.
- 'unbundled development' is the idea that, since we have now an official module system in JavaScript, everybody can understand each other and we can skip the bundling process. The interesting thing is that, on source code modification, only the modules affected by the modification are re-compile (if needed) and shipped to the browser. So there is no need to rebuild everything. And this takes few milliseconds.
B. Why bundled development?
[This is part is next to the subject, but I think it is a legitimate question] Why front-end developers have undergone that dev build process until now?
The first thing we can note is that, before supporting ES modules (so until late 2017 for Chrome, early 2020 for Edge), browsers did not implement any standard module system. So source code, which was modularized, had to be bundled for browsers.
Could have the community provided a browser-friendly module system then?
Probably, if there had not been a deeper problem: until ES6, there was no official module format in JavaScript. This, fortunately, did not prevent the community to use the module pattern, by creating its own module format. The thing is that not one format was defined, but numerous ones: CommonJS (CJS), Asynchronous Module Definition (AMD), Universal Module Definition (UMD). Plus liraries using Globals. Plus frameworks using their own module systems (Angular JS scope and dependency injection).
For node.js environment, CJS has been imposed from the beginning, so this problem is purely browser-related.
For more on the modules-in-JavaScript issue, you can have a look on this opinionated article
Why did not one 'win' over others? CJS was not browser-friendly. AMD was browser-friendly and had browser compatible loaders (require.js, curl.js) but dependency management was difficult to configure. I guess UMD came too late.
Therefore, it has been commonly agreed that the best solution was to use a bundler (namely Webpack) which could understand all module formats and compile them into a good old script.
D. Unbundled development:
With the implementation of ES modules, ES based servers appeared: es-dev-server, Snowpack, Vite.
So how Vite differs from SnowPack and es-dev-server (refs here and here)?
- on development, Vite supports HMR (which is also the case for Snowpack, but only since v.2), and has very 'fine-grained' HMR support for Vue apps
- on production, Vite uses Rollup, which, being based on ESM, can produce smaller bundles
II. Vite's features:
This part is partly inspired from this tweet from Evan You.
A. Generalities:
Vite is a development server based on ES modules and a production server based on the bundler Rollup (which itself relies on ES modules). Even though Vite has built-in support for Vue apps (this point is being discussed here), Vite is framework-agnostic and can support other frameworks with plugins. It currently has working plugins for React, Preact and Svelte. Also, VitePress, a static site generator based on Vue and Vite ("VuePress's brother") is currently under development.
In dev mode, Vite supports HMR (Hot Module Replacement) for the frameworks previously mentionned. Added to unbundled development, Vite can update instantaneously a running app (see Part II for details on how this works).
B. Code transformation:
Vite is able to compiles (on the fly, in dev mode) several kinds of files into ES modules:
- Vue's SFC
- resources such as CSS files or assets, which, like with Webpack, can be directly imported in JS code. About CSS, Vite supports CSS pre-processors and PostCSS
- TypeScript and JSX files (via esbuild, a blazing fast build tool)
Vite handles this via a series of Koa middlewares, which look a bit like Webpack loaders.
Vite is built with the Koa framework
C. Dependencies management:
This is an important point: excepted for dev dependencies, Vite looks for an ES distribution, or, when it is not available, prebundles the dependency into ESM.
By 'dev dependencies', I mean dependencies which are needed during development and that are not shipped to the browser (ESLint, Babel...). Dev dependencies are run by Vite, which is a node application, so they don't need transpilation into ESM.
see Part II for details on this
D. Customisation:
Vite is customisable by adding a vite.config.(j|t)s
file to the project. The file (ESM or CJS) has to export an object whose type is defined here.
In particular:
- to handle custom files transformation, plugins (koa middlewares) can be added via the config file (cf A). To do this, a transform function (
code => trasformedCode
) has to be added to the config file.
for a detailled tutorial on how to write a plugin, have a look on this article
- to add HMR support for other frameworks, an API is available, which can be added at the end of modules which accept HMR
Ex: here, in the React plugin
What is this
import.meta.hot
thing? This is a context created by Vite to expose the HMR API (see Part II).
- for production, custom Rollup plugins can be added to the config file
E. What Vite does not:
For now, what is out of Vite scope (ref):
- linting
- type-checking on development process (but it does it on build) (also see here some limitations on TypeScript)
- running tests
- formatting code
The idea is that these tasks can be handled by other tools (IDEs, test libraries...), and setting them in Vite would be redundant.
III. How can I 'migrate' my app to use Vite?
Nb: There is a package (and tutorials) on how to create an app with Vite, so this point has been skipped.
Nb2: At the time of writting, there is no documentation, official guideline neither tutorial on how to migrate an existing app to Vite. So, since the following part is completely experimental, any comment or feedback would be most welcome.
A. For any app:
Even though Vite is primarily designed to work with Vue, it is framework-agnostic, and can be 'customized' to other frameworks through plugins. But, plugins apart, there are some operations to perform which are common to any migration, and which are described bellow.
- One good thing to do is to add Vite:
yarn add --dev vite
- [if possible] Change non-dev dependencies distributions to ES modules, or, if possible, register them as dev dependencies (in package.json). Indeed, for each dependencies, Vite will either use an ES distribution (which is the best option), or, if none is available, pre-bundle it into ESM.
A consistent part of front-end NPM packages are still exported in CJS/AMD/UMD, and do not provide ES builds.
B. Vue app:
In the case of a Vue app, here are the next steps to do (in addition to the steps exposed in A.):
- Since Vite is only compatible with Vue 3, first thing is (if needed) to upgrade to Vue 3. For this, you can run the following Vue CLI plugin:
vue add vue-next
- [optional] you can remove CLI packages. But, if you are using some CLI features which are not supplied by Vite (see running tests below), you may want to keep this package:
yarn remove @vue/cli-service
- change the `scripts` in `package.json`:
"scripts": { - "serve": "vue-cli-service serve", - "build": "vue-cli-service build", + "dev": "vite", + "build": "vite build" },
- (this is conditioned with your entry point `index.html` - here we use the default entry point generated by the Vue create-app) add the import of the top-level module in the entry point, remove all that is related with Webpack:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> - <link rel="icon" href="<%= BASE_URL %>favicon.ico"> - <title><%= htmlWebpackPlugin.options.title %></title> + <link rel="icon" href="/favicon.ico"> + <title>My new Vite app!</title> </head> <body> <noscript> <strong>We're sorry but ....</strong> </noscript> <div id="app"></div> - <!-- built files will be auto injected --> + <script type="module" src="/src/main.js"></script> </body>
Running tests:
As we mentioned, Vite keeps by design testing out of it's scope. This is consistent (and compatible) with the new Vue 3 testing package, vue-test-utils-next
, which does not need any CLI plugin to run tests. The team is working on a documentation to migrate Vue 2 test utils.
C. React app:
- [important] Since React is distributed in CJS and UMD, and not in ESM, the React Vite plugin requires to install the packages @pika/react and @pika/react-dom.
@pika/react and @pika/react-dom are packages built by @FredKSchott (SnowPack) which automatically parse React builds to output them in ES modules. The repo checks daily for React updates and is always up-to-date.
"dependencies": { - "react-dom": "^16.13.1", - "react-dom": "^16.13.1", + "@pika/react": "^16.13.1", + "@pika/react-dom": "^16.13.1" },
- Add, at the root level, a Vite config file informing that Vite needs to use the React plugin (details here)
- Change the extension of files containing JSX from `(j|t).s` to `(j|t).sx`
- (this is conditioned with your entry point `index.html` - here we use the default entry point generated by the React create-app) add the import of the top-level module in the entry point:
<body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <script type="module" src="/src/index.jsx"></script> </body>
Running tests:
Since React testing utilities does not provide ESM distributions, make sure that all testing packages are registered as 'dev' dependencies (in package.json), otherwise Vite would throw an error.
Summary:
Vite is a development and production server based on ES modules.
In development, Vite relies on native ESM instead of bundles and compiles on the fly required files into ES module. This, combined with HMR support for Vue, React / Preact, Svelte, enables Vite to update, on source code change, a running application in browser instantaneously.
In production, Vite also offers a bundled build based on Rollup, which guarantees highly optimised bundles (thanks to ESM support).
So now that we have an overview of what can Vite do, we can have a look on how it works - I'll see you in part II.