.onrender.com/`.
By default, any new commit pushed to the specified branch will automatically trigger a new deployment. [Auto-Deploy](https://render.com/docs/deploys#toggling-auto-deploy-for-a-service) can be configured in the project settings.
You can also add a [custom domain](https://render.com/docs/custom-domains) to your project.
## Flightcontrol
Deploy your static site using [Flightcontrol](https://www.flightcontrol.dev/?ref=docs-vite) by following these [instructions](https://www.flightcontrol.dev/docs/reference/examples/vite?ref=docs-vite).
## Kinsta Static Site Hosting
Deploy your static site using [Kinsta](https://kinsta.com/static-site-hosting/) by following these [instructions](https://kinsta.com/docs/react-vite-example/).
## xmit Static Site Hosting
Deploy your static site using [xmit](https://xmit.co) by following this [guide](https://xmit.dev/posts/vite-quickstart/).
---
---
url: /guide/env-and-mode.md
---
# Env Variables and Modes
Vite exposes certain constants under the special `import.meta.env` object. These constants are defined as global variables during dev and statically replaced at build time to make tree-shaking effective.
## Built-in Constants
Some built-in constants are available in all cases:
* **`import.meta.env.MODE`**: {string} the [mode](#modes) the app is running in.
* **`import.meta.env.BASE_URL`**: {string} the base url the app is being served from. This is determined by the [`base` config option](/config/shared-options.md#base).
* **`import.meta.env.PROD`**: {boolean} whether the app is running in production (running the dev server with `NODE_ENV='production'` or running an app built with `NODE_ENV='production'`).
* **`import.meta.env.DEV`**: {boolean} whether the app is running in development (always the opposite of `import.meta.env.PROD`)
* **`import.meta.env.SSR`**: {boolean} whether the app is running in the [server](./ssr.md#conditional-logic).
## Env Variables
Vite exposes env variables under `import.meta.env` object as strings automatically.
To prevent accidentally leaking env variables to the client, only variables prefixed with `VITE_` are exposed to your Vite-processed code. e.g. for the following env variables:
```[.env]
VITE_SOME_KEY=123
DB_PASSWORD=foobar
```
Only `VITE_SOME_KEY` will be exposed as `import.meta.env.VITE_SOME_KEY` to your client source code, but `DB_PASSWORD` will not.
```js
console.log(import.meta.env.VITE_SOME_KEY) // "123"
console.log(import.meta.env.DB_PASSWORD) // undefined
```
If you want to customize the env variables prefix, see the [envPrefix](/config/shared-options.html#envprefix) option.
:::tip Env parsing
As shown above, `VITE_SOME_KEY` is a number but returns a string when parsed. The same would also happen for boolean env variables. Make sure to convert to the desired type when using it in your code.
:::
### `.env` Files
Vite uses [dotenv](https://github.com/motdotla/dotenv) to load additional environment variables from the following files in your [environment directory](/config/shared-options.md#envdir):
```
.env # loaded in all cases
.env.local # loaded in all cases, ignored by git
.env.[mode] # only loaded in specified mode
.env.[mode].local # only loaded in specified mode, ignored by git
```
:::tip Env Loading Priorities
An env file for a specific mode (e.g. `.env.production`) will take higher priority than a generic one (e.g. `.env`).
Vite will always load `.env` and `.env.local` in addition to the mode-specific `.env.[mode]` file. Variables declared in mode-specific files will take precedence over those in generic files, but variables defined only in `.env` or `.env.local` will still be available in the environment.
In addition, environment variables that already exist when Vite is executed have the highest priority and will not be overwritten by `.env` files. For example, when running `VITE_SOME_KEY=123 vite build`.
`.env` files are loaded at the start of Vite. Restart the server after making changes.
:::
Also, Vite uses [dotenv-expand](https://github.com/motdotla/dotenv-expand) to expand variables written in env files out of the box. To learn more about the syntax, check out [their docs](https://github.com/motdotla/dotenv-expand#what-rules-does-the-expansion-engine-follow).
Note that if you want to use `$` inside your environment value, you have to escape it with `\`.
```[.env]
KEY=123
NEW_KEY1=test$foo # test
NEW_KEY2=test\$foo # test$foo
NEW_KEY3=test$KEY # test123
```
:::warning SECURITY NOTES
* `.env.*.local` files are local-only and can contain sensitive variables. You should add `*.local` to your `.gitignore` to avoid them being checked into git.
* Since any variables exposed to your Vite source code will end up in your client bundle, `VITE_*` variables should *not* contain any sensitive information.
:::
::: details Expanding variables in reverse order
Vite supports expanding variables in reverse order.
For example, the `.env` below will be evaluated as `VITE_FOO=foobar`, `VITE_BAR=bar`.
```[.env]
VITE_FOO=foo${VITE_BAR}
VITE_BAR=bar
```
This does not work in shell scripts and other tools like `docker-compose`.
That said, Vite supports this behavior as this has been supported by `dotenv-expand` for a long time and other tools in JavaScript ecosystem uses older versions that supports this behavior.
To avoid interop issues, it is recommended to avoid relying on this behavior. Vite may start emitting warnings for this behavior in the future.
:::
## IntelliSense for TypeScript
By default, Vite provides type definitions for `import.meta.env` in [`vite/client.d.ts`](https://github.com/vitejs/vite/blob/main/packages/vite/client.d.ts). While you can define more custom env variables in `.env.[mode]` files, you may want to get TypeScript IntelliSense for user-defined env variables that are prefixed with `VITE_`.
To achieve this, you can create an `vite-env.d.ts` in `src` directory, then augment `ImportMetaEnv` like this:
```typescript [vite-env.d.ts]
///
interface ViteTypeOptions {
// By adding this line, you can make the type of ImportMetaEnv strict
// to disallow unknown keys.
// strictImportMetaEnv: unknown
}
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
// more env variables...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
```
If your code relies on types from browser environments such as [DOM](https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts) and [WebWorker](https://github.com/microsoft/TypeScript/blob/main/src/lib/webworker.generated.d.ts), you can update the [lib](https://www.typescriptlang.org/tsconfig#lib) field in `tsconfig.json`.
```json [tsconfig.json]
{
"lib": ["WebWorker"]
}
```
:::warning Imports will break type augmentation
If the `ImportMetaEnv` augmentation does not work, make sure you do not have any `import` statements in `vite-env.d.ts`. See the [TypeScript documentation](https://www.typescriptlang.org/docs/handbook/2/modules.html#how-javascript-modules-are-defined) for more information.
:::
## HTML Constant Replacement
Vite also supports replacing constants in HTML files. Any properties in `import.meta.env` can be used in HTML files with a special `%CONST_NAME%` syntax:
```html
Vite is running in %MODE%
Using data from %VITE_API_URL%
```
If the env doesn't exist in `import.meta.env`, e.g. `%NON_EXISTENT%`, it will be ignored and not replaced, unlike `import.meta.env.NON_EXISTENT` in JS where it's replaced as `undefined`.
Given that Vite is used by many frameworks, it is intentionally unopinionated about complex replacements like conditionals. Vite can be extended using [an existing userland plugin](https://github.com/vitejs/awesome-vite#transformers) or a custom plugin that implements the [`transformIndexHtml` hook](./api-plugin#transformindexhtml).
## Modes
By default, the dev server (`dev` command) runs in `development` mode and the `build` command runs in `production` mode.
This means when running `vite build`, it will load the env variables from `.env.production` if there is one:
```[.env.production]
VITE_APP_TITLE=My App
```
In your app, you can render the title using `import.meta.env.VITE_APP_TITLE`.
In some cases, you may want to run `vite build` with a different mode to render a different title. You can overwrite the default mode used for a command by passing the `--mode` option flag. For example, if you want to build your app for a staging mode:
```bash
vite build --mode staging
```
And create a `.env.staging` file:
```[.env.staging]
VITE_APP_TITLE=My App (staging)
```
As `vite build` runs a production build by default, you can also change this and run a development build by using a different mode and `.env` file configuration:
```[.env.testing]
NODE_ENV=development
```
### NODE\_ENV and Modes
It's important to note that `NODE_ENV` (`process.env.NODE_ENV`) and modes are two different concepts. Here's how different commands affect the `NODE_ENV` and mode:
| Command | NODE\_ENV | Mode |
| ---------------------------------------------------- | --------------- | --------------- |
| `vite build` | `"production"` | `"production"` |
| `vite build --mode development` | `"production"` | `"development"` |
| `NODE_ENV=development vite build` | `"development"` | `"production"` |
| `NODE_ENV=development vite build --mode development` | `"development"` | `"development"` |
The different values of `NODE_ENV` and mode also reflect on its corresponding `import.meta.env` properties:
| Command | `import.meta.env.PROD` | `import.meta.env.DEV` |
| ---------------------- | ---------------------- | --------------------- |
| `NODE_ENV=production` | `true` | `false` |
| `NODE_ENV=development` | `false` | `true` |
| `NODE_ENV=other` | `false` | `true` |
| Command | `import.meta.env.MODE` |
| -------------------- | ---------------------- |
| `--mode production` | `"production"` |
| `--mode development` | `"development"` |
| `--mode staging` | `"staging"` |
:::tip `NODE_ENV` in `.env` files
`NODE_ENV=...` can be set in the command, and also in your `.env` file. If `NODE_ENV` is specified in a `.env.[mode]` file, the mode can be used to control its value. However, both `NODE_ENV` and modes remain as two different concepts.
The main benefit with `NODE_ENV=...` in the command is that it allows Vite to detect the value early. It also allows you to read `process.env.NODE_ENV` in your Vite config as Vite can only load the env files once the config is evaluated.
:::
---
---
url: /guide/api-environment.md
---
# Environment API
:::warning Experimental
Environment API is experimental. We'll keep the APIs stable during Vite 6 to let the ecosystem experiment and build on top of it. We're planning to stabilize these new APIs with potential breaking changes in Vite 7.
Resources:
* [Feedback discussion](https://github.com/vitejs/vite/discussions/16358) where we are gathering feedback about the new APIs.
* [Environment API PR](https://github.com/vitejs/vite/pull/16471) where the new API were implemented and reviewed.
Please share your feedback with us.
:::
## Formalizing Environments
Vite 6 formalizes the concept of Environments. Until Vite 5, there were two implicit Environments (`client`, and optionally `ssr`). The new Environment API allows users and framework authors to create as many environments as needed to map the way their apps work in production. This new capability required a big internal refactoring, but a lot of effort has been placed on backward compatibility. The initial goal of Vite 6 is to move the ecosystem to the new major as smoothly as possible, delaying the adoption of these new experimental APIs until enough users have migrated and frameworks and plugin authors have validated the new design.
## Closing the Gap Between Build and Dev
For a simple SPA/MPA, no new APIs around environments are exposed to the config. Internally, Vite will apply the options to a `client` environment, but it's not necessary to know of this concept when configuring Vite. The config and behavior from Vite 5 should work seamlessly here.
When we move to a typical server-side rendered (SSR) app, we'll have two environments:
* `client`: runs the app in the browser.
* `server`: runs the app in node (or other server runtimes) which renders pages before sending them to the browser.
In dev, Vite executes the server code in the same Node process as the Vite dev server, giving a close approximation to the production environment. However, it is also possible for servers to run in other JS runtimes, like [Cloudflare's workerd](https://github.com/cloudflare/workerd) which have different constraints. Modern apps may also run in more than two environments, e.g. a browser, a node server, and an edge server. Vite 5 didn't allow to properly represent these environments.
Vite 6 allows users to configure their app during build and dev to map all of its environments. During dev, a single Vite dev server can now be used to run code in multiple different environments concurrently. The app source code is still transformed by Vite dev server. On top of the shared HTTP server, middlewares, resolved config, and plugins pipeline, the Vite dev server now has a set of independent dev environments. Each of them is configured to match the production environment as closely as possible, and is connected to a dev runtime where the code is executed (for workerd, the server code can now run in miniflare locally). In the client, the browser imports and executes the code. In other environments, a module runner fetches and evaluates the transformed code.

## Environments Configuration
For an SPA/MPA, the configuration will look similar to Vite 5. Internally these options are used to configure the `client` environment.
```js
export default defineConfig({
build: {
sourcemap: false,
},
optimizeDeps: {
include: ['lib'],
},
})
```
This is important because we'd like to keep Vite approachable and avoid exposing new concepts until they are needed.
If the app is composed of several environments, then these environments can be configured explicitly with the `environments` config option.
```js
export default {
build: {
sourcemap: false,
},
optimizeDeps: {
include: ['lib'],
},
environments: {
server: {},
edge: {
resolve: {
noExternal: true,
},
},
},
}
```
When not explicitly documented, environment inherits the configured top-level config options (for example, the new `server` and `edge` environments will inherit the `build.sourcemap: false` option). A small number of top-level options, like `optimizeDeps`, only apply to the `client` environment, as they don't work well when applied as a default to server environments. The `client` environment can also be configured explicitly through `environments.client`, but we recommend to do it with the top-level options so the client config remains unchanged when adding new environments.
The `EnvironmentOptions` interface exposes all the per-environment options. There are environment options that apply to both `build` and `dev`, like `resolve`. And there are `DevEnvironmentOptions` and `BuildEnvironmentOptions` for dev and build specific options (like `dev.warmup` or `build.outDir`). Some options like `optimizeDeps` only applies to dev, but is kept as top level instead of nested in `dev` for backward compatibility.
```ts
interface EnvironmentOptions {
define?: Record
resolve?: EnvironmentResolveOptions
optimizeDeps: DepOptimizationOptions
consumer?: 'client' | 'server'
dev: DevOptions
build: BuildOptions
}
```
The `UserConfig` interface extends from the `EnvironmentOptions` interface, allowing to configure the client and defaults for other environments, configured through the `environments` option. The `client` and a server environment named `ssr` are always present during dev. This allows backward compatibility with `server.ssrLoadModule(url)` and `server.moduleGraph`. During build, the `client` environment is always present, and the `ssr` environment is only present if it is explicitly configured (using `environments.ssr` or for backward compatibility `build.ssr`). An app doesn't need to use the `ssr` name for its SSR environment, it could name it `server` for example.
```ts
interface UserConfig extends EnvironmentOptions {
environments: Record
// other options
}
```
Note that the `ssr` top-level property is going to be deprecated once the Environment API is stable. This option has the same role as `environments`, but for the default `ssr` environment and only allowed configuring of a small set of options.
## Custom Environment Instances
Low level configuration APIs are available so runtime providers can provide environments with proper defaults for their runtimes. These environments can also spawn other processes or threads to run the modules during dev in a closer runtime to the production environment.
```js
import { customEnvironment } from 'vite-environment-provider'
export default {
build: {
outDir: '/dist/client',
},
environments: {
ssr: customEnvironment({
build: {
outDir: '/dist/ssr',
},
}),
},
}
```
## Backward Compatibility
The current Vite server API are not yet deprecated and are backward compatible with Vite 5. The new Environment API is experimental.
The `server.moduleGraph` returns a mixed view of the client and ssr module graphs. Backward compatible mixed module nodes will be returned from all its methods. The same scheme is used for the module nodes passed to `handleHotUpdate`.
We don't recommend switching to Environment API yet. We are aiming for a good portion of the user base to adopt Vite 6 before so plugins don't need to maintain two versions. Checkout the future breaking changes section for information on future deprecations and upgrade path:
* [`this.environment` in Hooks](/changes/this-environment-in-hooks)
* [HMR `hotUpdate` Plugin Hook](/changes/hotupdate-hook)
* [Move to per-environment APIs](/changes/per-environment-apis)
* [SSR using `ModuleRunner` API](/changes/ssr-using-modulerunner)
* [Shared plugins during build](/changes/shared-plugins-during-build)
## Target Users
This guide provides the basic concepts about environments for end users.
Plugin authors have a more consistent API available to interact with the current environment configuration. If you're building on top of Vite, the [Environment API Plugins Guide](./api-environment-plugins.md) guide describes the way extended plugin APIs available to support multiple custom environments.
Frameworks could decide to expose environments at different levels. If you're a framework author, continue reading the [Environment API Frameworks Guide](./api-environment-frameworks) to learn about the Environment API programmatic side.
For Runtime providers, the [Environment API Runtimes Guide](./api-environment-runtimes.md) explains how to offer custom environment to be consumed by frameworks and users.
---
---
url: /guide/api-environment-frameworks.md
---
# Environment API for Frameworks
:::warning Experimental
Environment API is experimental. We'll keep the APIs stable during Vite 6 to let the ecosystem experiment and build on top of it. We're planning to stabilize these new APIs with potential breaking changes in Vite 7.
Resources:
* [Feedback discussion](https://github.com/vitejs/vite/discussions/16358) where we are gathering feedback about the new APIs.
* [Environment API PR](https://github.com/vitejs/vite/pull/16471) where the new API were implemented and reviewed.
Please share your feedback with us.
:::
## Environments and frameworks
The implicit `ssr` environment and other non-client environments use a `RunnableDevEnvironment` by default during dev. While this requires the runtime to be the same with the one the Vite server is running in, this works similarly with `ssrLoadModule` and allows frameworks to migrate and enable HMR for their SSR dev story. You can guard any runnable environment with an `isRunnableDevEnvironment` function.
```ts
export class RunnableDevEnvironment extends DevEnvironment {
public readonly runner: ModuleRunner
}
class ModuleRunner {
/**
* URL to execute.
* Accepts file path, server path, or id relative to the root.
* Returns an instantiated module (same as in ssrLoadModule)
*/
public async import(url: string): Promise>
/**
* Other ModuleRunner methods...
*/
}
if (isRunnableDevEnvironment(server.environments.ssr)) {
await server.environments.ssr.runner.import('/entry-point.js')
}
```
:::warning
The `runner` is evaluated lazily only when it's accessed for the first time. Beware that Vite enables source map support when the `runner` is created by calling `process.setSourceMapsEnabled` or by overriding `Error.prepareStackTrace` if it's not available.
:::
Frameworks that communicate with their runtime via the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) can utilize the `FetchableDevEnvironment` that provides a standardized way of handling requests via the `handleRequest` method:
```ts
import {
createServer,
createFetchableDevEnvironment,
isFetchableDevEnvironment,
} from 'vite'
const server = await createServer({
server: { middlewareMode: true },
appType: 'custom',
environments: {
custom: {
dev: {
createEnvironment(name, config) {
return createFetchableDevEnvironment(name, config, {
handleRequest(request: Request): Promise | Response {
// handle Request and return a Response
},
})
},
},
},
},
})
// Any consumer of the environment API can now call `dispatchFetch`
if (isFetchableDevEnvironment(server.environments.custom)) {
const response: Response = await server.environments.custom.dispatchFetch(
new Request('/request-to-handle'),
)
}
```
:::warning
Vite validates the input and output of the `dispatchFetch` method: the request must be an instance of the global `Request` class and the response must be the instance of the global `Response` class. Vite will throw a `TypeError` if this is not the case.
Note that although the `FetchableDevEnvironment` is implemented as a class, it is considered an implementation detail by the Vite team and might change at any moment.
:::
## Default `RunnableDevEnvironment`
Given a Vite server configured in middleware mode as described by the [SSR setup guide](/guide/ssr#setting-up-the-dev-server), let's implement the SSR middleware using the environment API. Error handling is omitted.
```js
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { createServer } from 'vite'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const server = await createServer({
server: { middlewareMode: true },
appType: 'custom',
environments: {
server: {
// by default, modules are run in the same process as the vite server
},
},
})
// You might need to cast this to RunnableDevEnvironment in TypeScript or
// use isRunnableDevEnvironment to guard the access to the runner
const environment = server.environments.node
app.use('*', async (req, res, next) => {
const url = req.originalUrl
// 1. Read index.html
const indexHtmlPath = path.resolve(__dirname, 'index.html')
let template = fs.readFileSync(indexHtmlPath, 'utf-8')
// 2. Apply Vite HTML transforms. This injects the Vite HMR client,
// and also applies HTML transforms from Vite plugins, e.g. global
// preambles from @vitejs/plugin-react
template = await server.transformIndexHtml(url, template)
// 3. Load the server entry. import(url) automatically transforms
// ESM source code to be usable in Node.js! There is no bundling
// required, and provides full HMR support.
const { render } = await environment.runner.import('/src/entry-server.js')
// 4. render the app HTML. This assumes entry-server.js's exported
// `render` function calls appropriate framework SSR APIs,
// e.g. ReactDOMServer.renderToString()
const appHtml = await render(url)
// 5. Inject the app-rendered HTML into the template.
const html = template.replace(``, appHtml)
// 6. Send the rendered HTML back.
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
})
```
## Runtime Agnostic SSR
Since the `RunnableDevEnvironment` can only be used to run the code in the same runtime as the Vite server, it requires a runtime that can run the Vite Server (a runtime that is compatible with Node.js). This means that you will need to use the raw `DevEnvironment` to make it runtime agnostic.
:::info `FetchableDevEnvironment` proposal
The initial proposal had a `run` method on the `DevEnvironment` class that would allow consumers to invoke an import on the runner side by using the `transport` option. During our testing we found out that the API was not universal enough to start recommending it. At the moment, we are looking for feedback on [the `FetchableDevEnvironment` proposal](https://github.com/vitejs/vite/discussions/18191).
:::
`RunnableDevEnvironment` has a `runner.import` function that returns the value of the module. But this function is not available in the raw `DevEnvironment` and requires the code using the Vite's APIs and the user modules to be decoupled.
For example, the following example uses the value of the user module from the code using the Vite's APIs:
```ts
// code using the Vite's APIs
import { createServer } from 'vite'
const server = createServer()
const ssrEnvironment = server.environment.ssr
const input = {}
const { createHandler } = await ssrEnvironment.runner.import('./entrypoint.js')
const handler = createHandler(input)
const response = handler(new Request('/'))
// -------------------------------------
// ./entrypoint.js
export function createHandler(input) {
return function handler(req) {
return new Response('hello')
}
}
```
If your code can run in the same runtime as the user modules (i.e., it does not rely on Node.js-specific APIs), you can use a virtual module. This approach eliminates the need to access the value from the code using Vite's APIs.
```ts
// code using the Vite's APIs
import { createServer } from 'vite'
const server = createServer({
plugins: [
// a plugin that handles `virtual:entrypoint`
{
name: 'virtual-module',
/* plugin implementation */
},
],
})
const ssrEnvironment = server.environment.ssr
const input = {}
// use exposed functions by each environment factories that runs the code
// check for each environment factories what they provide
if (ssrEnvironment instanceof RunnableDevEnvironment) {
ssrEnvironment.runner.import('virtual:entrypoint')
} else if (ssrEnvironment instanceof CustomDevEnvironment) {
ssrEnvironment.runEntrypoint('virtual:entrypoint')
} else {
throw new Error(`Unsupported runtime for ${ssrEnvironment.name}`)
}
// -------------------------------------
// virtual:entrypoint
const { createHandler } = await import('./entrypoint.js')
const handler = createHandler(input)
const response = handler(new Request('/'))
// -------------------------------------
// ./entrypoint.js
export function createHandler(input) {
return function handler(req) {
return new Response('hello')
}
}
```
For example, to call `transformIndexHtml` on the user module, the following plugin can be used:
```ts {13-21}
function vitePluginVirtualIndexHtml(): Plugin {
let server: ViteDevServer | undefined
return {
name: vitePluginVirtualIndexHtml.name,
configureServer(server_) {
server = server_
},
resolveId(source) {
return source === 'virtual:index-html' ? '\0' + source : undefined
},
async load(id) {
if (id === '\0' + 'virtual:index-html') {
let html: string
if (server) {
this.addWatchFile('index.html')
html = fs.readFileSync('index.html', 'utf-8')
html = await server.transformIndexHtml('/', html)
} else {
html = fs.readFileSync('dist/client/index.html', 'utf-8')
}
return `export default ${JSON.stringify(html)}`
}
return
},
}
}
```
If your code requires Node.js APIs, you can use `hot.send` to communicate with the code that uses Vite's APIs from the user modules. However, be aware that this approach may not work the same way after the build process.
```ts
// code using the Vite's APIs
import { createServer } from 'vite'
const server = createServer({
plugins: [
// a plugin that handles `virtual:entrypoint`
{
name: 'virtual-module',
/* plugin implementation */
},
],
})
const ssrEnvironment = server.environment.ssr
const input = {}
// use exposed functions by each environment factories that runs the code
// check for each environment factories what they provide
if (ssrEnvironment instanceof RunnableDevEnvironment) {
ssrEnvironment.runner.import('virtual:entrypoint')
} else if (ssrEnvironment instanceof CustomDevEnvironment) {
ssrEnvironment.runEntrypoint('virtual:entrypoint')
} else {
throw new Error(`Unsupported runtime for ${ssrEnvironment.name}`)
}
const req = new Request('/')
const uniqueId = 'a-unique-id'
ssrEnvironment.send('request', serialize({ req, uniqueId }))
const response = await new Promise((resolve) => {
ssrEnvironment.on('response', (data) => {
data = deserialize(data)
if (data.uniqueId === uniqueId) {
resolve(data.res)
}
})
})
// -------------------------------------
// virtual:entrypoint
const { createHandler } = await import('./entrypoint.js')
const handler = createHandler(input)
import.meta.hot.on('request', (data) => {
const { req, uniqueId } = deserialize(data)
const res = handler(req)
import.meta.hot.send('response', serialize({ res: res, uniqueId }))
})
const response = handler(new Request('/'))
// -------------------------------------
// ./entrypoint.js
export function createHandler(input) {
return function handler(req) {
return new Response('hello')
}
}
```
## Environments During Build
In the CLI, calling `vite build` and `vite build --ssr` will still build the client only and ssr only environments for backward compatibility.
When `builder` is not `undefined` (or when calling `vite build --app`), `vite build` will opt-in into building the entire app instead. This would later on become the default in a future major. A `ViteBuilder` instance will be created (build-time equivalent to a `ViteDevServer`) to build all configured environments for production. By default the build of environments is run in series respecting the order of the `environments` record. A framework or user can further configure how the environments are built using:
```js
export default {
builder: {
buildApp: async (builder) => {
const environments = Object.values(builder.environments)
return Promise.all(
environments.map((environment) => builder.build(environment)),
)
},
},
}
```
## Environment Agnostic Code
Most of the time, the current `environment` instance will be available as part of the context of the code being run so the need to access them through `server.environments` should be rare. For example, inside plugin hooks the environment is exposed as part of the `PluginContext`, so it can be accessed using `this.environment`. See [Environment API for Plugins](./api-environment-plugins.md) to learn about how to build environment aware plugins.
---
---
url: /guide/api-environment-plugins.md
---
# Environment API for Plugins
:::warning Experimental
Environment API is experimental. We'll keep the APIs stable during Vite 6 to let the ecosystem experiment and build on top of it. We're planning to stabilize these new APIs with potential breaking changes in Vite 7.
Resources:
* [Feedback discussion](https://github.com/vitejs/vite/discussions/16358) where we are gathering feedback about the new APIs.
* [Environment API PR](https://github.com/vitejs/vite/pull/16471) where the new API were implemented and reviewed.
Please share your feedback with us.
:::
## Accessing the Current Environment in Hooks
Given that there were only two Environments until Vite 6 (`client` and `ssr`), a `ssr` boolean was enough to identify the current environment in Vite APIs. Plugin Hooks received a `ssr` boolean in the last options parameter, and several APIs expected an optional last `ssr` parameter to properly associate modules to the correct environment (for example `server.moduleGraph.getModuleByUrl(url, { ssr })`).
With the advent of configurable environments, we now have a uniform way to access their options and instance in plugins. Plugin hooks now expose `this.environment` in their context, and APIs that previously expected a `ssr` boolean are now scoped to the proper environment (for example `environment.moduleGraph.getModuleByUrl(url)`).
The Vite server has a shared plugin pipeline, but when a module is processed it is always done in the context of a given environment. The `environment` instance is available in the plugin context.
A plugin could use the `environment` instance to change how a module is processed depending on the configuration for the environment (which can be accessed using `environment.config`).
```ts
transform(code, id) {
console.log(this.environment.config.resolve.conditions)
}
```
## Registering New Environments Using Hooks
Plugins can add new environments in the `config` hook (for example to have a separate module graph for [RSC](https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components)):
```ts
config(config: UserConfig) {
config.environments.rsc ??= {}
}
```
An empty object is enough to register the environment, default values from the root level environment config.
## Configuring Environment Using Hooks
While the `config` hook is running, the complete list of environments isn't yet known and the environments can be affected by both the default values from the root level environment config or explicitly through the `config.environments` record.
Plugins should set default values using the `config` hook. To configure each environment, they can use the new `configEnvironment` hook. This hook is called for each environment with its partially resolved config including resolution of final defaults.
```ts
configEnvironment(name: string, options: EnvironmentOptions) {
if (name === 'rsc') {
options.resolve.conditions = // ...
```
## The `hotUpdate` Hook
* **Type:** `(this: { environment: DevEnvironment }, options: HotUpdateOptions) => Array | void | Promise | void>`
* **See also:** [HMR API](./api-hmr)
The `hotUpdate` hook allows plugins to perform custom HMR update handling for a given environment. When a file changes, the HMR algorithm is run for each environment in series according to the order in `server.environments`, so the `hotUpdate` hook will be called multiple times. The hook receives a context object with the following signature:
```ts
interface HotUpdateOptions {
type: 'create' | 'update' | 'delete'
file: string
timestamp: number
modules: Array
read: () => string | Promise
server: ViteDevServer
}
```
* `this.environment` is the module execution environment where a file update is currently being processed.
* `modules` is an array of modules in this environment that are affected by the changed file. It's an array because a single file may map to multiple served modules (e.g. Vue SFCs).
* `read` is an async read function that returns the content of the file. This is provided because, on some systems, the file change callback may fire too fast before the editor finishes updating the file, and direct `fs.readFile` will return empty content. The read function passed in normalizes this behavior.
The hook can choose to:
* Filter and narrow down the affected module list so that the HMR is more accurate.
* Return an empty array and perform a full reload:
```js
hotUpdate({ modules, timestamp }) {
if (this.environment.name !== 'client')
return
// Invalidate modules manually
const invalidatedModules = new Set()
for (const mod of modules) {
this.environment.moduleGraph.invalidateModule(
mod,
invalidatedModules,
timestamp,
true
)
}
this.environment.hot.send({ type: 'full-reload' })
return []
}
```
* Return an empty array and perform complete custom HMR handling by sending custom events to the client:
```js
hotUpdate() {
if (this.environment.name !== 'client')
return
this.environment.hot.send({
type: 'custom',
event: 'special-update',
data: {}
})
return []
}
```
Client code should register the corresponding handler using the [HMR API](./api-hmr) (this could be injected by the same plugin's `transform` hook):
```js
if (import.meta.hot) {
import.meta.hot.on('special-update', (data) => {
// perform custom update
})
}
```
## Per-environment Plugins
A plugin can define what are the environments it should apply to with the `applyToEnvironment` function.
```js
const UnoCssPlugin = () => {
// shared global state
return {
buildStart() {
// init per-environment state with WeakMap
// using this.environment
},
configureServer() {
// use global hooks normally
},
applyToEnvironment(environment) {
// return true if this plugin should be active in this environment,
// or return a new plugin to replace it.
// if the hook is not used, the plugin is active in all environments
},
resolveId(id, importer) {
// only called for environments this plugin apply to
},
}
}
```
If a plugin isn't environment aware and has state that isn't keyed on the current environment, the `applyToEnvironment` hook allows to easily make it per-environment.
```js
import { nonShareablePlugin } from 'non-shareable-plugin'
export default defineConfig({
plugins: [
{
name: 'per-environment-plugin',
applyToEnvironment(environment) {
return nonShareablePlugin({ outputName: environment.name })
},
},
],
})
```
Vite exports a `perEnvironmentPlugin` helper to simplify these cases where no other hooks are required:
```js
import { nonShareablePlugin } from 'non-shareable-plugin'
export default defineConfig({
plugins: [
perEnvironmentPlugin('per-environment-plugin', (environment) =>
nonShareablePlugin({ outputName: environment.name }),
),
],
})
```
## Environment in Build Hooks
In the same way as during dev, plugin hooks also receive the environment instance during build, replacing the `ssr` boolean.
This also works for `renderChunk`, `generateBundle`, and other build only hooks.
## Shared Plugins During Build
Before Vite 6, the plugins pipelines worked in a different way during dev and build:
* **During dev:** plugins are shared
* **During Build:** plugins are isolated for each environment (in different processes: `vite build` then `vite build --ssr`).
This forced frameworks to share state between the `client` build and the `ssr` build through manifest files written to the file system. In Vite 6, we are now building all environments in a single process so the way the plugins pipeline and inter-environment communication can be aligned with dev.
In a future major (Vite 7 or 8), we aim to have complete alignment:
* **During both dev and build:** plugins are shared, with [per-environment filtering](#per-environment-plugins)
There will also be a single `ResolvedConfig` instance shared during build, allowing for caching at entire app build process level in the same way as we have been doing with `WeakMap` during dev.
For Vite 6, we need to do a smaller step to keep backward compatibility. Ecosystem plugins are currently using `config.build` instead of `environment.config.build` to access configuration, so we need to create a new `ResolvedConfig` per-environment by default. A project can opt-in into sharing the full config and plugins pipeline setting `builder.sharedConfigBuild` to `true`.
This option would only work of a small subset of projects at first, so plugin authors can opt-in for a particular plugin to be shared by setting the `sharedDuringBuild` flag to `true`. This allows for easily sharing state both for regular plugins:
```js
function myPlugin() {
// Share state among all environments in dev and build
const sharedState = ...
return {
name: 'shared-plugin',
transform(code, id) { ... },
// Opt-in into a single instance for all environments
sharedDuringBuild: true,
}
}
```
---
---
url: /guide/api-environment-runtimes.md
---
# Environment API for Runtimes
:::warning Experimental
Environment API is experimental. We'll keep the APIs stable during Vite 6 to let the ecosystem experiment and build on top of it. We're planning to stabilize these new APIs with potential breaking changes in Vite 7.
Resources:
* [Feedback discussion](https://github.com/vitejs/vite/discussions/16358) where we are gathering feedback about the new APIs.
* [Environment API PR](https://github.com/vitejs/vite/pull/16471) where the new API were implemented and reviewed.
Please share your feedback with us.
:::
## Environment Factories
Environments factories are intended to be implemented by Environment providers like Cloudflare, and not by end users. Environment factories return a `EnvironmentOptions` for the most common case of using the target runtime for both dev and build environments. The default environment options can also be set so the user doesn't need to do it.
```ts
function createWorkerdEnvironment(
userConfig: EnvironmentOptions,
): EnvironmentOptions {
return mergeConfig(
{
resolve: {
conditions: [
/*...*/
],
},
dev: {
createEnvironment(name, config) {
return createWorkerdDevEnvironment(name, config, {
hot: true,
transport: customHotChannel(),
})
},
},
build: {
createEnvironment(name, config) {
return createWorkerdBuildEnvironment(name, config)
},
},
},
userConfig,
)
}
```
Then the config file can be written as:
```js
import { createWorkerdEnvironment } from 'vite-environment-workerd'
export default {
environments: {
ssr: createWorkerdEnvironment({
build: {
outDir: '/dist/ssr',
},
}),
rsc: createWorkerdEnvironment({
build: {
outDir: '/dist/rsc',
},
}),
},
}
```
and frameworks can use an environment with the workerd runtime to do SSR using:
```js
const ssrEnvironment = server.environments.ssr
```
## Creating a New Environment Factory
A Vite dev server exposes two environments by default: a `client` environment and an `ssr` environment. The client environment is a browser environment by default, and the module runner is implemented by importing the virtual module `/@vite/client` to client apps. The SSR environment runs in the same Node runtime as the Vite server by default and allows application servers to be used to render requests during dev with full HMR support.
The transformed source code is called a module, and the relationships between the modules processed in each environment are kept in a module graph. The transformed code for these modules is sent to the runtimes associated with each environment to be executed. When a module is evaluated in the runtime, its imported modules will be requested triggering the processing of a section of the module graph.
A Vite Module Runner allows running any code by processing it with Vite plugins first. It is different from `server.ssrLoadModule` because the runner implementation is decoupled from the server. This allows library and framework authors to implement their layer of communication between the Vite server and the runner. The browser communicates with its corresponding environment using the server Web Socket and through HTTP requests. The Node Module runner can directly do function calls to process modules as it is running in the same process. Other environments could run modules connecting to a JS runtime like workerd, or a Worker Thread as Vitest does.
One of the goals of this feature is to provide a customizable API to process and run code. Users can create new environment factories using the exposed primitives.
```ts
import { DevEnvironment, HotChannel } from 'vite'
function createWorkerdDevEnvironment(
name: string,
config: ResolvedConfig,
context: DevEnvironmentContext
) {
const connection = /* ... */
const transport: HotChannel = {
on: (listener) => { connection.on('message', listener) },
send: (data) => connection.send(data),
}
const workerdDevEnvironment = new DevEnvironment(name, config, {
options: {
resolve: { conditions: ['custom'] },
...context.options,
},
hot: true,
transport,
})
return workerdDevEnvironment
}
```
## `ModuleRunner`
A module runner is instantiated in the target runtime. All APIs in the next section are imported from `vite/module-runner` unless stated otherwise. This export entry point is kept as lightweight as possible, only exporting the minimal needed to create module runners.
**Type Signature:**
```ts
export class ModuleRunner {
constructor(
public options: ModuleRunnerOptions,
public evaluator: ModuleEvaluator = new ESModulesEvaluator(),
private debug?: ModuleRunnerDebugger,
) {}
/**
* URL to execute.
* Accepts file path, server path, or id relative to the root.
*/
public async import(url: string): Promise
/**
* Clear all caches including HMR listeners.
*/
public clearCache(): void
/**
* Clear all caches, remove all HMR listeners, reset sourcemap support.
* This method doesn't stop the HMR connection.
*/
public async close(): Promise
/**
* Returns `true` if the runner has been closed by calling `close()`.
*/
public isClosed(): boolean
}
```
The module evaluator in `ModuleRunner` is responsible for executing the code. Vite exports `ESModulesEvaluator` out of the box, it uses `new AsyncFunction` to evaluate the code. You can provide your own implementation if your JavaScript runtime doesn't support unsafe evaluation.
Module runner exposes `import` method. When Vite server triggers `full-reload` HMR event, all affected modules will be re-executed. Be aware that Module Runner doesn't update `exports` object when this happens (it overrides it), you would need to run `import` or get the module from `evaluatedModules` again if you rely on having the latest `exports` object.
**Example Usage:**
```js
import { ModuleRunner, ESModulesEvaluator } from 'vite/module-runner'
import { transport } from './rpc-implementation.js'
const moduleRunner = new ModuleRunner(
{
transport,
},
new ESModulesEvaluator(),
)
await moduleRunner.import('/src/entry-point.js')
```
## `ModuleRunnerOptions`
```ts twoslash
import type {
InterceptorOptions as InterceptorOptionsRaw,
ModuleRunnerHmr as ModuleRunnerHmrRaw,
EvaluatedModules,
} from 'vite/module-runner'
import type { Debug } from '@type-challenges/utils'
type InterceptorOptions = Debug
type ModuleRunnerHmr = Debug
/** see below */
type ModuleRunnerTransport = unknown
// ---cut---
interface ModuleRunnerOptions {
/**
* A set of methods to communicate with the server.
*/
transport: ModuleRunnerTransport
/**
* Configure how source maps are resolved.
* Prefers `node` if `process.setSourceMapsEnabled` is available.
* Otherwise it will use `prepareStackTrace` by default which overrides
* `Error.prepareStackTrace` method.
* You can provide an object to configure how file contents and
* source maps are resolved for files that were not processed by Vite.
*/
sourcemapInterceptor?:
| false
| 'node'
| 'prepareStackTrace'
| InterceptorOptions
/**
* Disable HMR or configure HMR options.
*
* @default true
*/
hmr?: boolean | ModuleRunnerHmr
/**
* Custom module cache. If not provided, it creates a separate module
* cache for each module runner instance.
*/
evaluatedModules?: EvaluatedModules
}
```
## `ModuleEvaluator`
**Type Signature:**
```ts twoslash
import type { ModuleRunnerContext as ModuleRunnerContextRaw } from 'vite/module-runner'
import type { Debug } from '@type-challenges/utils'
type ModuleRunnerContext = Debug
// ---cut---
export interface ModuleEvaluator {
/**
* Number of prefixed lines in the transformed code.
*/
startOffset?: number
/**
* Evaluate code that was transformed by Vite.
* @param context Function context
* @param code Transformed code
* @param id ID that was used to fetch the module
*/
runInlinedModule(
context: ModuleRunnerContext,
code: string,
id: string,
): Promise
/**
* evaluate externalized module.
* @param file File URL to the external module
*/
runExternalModule(file: string): Promise
}
```
Vite exports `ESModulesEvaluator` that implements this interface by default. It uses `new AsyncFunction` to evaluate code, so if the code has inlined source map it should contain an [offset of 2 lines](https://tc39.es/ecma262/#sec-createdynamicfunction) to accommodate for new lines added. This is done automatically by the `ESModulesEvaluator`. Custom evaluators will not add additional lines.
## `ModuleRunnerTransport`
**Type Signature:**
```ts twoslash
import type { ModuleRunnerTransportHandlers } from 'vite/module-runner'
/** an object */
type HotPayload = unknown
// ---cut---
interface ModuleRunnerTransport {
connect?(handlers: ModuleRunnerTransportHandlers): Promise | void
disconnect?(): Promise | void
send?(data: HotPayload): Promise | void
invoke?(data: HotPayload): Promise<{ result: any } | { error: any }>
timeout?: number
}
```
Transport object that communicates with the environment via an RPC or by directly calling the function. When `invoke` method is not implemented, the `send` method and `connect` method is required to be implemented. Vite will construct the `invoke` internally.
You need to couple it with the `HotChannel` instance on the server like in this example where module runner is created in the worker thread:
::: code-group
```js [worker.js]
import { parentPort } from 'node:worker_threads'
import { fileURLToPath } from 'node:url'
import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner'
/** @type {import('vite/module-runner').ModuleRunnerTransport} */
const transport = {
connect({ onMessage, onDisconnection }) {
parentPort.on('message', onMessage)
parentPort.on('close', onDisconnection)
},
send(data) {
parentPort.postMessage(data)
},
}
const runner = new ModuleRunner(
{
transport,
},
new ESModulesEvaluator(),
)
```
```js [server.js]
import { BroadcastChannel } from 'node:worker_threads'
import { createServer, RemoteEnvironmentTransport, DevEnvironment } from 'vite'
function createWorkerEnvironment(name, config, context) {
const worker = new Worker('./worker.js')
const handlerToWorkerListener = new WeakMap()
const workerHotChannel = {
send: (data) => worker.postMessage(data),
on: (event, handler) => {
if (event === 'connection') return
const listener = (value) => {
if (value.type === 'custom' && value.event === event) {
const client = {
send(payload) {
worker.postMessage(payload)
},
}
handler(value.data, client)
}
}
handlerToWorkerListener.set(handler, listener)
worker.on('message', listener)
},
off: (event, handler) => {
if (event === 'connection') return
const listener = handlerToWorkerListener.get(handler)
if (listener) {
worker.off('message', listener)
handlerToWorkerListener.delete(handler)
}
},
}
return new DevEnvironment(name, config, {
transport: workerHotChannel,
})
}
await createServer({
environments: {
worker: {
dev: {
createEnvironment: createWorkerEnvironment,
},
},
},
})
```
:::
A different example using an HTTP request to communicate between the runner and the server:
```ts
import { ESModulesEvaluator, ModuleRunner } from 'vite/module-runner'
export const runner = new ModuleRunner(
{
transport: {
async invoke(data) {
const response = await fetch(`http://my-vite-server/invoke`, {
method: 'POST',
body: JSON.stringify(data),
})
return response.json()
},
},
hmr: false, // disable HMR as HMR requires transport.connect
},
new ESModulesEvaluator(),
)
await runner.import('/entry.js')
```
In this case, the `handleInvoke` method in the `NormalizedHotChannel` can be used:
```ts
const customEnvironment = new DevEnvironment(name, config, context)
server.onRequest((request: Request) => {
const url = new URL(request.url)
if (url.pathname === '/invoke') {
const payload = (await request.json()) as HotPayload
const result = customEnvironment.hot.handleInvoke(payload)
return new Response(JSON.stringify(result))
}
return Response.error()
})
```
But note that for HMR support, `send` and `connect` methods are required. The `send` method is usually called when the custom event is triggered (like, `import.meta.hot.send("my-event")`).
Vite exports `createServerHotChannel` from the main entry point to support HMR during Vite SSR.
---
---
url: /guide/features.md
---
# Features
At the very basic level, developing using Vite is not that different from using a static file server. However, Vite provides many enhancements over native ESM imports to support various features that are typically seen in bundler-based setups.
## npm Dependency Resolving and Pre-Bundling
Native ES imports do not support bare module imports like the following:
```js
import { someMethod } from 'my-dep'
```
The above will throw an error in the browser. Vite will detect such bare module imports in all served source files and perform the following:
1. [Pre-bundle](./dep-pre-bundling) them to improve page loading speed and convert CommonJS / UMD modules to ESM. The pre-bundling step is performed with [esbuild](http://esbuild.github.io/) and makes Vite's cold start time significantly faster than any JavaScript-based bundler.
2. Rewrite the imports to valid URLs like `/node_modules/.vite/deps/my-dep.js?v=f3sf2ebd` so that the browser can import them properly.
**Dependencies are Strongly Cached**
Vite caches dependency requests via HTTP headers, so if you wish to locally edit/debug a dependency, follow the steps [here](./dep-pre-bundling#browser-cache).
## Hot Module Replacement
Vite provides an [HMR API](./api-hmr) 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](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue) and [React Fast Refresh](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react). There are also official integrations for Preact via [@prefresh/vite](https://github.com/JoviDeCroock/prefresh/tree/main/packages/vite).
Note you don't need to manually set these up - when you [create an app via `create-vite`](./), the selected templates would have these pre-configured for you already.
## TypeScript
Vite supports importing `.ts` files out of the box.
### Transpile Only
Note that 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.
The reason Vite does not perform type checking as part of the transform process is because the two jobs work fundamentally differently. Transpilation can work on a per-file basis and aligns perfectly with Vite's on-demand compile model. In comparison, type checking requires knowledge of the entire module graph. Shoe-horning type checking into Vite's transform pipeline will inevitably compromise Vite's speed benefits.
Vite's job is to get your source modules into a form that can run in the browser as fast as possible. To that end, we recommend separating static analysis checks from Vite's transform pipeline. This principle applies to other static analysis checks such as ESLint.
* For production builds, you can run `tsc --noEmit` in addition to Vite's build command.
* During development, if you need more than IDE hints, we recommend running `tsc --noEmit --watch` in a separate process, or use [vite-plugin-checker](https://github.com/fi3ework/vite-plugin-checker) if you prefer having type errors directly reported in the browser.
Vite uses [esbuild](https://github.com/evanw/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.
Use the [Type-Only Imports and Export](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export) syntax to avoid potential problems like type-only imports being incorrectly bundled, for example:
```ts
import type { T } from 'only/types'
export type { T }
```
### TypeScript Compiler Options
Some configuration fields under `compilerOptions` in `tsconfig.json` require special attention.
#### `isolatedModules`
* [TypeScript documentation](https://www.typescriptlang.org/tsconfig#isolatedModules)
Should be set to `true`.
It is 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.
If a dependency doesn't work well with `"isolatedModules": true`. You can use `"skipLibCheck": true` to temporarily suppress the errors until it is fixed upstream.
#### `useDefineForClassFields`
* [TypeScript documentation](https://www.typescriptlang.org/tsconfig#useDefineForClassFields)
The default value will be `true` if the TypeScript target is `ES2022` or newer including `ESNext`. It is consistent with the [behavior of TypeScript 4.3.2+](https://github.com/microsoft/TypeScript/pull/42663).
Other TypeScript targets will default to `false`.
`true` is the standard ECMAScript runtime behavior.
If you are using a library that heavily relies on class fields, please be careful about the library's intended usage of it.
While most libraries expect `"useDefineForClassFields": true`, you can explicitly set `useDefineForClassFields` to `false` if your library doesn't support it.
#### `target`
* [TypeScript documentation](https://www.typescriptlang.org/tsconfig#target)
Vite ignores the `target` value in the `tsconfig.json`, following the same behavior as `esbuild`.
To specify the target in dev, the [`esbuild.target`](/config/shared-options.html#esbuild) option can be used, which defaults to `esnext` for minimal transpilation. In builds, the [`build.target`](/config/build-options.html#build-target) option takes higher priority over `esbuild.target` and can also be set if needed.
::: warning `useDefineForClassFields`
If `target` in `tsconfig.json` is not `ESNext` or `ES2022` or newer, or if there's no `tsconfig.json` file, `useDefineForClassFields` will default to `false` which can be problematic with the default `esbuild.target` value of `esnext`. It may transpile to [static initialization blocks](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks#browser_compatibility) which may not be supported in your browser.
As such, it is recommended to set `target` to `ESNext` or `ES2022` or newer, or set `useDefineForClassFields` to `true` explicitly when configuring `tsconfig.json`.
:::
#### Other Compiler Options Affecting the Build Result
* [`extends`](https://www.typescriptlang.org/tsconfig#extends)
* [`importsNotUsedAsValues`](https://www.typescriptlang.org/tsconfig#importsNotUsedAsValues)
* [`preserveValueImports`](https://www.typescriptlang.org/tsconfig#preserveValueImports)
* [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax)
* [`jsx`](https://www.typescriptlang.org/tsconfig#jsx)
* [`jsxFactory`](https://www.typescriptlang.org/tsconfig#jsxFactory)
* [`jsxFragmentFactory`](https://www.typescriptlang.org/tsconfig#jsxFragmentFactory)
* [`jsxImportSource`](https://www.typescriptlang.org/tsconfig#jsxImportSource)
* [`experimentalDecorators`](https://www.typescriptlang.org/tsconfig#experimentalDecorators)
* [`alwaysStrict`](https://www.typescriptlang.org/tsconfig#alwaysStrict)
::: tip `skipLibCheck`
Vite starter templates have `"skipLibCheck": "true"` by default to avoid typechecking dependencies, as they may choose to only support specific versions and configurations of TypeScript. You can learn more at [vuejs/vue-cli#5688](https://github.com/vuejs/vue-cli/pull/5688).
:::
### 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 a `d.ts` declaration file:
```typescript
///
```
::: details Using `compilerOptions.types`
Alternatively, you can add `vite/client` to `compilerOptions.types` inside `tsconfig.json`:
```json [tsconfig.json]
{
"compilerOptions": {
"types": ["vite/client", "some-other-global-lib"]
}
}
```
Note that if [`compilerOptions.types`](https://www.typescriptlang.org/tsconfig#types) is specified, only these packages will be included in the global scope (instead of all visible ”@types” packages).
:::
`vite/client` provides the following type shims:
* Asset imports (e.g. importing an `.svg` file)
* Types for the Vite-injected [constants](./env-and-mode#env-variables) on `import.meta.env`
* Types for the [HMR API](./api-hmr) on `import.meta.hot`
::: tip
To override the default typing, add a type definition file that contains your typings. Then, add the type reference before `vite/client`.
For example, to make the default import of `*.svg` a React component:
* `vite-env-override.d.ts` (the file that contains your typings):
```ts
declare module '*.svg' {
const content: React.FC>
export default content
}
```
* The file containing the reference to `vite/client`:
```ts
///
///
```
:::
## HTML
HTML files stand [front-and-center](/guide/#index-html-and-project-root) of a Vite project, serving as the entry points for your application, making it simple to build single-page and [multi-page applications](/guide/build.html#multi-page-app).
Any HTML files in your project root can be directly accessed by its respective directory path:
* `/index.html` -> `http://localhost:5173/`
* `/about.html` -> `http://localhost:5173/about.html`
* `/blog/index.html` -> `http://localhost:5173/blog/index.html`
Assets referenced by HTML elements such as `