Features

At the very basic level, developing using Vite is not that much different from using a static file server. However, Vite provides many enhancements over native ESM imports to support

Hot Module Replacement

Vite provides an HMR API over native ESM. Frameworks with HMR capabilities can leverage the API to provide instant, precise updates without reloading the page or blowing away application state. Vite provides first-party HMR integrations for Vue Single File Components and React Fast Refresh. There are also official integrations for Preact via @prefresh/vite.

Note you don’t need to manually set these up - when you create an app via @vitejs/create-app, the selected templates would have these pre-configured for you already.

NPM Dependency Resolving

Native ES imports do not support bare module imports like the following:

  1. import { someMethod } from 'my-dep'

The above will throw an error in the browser. Vite detects such bare module imports in all served .js files and rewrites them to resolved paths like /node_modules/my-dep/dist/my-dep.js?v=1.0.0 so that the browser can handle them properly.

Dependency Caching

Resolved dependency requests are strongly cached with headers max-age=31536000,immutable to improve page reload performance during dev. Once cached, these requests will never hit the dev server again. They are auto invalidated by the appended version query if a different version is installed. If you made manual local edits to your dependencies, you can temporarily disable cache via your browser devtools and reload the page.

TypeScript

Vite supports importing .ts files out of the box.

Vite only performs transpilation on .ts files and does NOT perform type checking. It assumes type checking is taken care of by your IDE and build process (you can run tsc --noEmit in the build script).

Vite uses esbuild to transpile TypeScript into JavaScript which is about 20~30x faster than vanilla tsc, and HMR updates can reflect in the browser in under 50ms.

Note that because esbuild only performs transpilation without type information, it doesn’t support certain features like const enum and implicit type-only imports. You must set "isolatedModules": true in your tsconfig.json under compilerOptions so that TS will warn you against the features that do not work with isolated transpilation.

Client Types

Vite’s default types are for its Node.js API. To shim the environment of client side code in a Vite application, add vite/client to compilerOptions.types of your tsconfig:

  1. {
  2. "compilerOptions": {
  3. "types": ["vite/client"]
  4. }
  5. }

This will provide the following type shims:

  • Asset imports (e.g. importing an .svg file)
  • Types for the Vite-injected env variables on import.meta.env
  • Types for the HMR API on import.meta.hot

Vue

Vite provides first-class Vue support:

JSX

.jsx and .tsx files are also supported out of the box. JSX transpilation is also handled via ESBuild, and defaults to the React 16 flavor. React 17 style JSX support in ESBuild is tracked here.

Vue users should use the official @vitejs/plugin-vue-jsx plugin, which provides Vue 3 specific features including HMR, global component resolving, directives and slots.

If not using JSX with React or Vue, custom jsxFactory and jsxFragment can be configured using the esbuild option. For example for Preact:

  1. // vite.config.js
  2. export default {
  3. esbuild: {
  4. jsxFactory: 'h',
  5. jsxFragment: 'Fragment'
  6. }
  7. }

More details in ESBuild docs.

You can inject the JSX helpers using jsxInject (which is a Vite-only option) to avoid manual imports:

  1. // vite.config.js
  2. export default {
  3. esbuild: {
  4. jsxInject: `import React from 'react'`
  5. }
  6. }

CSS

Importing .css files will inject its content to the page via a <style> tag with HMR support. You can also retrieve the processed CSS as a string as the module’s default export.

PostCSS

If the project contains valid PostCSS config (any format supported by postcss-load-config, e.g. postcss.config.js), it will be automatically applied to all imported CSS.

CSS Modules

Any CSS file ending with .module.css is considered a CSS modules file. Importing such a file will return the corresponding module object:

  1. /* example.module.css */
  2. .red {
  3. color: red;
  4. }
  1. import classes from './example.module.css'
  2. document.getElementById('foo').className = classes.red

CSS modules behavior can be configured via the css.modules option.

CSS Pre-processors

Because Vite targets modern browsers only, it is recommended to use native CSS variables with PostCSS plugins that implement CSSWG drafts (e.g. postcss-nesting) and author plain, future-standards-compliant CSS.

That said, Vite does provide built-in support for .scss, .sass, .less, .styl and .stylus files. There is no need to install Vite-specific plugins for them, but the corresponding pre-processor itself must be installed as a peer dependency:

You can also use CSS modules combined with pre-processors by prepending .module to the file extension, for example style.module.scss.

Asset Handling

URL Imports

Importing a static asset will return the resolved public URL when it is served:

  1. import imgUrl from './img.png'
  2. document.getElementById('hero-img').src = imgUrl

The behavior is similar to webpack’s file-loader. The difference is that the import can be either using absolute public paths (based on project root during dev) or relative paths.

  • url() references in CSS are handled the same way.

  • If using the Vue plugin, asset references in Vue SFC templates are automatically converted into imports.

  • Common image, media, and font filetypes are detected as assets automatically. You can extend the internal list using the assetsInclude option.

  • Referenced assets are included as part of the build assets graph, will get hashed file names, and can be processed by plugins for optimization.

  • Assets smaller in bytes than the assetsInlineLimit option will be inlined as base64 data URLs.

The public Directory

If you have assets that are:

  • Never referenced in source code (e.g. robots.txt)
  • Must retain the exact same file name (without hashing)
  • …or you simply don’t want to have to import an asset first just to get its URL

Then you can place the asset in a special public directory under your project root. Assets in this directory will be served at root path / during dev, and copied to the root of the dist directory as-is.

Note that:

  • You should always reference public assets using root absolute path - for example, public/icon.png should be referenced in source code as /icon.png.
  • Assets in public cannot be imported from JavaScript.

JSON

JSON files can be directly imported - named imports are also supported:

  1. // import the entire object
  2. import json from './example.json'
  3. // import a root field as named exports - helps with treeshaking!
  4. import { field } from './example.json'

Glob Import

Requires ^2.0.0-beta.17

Vite supports importing multiple modules from the file system via the special import.meta.glob function:

  1. const modules = import.meta.glob('./dir/*.js')

The above will be transformed into the following:

  1. // code produced by vite
  2. const modules = {
  3. './dir/foo.js': () => import('./dir/foo.js'),
  4. './dir/bar.js': () => import('./dir/bar.js')
  5. }

You can then iterate over the keys of the modules object to access the corresponding modules:

  1. for (const path in modules) {
  2. modules[path]().then((mod) => {
  3. console.log(path, mod)
  4. })
  5. }

Matched files are by default lazy loaded via dynamic import and will be split into separate chunks during build. If you’d rather import all the modules directly (e.g. relying on side-effects in these modules to be applied first), you can use import.meta.globEager instead:

  1. const modules = import.meta.globEager('./dir/*.js')

The above will be transformed into the following:

  1. // code produced by vite
  2. import * as __glob__0_0 from './dir/foo.js'
  3. import * as __glob__0_1 from './dir/bar.js'
  4. const modules = {
  5. './dir/foo.js': __glob__0_0,
  6. './dir/bar.js': __glob__0_1
  7. }

Note that:

  • This is a Vite-only feature and is not a web or ES standard.
  • The glob patterns must be relative and start with ..
  • The glob matching is done via fast-glob - check out its documentation for supported glob patterns.

Web Assembly

Pre-compiled .wasm files can be directly imported - the default export will be an initialization function that returns a Promise of the exports object of the wasm instance:

  1. import init from './example.wasm'
  2. init().then((exports) => {
  3. exports.test()
  4. })

The init function can also take the imports object which is passed along to WebAssembly.instantiate as its second argument:

  1. init({
  2. imports: {
  3. someFunc: () => {
  4. /* ... */
  5. }
  6. }
  7. }).then(() => {
  8. /* ... */
  9. })

In the production build, .wasm files smaller than assetInlineLimit will be inlined as base64 strings. Otherwise, they will be copied to the dist directory as an asset and fetched on-demand.

Web Workers

A web worker script can be directly imported by appending ?worker to the import request. The default export will be a custom worker constructor:

  1. import MyWorker from './worker?worker'
  2. const worker = new MyWorker()

The worker script can also use import statements instead of importScripts() - note during dev this relies on browser native support and currently only works in Chrome, but for the production build it is compiled away.

By default, the worker script will be emitted as a separate chunk in the production build. If you wish to inline the worker as base64 strings, add the inline query:

  1. import MyWorker from './worker?worker&inline'