# HMR `hotUpdate` Plugin Hook **Feedback** Give us feedback at [Environment API feedback discussion](https://github.com/vitejs/vite/discussions/16358) We're planning to deprecate the `handleHotUpdate` plugin hook in favor of [`hotUpdate` hook](../guide/api-environment#the-hotupdate-hook) to be [Environment API](../guide/api-environment) aware, and handle additional watch events with `create` and `delete`. Affected scope: `Vite Plugin Authors` **Future Deprecation** `hotUpdate` was first introduced in `v6.0`. The deprecation of `handleHotUpdate` is planned for a future major. We don't yet recommend moving away from `handleHotUpdate` yet. If you want to experiment and give us feedback, you can use the `future.removePluginHookHandleHotUpdate` to `"warn"` in your vite config. ## Motivation The [`handleHotUpdate` hook](../guide/api-plugin#handlehotupdate) allows to perform custom HMR update handling. A list of modules to be updated is passed in the `HmrContext` interface HmrContext { file: string timestamp: number modules: Array read: () => string | Promise server: ViteDevServer } This hook is called once for all environments, and the passed modules have mixed information from the Client and SSR environments only. Once frameworks move to custom environments, a new hook that is called for each of them is needed. The new `hotUpdate` hook works in the same way as `handleHotUpdate` but it is called for each environment and receives a new `HotUpdateOptions` instance: interface HotUpdateOptions { type: 'create' | 'update' | 'delete' file: string timestamp: number modules: Array read: () => string | Promise server: ViteDevServer } The current dev environment can be accessed like in other Plugin hooks with `this.environment`. The `modules` list will now be module nodes from the current environment only. Each environment update can define different update strategies. This hook is also now called for additional watch events and not only for `'update'`. Use `type` to differentiate between them. ## Migration Guide Filter and narrow down the affected module list so that the HMR is more accurate. handleHotUpdate({ modules }) { return modules.filter(condition) } // Migrate to: hotUpdate({ modules }) { return modules.filter(condition) } Return an empty array and perform a full reload: handleHotUpdate({ server, modules, timestamp }) { // Invalidate modules manually const invalidatedModules = new Set() for (const mod of modules) { server.moduleGraph.invalidateModule( mod, invalidatedModules, timestamp, true ) } server.ws.send({ type: 'full-reload' }) return [] } // Migrate to: hotUpdate({ modules, timestamp }) { // 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: handleHotUpdate({ server }) { server.ws.send({ type: 'custom', event: 'special-update', data: {} }) return [] } // Migrate to... hotUpdate() { this.environment.hot.send({ type: 'custom', event: 'special-update', data: {} }) return [] } # Breaking Changes List of breaking changes in Vite including API deprecations, removals, and changes. Most of the changes below can be opt-in using the [`future` option](../config/shared-options#future) in your Vite config. ## Planned These changes are planned for the next major version of Vite. The deprecation or usage warnings will guide you where possible, and we're reaching out to framework, plugin authors, and users to apply these changes. * _No planned changes yet_ ## Considering These changes are being considered and are often experimental APIs that intend to improve upon current usage patterns. As not all changes are listed here, please check out the [Experimental Label in Vite GitHub Discussions](https://github.com/vitejs/vite/discussions/categories/feedback?discussions_q=label%3Aexperimental+category%3AFeedback) for the full list. We don't recommend switching to these APIs yet. They are included in Vite to help us gather feedback. Please check these proposals and let us know how they work in your use case in each's linked GitHub Discussions. * [`this.environment` in Hooks](this-environment-in-hooks) * [HMR `hotUpdate` Plugin Hook](hotupdate-hook) * [Move to Per-environment APIs](per-environment-apis) * [SSR Using `ModuleRunner` API](ssr-using-modulerunner) * [Shared Plugins During Build](shared-plugins-during-build) ## Past The changes below has been done or reverted. They are no longer relevant in the current major version. * _No past changes yet_ # Move to Per-environment APIs **Feedback** Give us feedback at [Environment API feedback discussion](https://github.com/vitejs/vite/discussions/16358) Multiple APIs from `ViteDevServer` related to module graph and modules transforms have been moved to the `DevEnvironment` instances. Affect scope: `Vite Plugin Authors` **Future Deprecation** The `Environment` instance was first introduced at `v6.0`. The deprecation of `server.moduleGraph` and other methods that are now in environments is planned for a future major. We don't recommend moving away from server methods yet. To identify your usage, set these in your vite config. future: { removeServerModuleGraph: 'warn', removeServerTransformRequest: 'warn', } ## Motivation In Vite v5 and before, a single Vite dev server always had two environments (`client` and `ssr`). The `server.moduleGraph` had mixed modules from both of these environments. Nodes were connected through `clientImportedModules` and `ssrImportedModules` lists (but a single `importers` list was maintained for each). A transformed module was represented by an `id` and a `ssr` boolean. This boolean needed to be passed to APIs, for example `server.moduleGraph.getModuleByUrl(url, ssr)` and `server.transformRequest(url, { ssr })`. In Vite v6, it is now possible to create any number of custom environments (`client`, `ssr`, `edge`, etc). A single `ssr` boolean isn't enough anymore. Instead of changing the APIs to be of the form `server.transformRequest(url, { environment })`, we moved these methods to the environment instance allowing them to be called without a Vite dev server. ## Migration Guide * `server.moduleGraph` -> [`environment.moduleGraph`](../guide/api-environment-instances#separate-module-graphs) * `server.transformRequest(url, ssr)` -> `environment.transformRequest(url)` * `server.warmupRequest(url, ssr)` -> `environment.warmupRequest(url)` # Shared Plugins during Build **Feedback** Give us feedback at [Environment API feedback discussion](https://github.com/vitejs/vite/discussions/16358) See [Shared plugins during build](../guide/api-environment-plugins#shared- plugins-during-build). Affect scope: `Vite Plugin Authors` **Future Default Change** `builder.sharedConfigBuild` was first introduce in `v6.0`. You can set it true to check how your plugins work with a shared config. We're looking for feedback about changing the default in a future major once the plugin ecosystem is ready. ## Motivation Align dev and build plugin pipelines. ## Migration Guide To be able to share plugins across environments, plugin state must be keyed by the current environment. A plugin of the following form will count the number of transformed modules across all environments. function CountTransformedModulesPlugin() { let transformedModules return { name: 'count-transformed-modules', buildStart() { transformedModules = 0 }, transform(id) { transformedModules++ }, buildEnd() { console.log(transformedModules) }, } } If we instead want to count the number of transformed modules for each environment, we need to keep a map: function PerEnvironmentCountTransformedModulesPlugin() { const state = new Map() return { name: 'count-transformed-modules', perEnvironmentStartEndDuringDev: true, buildStart() { state.set(this.environment, { count: 0 }) } transform(id) { state.get(this.environment).count++ }, buildEnd() { console.log(this.environment.name, state.get(this.environment).count) } } } To simplify this pattern, Vite exports a `perEnvironmentState` helper: function PerEnvironmentCountTransformedModulesPlugin() { const state = perEnvironmentState<{ count: number }>(() => ({ count: 0 })) return { name: 'count-transformed-modules', perEnvironmentStartEndDuringDev: true, buildStart() { state(this).count = 0 } transform(id) { state(this).count++ }, buildEnd() { console.log(this.environment.name, state(this).count) } } } # SSR Using `ModuleRunner` API **Feedback** Give us feedback at [Environment API feedback discussion](https://github.com/vitejs/vite/discussions/16358) `server.ssrLoadModule` has been replaced by importing from a [Module Runner](../guide/api-environment#modulerunner). Affect scope: `Vite Plugin Authors` **Future Deprecation** `ModuleRunner` was first introduce in `v6.0`. The deprecation of `server.ssrLoadModule` is planned for a future major. To identify your usage, set `future.removeSsrLoadModule` to `"warn"` in your vite config. ## Motivation The `server.ssrLoadModule(url)` only allows importing modules in the `ssr` environment and can only execute the modules in the same process as the Vite dev server. For apps with custom environments, each is associated with a `ModuleRunner` that may be running in a separate thread or process. To import modules, we now have `moduleRunner.import(url)`. ## Migration Guide Check out the [Environment API for Frameworks Guide](../guide/api-environment- frameworks). # `this.environment` in Hooks **Feedback** Give us feedback at [Environment API feedback discussion](https://github.com/vitejs/vite/discussions/16358) Before Vite 6, only two environments were available: `client` and `ssr`. A single `options.ssr` plugin hook argument in `resolveId`, `load` and `transform` allowed plugin authors to differentiate between these two environments when processing modules in plugin hooks. In Vite 6, a Vite application can define any number of named environments as needed. We're introducing `this.environment` in the plugin context to interact with the environment of the current module in hooks. Affect scope: `Vite Plugin Authors` **Future Deprecation** `this.environment` was introduced in `v6.0`. The deprecation of `options.ssr` is planned for a future major. At that point we'll start recommending migrating your plugins to use the new API. To identify your usage, set `future.removePluginHookSsrArgument` to `"warn"` in your vite config. ## Motivation `this.environment` not only allow the plugin hook implementation to know the current environment name, it also gives access to the environment config options, module graph information, and transform pipeline (`environment.config`, `environment.moduleGraph`, `environment.transformRequest()`). Having the environment instance available in the context allows plugin authors to avoid the dependency of the whole dev server (typically cached at startup through the `configureServer` hook). ## Migration Guide For the existing plugin to do a quick migration, replace the `options.ssr` argument with `this.environment.config.consumer === 'server'` in the `resolveId`, `load` and `transform` hooks: import { Plugin } from 'vite' export function myPlugin(): Plugin { return { name: 'my-plugin', resolveId(id, importer, options) { const isSSR = options.ssr const isSSR = this.environment.config.consumer === 'server' if (isSSR) { // SSR specific logic } else { // Client specific logic } }, } } For a more robust long term implementation, the plugin hook should handle for [multiple environments](../guide/api-environment-plugins#accessing-the- current-environment-in-hooks) using fine-grained environment options instead of relying on the environment name. # Build Options Unless noted, the options in this section are only applied to build. ## build.target * **Type:** `string | string[]` * **Default:** `'baseline-widely-available'` * **Related:** [Browser Compatibility](../guide/build#browser-compatibility) Browser compatibility target for the final bundle. The default value is a Vite special value, `'baseline-widely-available'`, which targets browsers that are included in the [Baseline](https://web-platform-dx.github.io/web-features/) Widely Available on 2025-05-01. Specifically, it is `['chrome107', 'edge107', 'firefox104', 'safari16']`. Another special value is `'esnext'` \- which assumes native dynamic imports support and will only perform minimal transpiling. The transform is performed with esbuild and the value should be a valid [esbuild target option](https://esbuild.github.io/api/#target). Custom targets can either be an ES version (e.g. `es2015`), a browser with version (e.g. `chrome58`), or an array of multiple target strings. Note the build will fail if the code contains features that cannot be safely transpiled by esbuild. See [esbuild docs](https://esbuild.github.io/content- types/#javascript) for more details. ## build.modulePreload * **Type:** `boolean | { polyfill?: boolean, resolveDependencies?: ResolveModulePreloadDependenciesFn }` * **Default:** `{ polyfill: true }` By default, a [module preload polyfill](https://guybedford.com/es-module- preloading-integrity#modulepreload-polyfill) is automatically injected. The polyfill is auto injected into the proxy module of each `index.html` entry. If the build is configured to use a non-HTML custom entry via `build.rollupOptions.input`, then it is necessary to manually import the polyfill in your custom entry: import 'vite/modulepreload-polyfill' Note: the polyfill does **not** apply to [Library Mode](../guide/build#library-mode). If you need to support browsers without native dynamic import, you should probably avoid using it in your library. The polyfill can be disabled using `{ polyfill: false }`. The list of chunks to preload for each dynamic import is computed by Vite. By default, an absolute path including the `base` will be used when loading these dependencies. If the `base` is relative (`''` or `'./'`), `import.meta.url` is used at runtime to avoid absolute paths that depend on the final deployed base. There is experimental support for fine grained control over the dependencies list and their paths using the `resolveDependencies` function. [Give Feedback](https://github.com/vitejs/vite/discussions/13841). It expects a function of type `ResolveModulePreloadDependenciesFn`: type ResolveModulePreloadDependenciesFn = ( url: string, deps: string[], context: { hostId: string hostType: 'html' | 'js' }, ) => string[] The `resolveDependencies` function will be called for each dynamic import with a list of the chunks it depends on, and it will also be called for each chunk imported in entry HTML files. A new dependencies array can be returned with these filtered or more dependencies injected, and their paths modified. The `deps` paths are relative to the `build.outDir`. The return value should be a relative path to the `build.outDir`. modulePreload: { resolveDependencies: (filename, deps, { hostId, hostType }) => { return deps.filter(condition) }, }, The resolved dependency paths can be further modified using [`experimental.renderBuiltUrl`](../guide/build#advanced-base-options). ## build.polyfillModulePreload * **Type:** `boolean` * **Default:** `true` * **Deprecated** use `build.modulePreload.polyfill` instead Whether to automatically inject a [module preload polyfill](https://guybedford.com/es-module-preloading-integrity#modulepreload- polyfill). ## build.outDir * **Type:** `string` * **Default:** `dist` Specify the output directory (relative to [project root](../guide/index#index- html-and-project-root)). ## build.assetsDir * **Type:** `string` * **Default:** `assets` Specify the directory to nest generated assets under (relative to `build.outDir`. This is not used in [Library Mode](../guide/build#library- mode)). ## build.assetsInlineLimit * **Type:** `number` | `((filePath: string, content: Buffer) => boolean | undefined)` * **Default:** `4096` (4 KiB) Imported or referenced assets that are smaller than this threshold will be inlined as base64 URLs to avoid extra http requests. Set to `0` to disable inlining altogether. If a callback is passed, a boolean can be returned to opt-in or opt-out. If nothing is returned the default logic applies. Git LFS placeholders are automatically excluded from inlining because they do not contain the content of the file they represent. **Note** If you specify `build.lib`, `build.assetsInlineLimit` will be ignored and assets will always be inlined, regardless of file size or being a Git LFS placeholder. ## build.cssCodeSplit * **Type:** `boolean` * **Default:** `true` Enable/disable CSS code splitting. When enabled, CSS imported in async JS chunks will be preserved as chunks and fetched together when the chunk is fetched. If disabled, all CSS in the entire project will be extracted into a single CSS file. **Note** If you specify `build.lib`, `build.cssCodeSplit` will be `false` as default. ## build.cssTarget * **Type:** `string | string[]` * **Default:** the same as `build.target` This option allows users to set a different browser target for CSS minification from the one used for JavaScript transpilation. It should only be used when you are targeting a non-mainstream browser. One example is Android WeChat WebView, which supports most modern JavaScript features but not the [`#RGBA` hexadecimal color notation in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#rgb_colors). In this case, you need to set `build.cssTarget` to `chrome61` to prevent vite from transforming `rgba()` colors into `#RGBA` hexadecimal notations. ## build.cssMinify * **Type:** `boolean | 'esbuild' | 'lightningcss'` * **Default:** the same as `build.minify` for client, `'esbuild'` for SSR This option allows users to override CSS minification specifically instead of defaulting to `build.minify`, so you can configure minification for JS and CSS separately. Vite uses `esbuild` by default to minify CSS. Set the option to `'lightningcss'` to use [Lightning CSS](https://lightningcss.dev/minification.html) instead. If selected, it can be configured using [`css.lightningcss`](shared-options#css-lightningcss). ## build.sourcemap * **Type:** `boolean | 'inline' | 'hidden'` * **Default:** `false` Generate production source maps. If `true`, a separate sourcemap file will be created. If `'inline'`, the sourcemap will be appended to the resulting output file as a data URI. `'hidden'` works like `true` except that the corresponding sourcemap comments in the bundled files are suppressed. ## build.rollupOptions * **Type:** [`RollupOptions`](https://rollupjs.org/configuration-options/) Directly customize the underlying Rollup bundle. This is the same as options that can be exported from a Rollup config file and will be merged with Vite's internal Rollup options. See [Rollup options docs](https://rollupjs.org/configuration-options/) for more details. ## build.commonjsOptions * **Type:** [`RollupCommonJSOptions`](https://github.com/rollup/plugins/tree/master/packages/commonjs#options) Options to pass on to [@rollup/plugin- commonjs](https://github.com/rollup/plugins/tree/master/packages/commonjs). ## build.dynamicImportVarsOptions * **Type:** [`RollupDynamicImportVarsOptions`](https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#options) * **Related:** [Dynamic Import](../guide/features#dynamic-import) Options to pass on to [@rollup/plugin-dynamic-import- vars](https://github.com/rollup/plugins/tree/master/packages/dynamic-import- vars). ## build.lib * **Type:** `{ entry: string | string[] | { [entryAlias: string]: string }, name?: string, formats?: ('es' | 'cjs' | 'umd' | 'iife')[], fileName?: string | ((format: ModuleFormat, entryName: string) => string), cssFileName?: string }` * **Related:** [Library Mode](../guide/build#library-mode) Build as a library. `entry` is required since the library cannot use HTML as entry. `name` is the exposed global variable and is required when `formats` includes `'umd'` or `'iife'`. Default `formats` are `['es', 'umd']`, or `['es', 'cjs']`, if multiple entries are used. `fileName` is the name of the package file output, which defaults to the `"name"` in `package.json`. It can also be defined as a function taking the `format` and `entryName` as arguments, and returning the file name. If your package imports CSS, `cssFileName` can be used to specify the name of the CSS file output. It defaults to the same value as `fileName` if it's set a string, otherwise it also falls back to the `"name"` in `package.json`. vite.config.js import { defineConfig } from 'vite' export default defineConfig({ build: { lib: { entry: ['src/main.js'], fileName: (format, entryName) => `my-lib-${entryName}.${format}.js`, cssFileName: 'my-lib-style', }, }, }) ## build.manifest * **Type:** `boolean | string` * **Default:** `false` * **Related:** [Backend Integration](../guide/backend-integration) Whether to generate a manifest file that contains a mapping of non-hashed asset filenames to their hashed versions, which can then be used by a server framework to render the correct asset links. When the value is a string, it will be used as the manifest file path relative to `build.outDir`. When set to `true`, the path would be `.vite/manifest.json`. ## build.ssrManifest * **Type:** `boolean | string` * **Default:** `false` * **Related:** [Server-Side Rendering](../guide/ssr) Whether to generate a SSR manifest file for determining style links and asset preload directives in production. When the value is a string, it will be used as the manifest file path relative to `build.outDir`. When set to `true`, the path would be `.vite/ssr- manifest.json`. ## build.ssr * **Type:** `boolean | string` * **Default:** `false` * **Related:** [Server-Side Rendering](../guide/ssr) Produce SSR-oriented build. The value can be a string to directly specify the SSR entry, or `true`, which requires specifying the SSR entry via `rollupOptions.input`. ## build.emitAssets * **Type:** `boolean` * **Default:** `false` During non-client builds, static assets aren't emitted as it is assumed they would be emitted as part of the client build. This option allows frameworks to force emitting them in other environments build. It is responsibility of the framework to merge the assets with a post build step. ## build.ssrEmitAssets * **Type:** `boolean` * **Default:** `false` During the SSR build, static assets aren't emitted as it is assumed they would be emitted as part of the client build. This option allows frameworks to force emitting them in both the client and SSR build. It is responsibility of the framework to merge the assets with a post build step. This option will be replaced by `build.emitAssets` once Environment API is stable. ## build.minify * **Type:** `boolean | 'terser' | 'esbuild'` * **Default:** `'esbuild'` for client build, `false` for SSR build Set to `false` to disable minification, or specify the minifier to use. The default is [esbuild](https://github.com/evanw/esbuild) which is 20 ~ 40x faster than terser and only 1 ~ 2% worse compression. [Benchmarks](https://github.com/privatenumber/minification-benchmarks) Note the `build.minify` option does not minify whitespaces when using the `'es'` format in lib mode, as it removes pure annotations and breaks tree- shaking. Terser must be installed when it is set to `'terser'`. npm add -D terser ## build.terserOptions * **Type:** `TerserOptions` Additional [minify options](https://terser.org/docs/api-reference#minify- options) to pass on to Terser. In addition, you can also pass a `maxWorkers: number` option to specify the max number of workers to spawn. Defaults to the number of CPUs minus 1. ## build.write * **Type:** `boolean` * **Default:** `true` Set to `false` to disable writing the bundle to disk. This is mostly used in [programmatic `build()` calls](../guide/api-javascript#build) where further post processing of the bundle is needed before writing to disk. ## build.emptyOutDir * **Type:** `boolean` * **Default:** `true` if `outDir` is inside `root` By default, Vite will empty the `outDir` on build if it is inside project root. It will emit a warning if `outDir` is outside of root to avoid accidentally removing important files. You can explicitly set this option to suppress the warning. This is also available via command line as `--emptyOutDir`. ## build.copyPublicDir * **Type:** `boolean` * **Default:** `true` By default, Vite will copy files from the `publicDir` into the `outDir` on build. Set to `false` to disable this. ## build.reportCompressedSize * **Type:** `boolean` * **Default:** `true` Enable/disable gzip-compressed size reporting. Compressing large output files can be slow, so disabling this may increase build performance for large projects. ## build.chunkSizeWarningLimit * **Type:** `number` * **Default:** `500` Limit for chunk size warnings (in kB). It is compared against the uncompressed chunk size as the [JavaScript size itself is related to the execution time](https://v8.dev/blog/cost-of-javascript-2019). ## build.watch * **Type:** [`WatcherOptions`](https://rollupjs.org/configuration-options/#watch)`| null` * **Default:** `null` Set to `{}` to enable rollup watcher. This is mostly used in cases that involve build-only plugins or integrations processes. **Using Vite on Windows Subsystem for Linux (WSL) 2** There are cases that file system watching does not work with WSL2. See [`server.watch`](server-options#server-watch) for more details. # Dep Optimization Options * **Related:** [Dependency Pre-Bundling](../guide/dep-pre-bundling) Unless noted, the options in this section are only applied to the dependency optimizer, which is only used in dev. ## optimizeDeps.entries * **Type:** `string | string[]` By default, Vite will crawl all your `.html` files to detect dependencies that need to be pre-bundled (ignoring `node_modules`, `build.outDir`, `__tests__` and `coverage`). If `build.rollupOptions.input` is specified, Vite will crawl those entry points instead. If neither of these fit your needs, you can specify custom entries using this option - the value should be a [`tinyglobby` pattern](https://github.com/SuperchupuDev/tinyglobby) or array of patterns that are relative from Vite project root. This will overwrite default entries inference. Only `node_modules` and `build.outDir` folders will be ignored by default when `optimizeDeps.entries` is explicitly defined. If other folders need to be ignored, you can use an ignore pattern as part of the entries list, marked with an initial `!`. `node_modules` will not be ignored for patterns that explicitly include the string `node_modules`. ## optimizeDeps.exclude * **Type:** `string[]` Dependencies to exclude from pre-bundling. **CommonJS** CommonJS dependencies should not be excluded from optimization. If an ESM dependency is excluded from optimization, but has a nested CommonJS dependency, the CommonJS dependency should be added to `optimizeDeps.include`. Example: export default defineConfig({ optimizeDeps: { include: ['esm-dep > cjs-dep'], }, }) ## optimizeDeps.include * **Type:** `string[]` By default, linked packages not inside `node_modules` are not pre-bundled. Use this option to force a linked package to be pre-bundled. **Experimental:** If you're using a library with many deep imports, you can also specify a trailing glob pattern to pre-bundle all deep imports at once. This will avoid constantly pre-bundling whenever a new deep import is used. [Give Feedback](https://github.com/vitejs/vite/discussions/15833). For example: export default defineConfig({ optimizeDeps: { include: ['my-lib/components/**/*.vue'], }, }) ## optimizeDeps.esbuildOptions * **Type:** [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys)`<`[`EsbuildBuildOptions`](https://esbuild.github.io/api/#general-options)`, | 'bundle' | 'entryPoints' | 'external' | 'write' | 'watch' | 'outdir' | 'outfile' | 'outbase' | 'outExtension' | 'metafile'>` Options to pass to esbuild during the dep scanning and optimization. Certain options are omitted since changing them would not be compatible with Vite's dep optimization. * `external` is also omitted, use Vite's `optimizeDeps.exclude` option * `plugins` are merged with Vite's dep plugin ## optimizeDeps.force * **Type:** `boolean` Set to `true` to force dependency pre-bundling, ignoring previously cached optimized dependencies. ## optimizeDeps.noDiscovery * **Type:** `boolean` * **Default:** `false` When set to `true`, automatic dependency discovery will be disabled and only dependencies listed in `optimizeDeps.include` will be optimized. CJS-only dependencies must be present in `optimizeDeps.include` during dev. ## optimizeDeps.holdUntilCrawlEnd * **Experimental:** [Give Feedback](https://github.com/vitejs/vite/discussions/15834) * **Type:** `boolean` * **Default:** `true` When enabled, it will hold the first optimized deps results until all static imports are crawled on cold start. This avoids the need for full-page reloads when new dependencies are discovered and they trigger the generation of new common chunks. If all dependencies are found by the scanner plus the explicitly defined ones in `include`, it is better to disable this option to let the browser process more requests in parallel. ## optimizeDeps.disabled * **Deprecated** * **Experimental:** [Give Feedback](https://github.com/vitejs/vite/discussions/13839) * **Type:** `boolean | 'build' | 'dev'` * **Default:** `'build'` This option is deprecated. As of Vite 5.1, pre-bundling of dependencies during build have been removed. Setting `optimizeDeps.disabled` to `true` or `'dev'` disables the optimizer, and configured to `false` or `'build'` leaves the optimizer during dev enabled. To disable the optimizer completely, use `optimizeDeps.noDiscovery: true` to disallow automatic discovery of dependencies and leave `optimizeDeps.include` undefined or empty. **WARNING** Optimizing dependencies during build time was an **experimental** feature. Projects trying out this strategy also removed `@rollup/plugin-commonjs` using `build.commonjsOptions: { include: [] }`. If you did so, a warning will guide you to re-enable it to support CJS only packages while bundling. ## optimizeDeps.needsInterop * **Experimental** * **Type:** `string[]` Forces ESM interop when importing these dependencies. Vite is able to properly detect when a dependency needs interop, so this option isn't generally needed. However, different combinations of dependencies could cause some of them to be prebundled differently. Adding these packages to `needsInterop` can speed up cold start by avoiding full-page reloads. You'll receive a warning if this is the case for one of your dependencies, suggesting to add the package name to this array in your config. # Configuring Vite When running `vite` from the command line, Vite will automatically try to resolve a config file named `vite.config.js` inside [project root](../guide/index#index-html-and-project-root) (other JS and TS extensions are also supported). The most basic config file looks like this: vite.config.js export default { // config options } Note Vite supports using ES modules syntax in the config file even if the project is not using native Node ESM, e.g. `"type": "module"` in `package.json`. In this case, the config file is auto pre-processed before load. You can also explicitly specify a config file to use with the `--config` CLI option (resolved relative to `cwd`): vite --config my-config.js **CONFIG LOADING** By default, Vite uses `esbuild` to bundle the config into a temporary file and load it. This may cause issues when importing TypeScript files in a monorepo. If you encounter any issues with this approach, you can specify `--configLoader runner` to use the [module runner](../guide/api-environment- runtimes#modulerunner) instead, which will not create a temporary config and will transform any files on the fly. Note that module runner doesn't support CJS in config files, but external CJS packages should work as usual. Alternatively, if you're using an environment that supports TypeScript (e.g. `node --experimental-strip-types`), or if you're only writing plain JavaScript, you can specify `--configLoader native` to use the environment's native runtime to load the config file. Note that updates to modules imported by the config file are not detected and hence would not auto-restart the Vite server. ## Config Intellisense Since Vite ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints: /** @type {import('vite').UserConfig} */ export default { // ... } Alternatively, you can use the `defineConfig` helper which should provide intellisense without the need for jsdoc annotations: import { defineConfig } from 'vite' export default defineConfig({ // ... }) Vite also supports TypeScript config files. You can use `vite.config.ts` with the `defineConfig` helper function above, or with the `satisfies` operator: import type { UserConfig } from 'vite' export default { // ... } satisfies UserConfig ## Conditional Config If the config needs to conditionally determine options based on the command (`serve` or `build`), the [mode](../guide/env-and-mode#modes) being used, if it's an SSR build (`isSsrBuild`), or is previewing the build (`isPreview`), it can export a function instead: export default defineConfig(({ command, mode, isSsrBuild, isPreview }) => { if (command === 'serve') { return { // dev specific config } } else { // command === 'build' return { // build specific config } } }) It is important to note that in Vite's API the `command` value is `serve` during dev (in the cli [`vite`](../guide/cli#vite), `vite dev`, and `vite serve` are aliases), and `build` when building for production ([`vite build`](../guide/cli#vite-build)). `isSsrBuild` and `isPreview` are additional optional flags to differentiate the kind of `build` and `serve` commands respectively. Some tools that load the Vite config may not support these flags and will pass `undefined` instead. Hence, it's recommended to use explicit comparison against `true` and `false`. ## Async Config If the config needs to call async functions, it can export an async function instead. And this async function can also be passed through `defineConfig` for improved intellisense support: export default defineConfig(async ({ command, mode }) => { const data = await asyncFunction() return { // vite config } }) ## Using Environment Variables in Config Environmental Variables can be obtained from `process.env` as usual. Note that Vite doesn't load `.env` files by default as the files to load can only be determined after evaluating the Vite config, for example, the `root` and `envDir` options affect the loading behaviour. However, you can use the exported `loadEnv` helper to load the specific `.env` file if needed. import { defineConfig, loadEnv } from 'vite' export default defineConfig(({ mode }) => { // Load env file based on `mode` in the current working directory. // Set the third parameter to '' to load all env regardless of the // `VITE_` prefix. const env = loadEnv(mode, process.cwd(), '') return { // vite config define: { __APP_ENV__: JSON.stringify(env.APP_ENV), }, } }) ## Debugging the Config File on VS Code With the default `--configLoader bundle` behavior, Vite writes the generated temporary configuration file to the `node_modules/.vite-temp` folder and a file not found error will occur when setting breakpoint debugging in the Vite config file. To fix the issue, add the following configuration to `.vscode/settings.json`: { "debug.javascript.terminalOptions": { "resolveSourceMapLocations": [ "${workspaceFolder}/**", "!**/node_modules/**", "**/node_modules/.vite-temp/**" ] } } # Preview Options Unless noted, the options in this section are only applied to preview. ## preview.host * **Type:** `string | boolean` * **Default:** [`server.host`](server-options#server-host) Specify which IP addresses the server should listen on. Set this to `0.0.0.0` or `true` to listen on all addresses, including LAN and public addresses. This can be set via the CLI using `--host 0.0.0.0` or `--host`. **NOTE** There are cases when other servers might respond instead of Vite. See [`server.host`](server-options#server-host) for more details. ## preview.allowedHosts * **Type:** `string | true` * **Default:** [`server.allowedHosts`](server-options#server-allowedhosts) The hostnames that Vite is allowed to respond to. See [`server.allowedHosts`](server-options#server-allowedhosts) for more details. ## preview.port * **Type:** `number` * **Default:** `4173` Specify server port. Note if the port is already being used, Vite will automatically try the next available port so this may not be the actual port the server ends up listening on. **Example:** export default defineConfig({ server: { port: 3030, }, preview: { port: 8080, }, }) ## preview.strictPort * **Type:** `boolean` * **Default:** [`server.strictPort`](server-options#server-strictport) Set to `true` to exit if port is already in use, instead of automatically trying the next available port. ## preview.https * **Type:** `https.ServerOptions` * **Default:** [`server.https`](server-options#server-https) Enable TLS + HTTP/2. See [`server.https`](server-options#server-https) for more details. ## preview.open * **Type:** `boolean | string` * **Default:** [`server.open`](server-options#server-open) Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname. If you want to open the server in a specific browser you like, you can set the env `process.env.BROWSER` (e.g. `firefox`). You can also set `process.env.BROWSER_ARGS` to pass additional arguments (e.g. `--incognito`). `BROWSER` and `BROWSER_ARGS` are also special environment variables you can set in the `.env` file to configure it. See [the `open` package](https://github.com/sindresorhus/open#app) for more details. ## preview.proxy * **Type:** `Record` * **Default:** [`server.proxy`](server-options#server-proxy) Configure custom proxy rules for the preview server. Expects an object of `{ key: options }` pairs. If the key starts with `^`, it will be interpreted as a `RegExp`. The `configure` option can be used to access the proxy instance. Uses [`http-proxy`](https://github.com/http-party/node-http-proxy). Full options [here](https://github.com/http-party/node-http-proxy#options). ## preview.cors * **Type:** `boolean | CorsOptions` * **Default:** [`server.cors`](server-options#server-cors) Configure CORS for the preview server. See [`server.cors`](server-options#server-cors) for more details. ## preview.headers * **Type:** `OutgoingHttpHeaders` Specify server response headers. # Server Options Unless noted, the options in this section are only applied to dev. ## server.host * **Type:** `string | boolean` * **Default:** `'localhost'` Specify which IP addresses the server should listen on. Set this to `0.0.0.0` or `true` to listen on all addresses, including LAN and public addresses. This can be set via the CLI using `--host 0.0.0.0` or `--host`. **NOTE** There are cases when other servers might respond instead of Vite. The first case is when `localhost` is used. Node.js under v17 reorders the result of DNS-resolved addresses by default. When accessing `localhost`, browsers use DNS to resolve the address and that address might differ from the address which Vite is listening to. Vite prints the resolved address when it differs. You can set [`dns.setDefaultResultOrder('verbatim')`](https://nodejs.org/api/dns.html#dns_dns_setdefaultresultorder_order) to disable the reordering behavior. Vite will then print the address as `localhost`. vite.config.js import { defineConfig } from 'vite' import dns from 'node:dns' dns.setDefaultResultOrder('verbatim') export default defineConfig({ // omit }) The second case is when wildcard hosts (e.g. `0.0.0.0`) are used. This is because servers listening on non-wildcard hosts take priority over those listening on wildcard hosts. **Accessing the server on WSL2 from your LAN** When running Vite on WSL2, it is not sufficient to set `host: true` to access the server from your LAN. See [the WSL document](https://learn.microsoft.com/en-us/windows/wsl/networking#accessing- a-wsl-2-distribution-from-your-local-area-network-lan) for more details. ## server.allowedHosts * **Type:** `string[] | true` * **Default:** `[]` The hostnames that Vite is allowed to respond to. `localhost` and domains under `.localhost` and all IP addresses are allowed by default. When using HTTPS, this check is skipped. If a string starts with `.`, it will allow that hostname without the `.` and all subdomains under the hostname. For example, `.example.com` will allow `example.com`, `foo.example.com`, and `foo.bar.example.com`. If set to `true`, the server is allowed to respond to requests for any hosts. What hosts are safe to be added? Hosts that you have control over which IP addresses they resolve to are safe to add to the list of allowed hosts. For example, if you own a domain `vite.dev`, you can add `vite.dev` and `.vite.dev` to the list. If you don't own that domain and you cannot trust the owner of that domain, you should not add it. Especially, you should never add Top-Level Domains like `.com` to the list. This is because anyone can purchase a domain like `example.com` and control the IP address it resolves to. **DANGER** Setting `server.allowedHosts` to `true` allows any website to send requests to your dev server through DNS rebinding attacks, allowing them to download your source code and content. We recommend always using an explicit list of allowed hosts. See [GHSA-vg6x-rcgg- rjx6](https://github.com/vitejs/vite/security/advisories/GHSA-vg6x-rcgg-rjx6) for more details. Configure via environment variable You can set the environment variable `__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS` to add an additional allowed host. ## server.port * **Type:** `number` * **Default:** `5173` Specify server port. Note if the port is already being used, Vite will automatically try the next available port so this may not be the actual port the server ends up listening on. ## server.strictPort * **Type:** `boolean` Set to `true` to exit if port is already in use, instead of automatically trying the next available port. ## server.https * **Type:** `https.ServerOptions` Enable TLS + HTTP/2. The value is an [options object](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener) passed to `https.createServer()`. Note that this downgrades to TLS only when the `server.proxy` option is also used. A valid certificate is needed. For a basic setup, you can add [@vitejs/plugin- basic-ssl](https://github.com/vitejs/vite-plugin-basic-ssl) to the project plugins, which will automatically create and cache a self-signed certificate. But we recommend creating your own certificates. ## server.open * **Type:** `boolean | string` Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname. If you want to open the server in a specific browser you like, you can set the env `process.env.BROWSER` (e.g. `firefox`). You can also set `process.env.BROWSER_ARGS` to pass additional arguments (e.g. `--incognito`). `BROWSER` and `BROWSER_ARGS` are also special environment variables you can set in the `.env` file to configure it. See [the `open` package](https://github.com/sindresorhus/open#app) for more details. **Example:** export default defineConfig({ server: { open: '/docs/index.html', }, }) ## server.proxy * **Type:** `Record` Configure custom proxy rules for the dev server. Expects an object of `{ key: options }` pairs. Any requests that request path starts with that key will be proxied to that specified target. If the key starts with `^`, it will be interpreted as a `RegExp`. The `configure` option can be used to access the proxy instance. If a request matches any of the configured proxy rules, the request won't be transformed by Vite. Note that if you are using non-relative [`base`](shared-options#base), you must prefix each key with that `base`. Extends [`http-proxy`](https://github.com/http-party/node-http-proxy#options). Additional options are [here](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/proxy.ts#L13). In some cases, you might also want to configure the underlying dev server (e.g. to add custom middlewares to the internal [connect](https://github.com/senchalabs/connect) app). In order to do that, you need to write your own [plugin](../guide/using-plugins) and use [configureServer](../guide/api-plugin#configureserver) function. **Example:** export default defineConfig({ server: { proxy: { // string shorthand: // http://localhost:5173/foo // -> http://localhost:4567/foo '/foo': 'http://localhost:4567', // with options: // http://localhost:5173/api/bar // -> http://jsonplaceholder.typicode.com/bar '/api': { target: 'http://jsonplaceholder.typicode.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), }, // with RegExp: // http://localhost:5173/fallback/ // -> http://jsonplaceholder.typicode.com/ '^/fallback/.*': { target: 'http://jsonplaceholder.typicode.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/fallback/, ''), }, // Using the proxy instance '/api': { target: 'http://jsonplaceholder.typicode.com', changeOrigin: true, configure: (proxy, options) => { // proxy will be an instance of 'http-proxy' }, }, // Proxying websockets or socket.io: // ws://localhost:5173/socket.io // -> ws://localhost:5174/socket.io // Exercise caution using `rewriteWsOrigin` as it can leave the // proxying open to CSRF attacks. '/socket.io': { target: 'ws://localhost:5174', ws: true, rewriteWsOrigin: true, }, }, }, }) ## server.cors * **Type:** `boolean | CorsOptions` * **Default:** `{ origin: /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/ }` (allows localhost, `127.0.0.1` and `::1`) Configure CORS for the dev server. Pass an [options object](https://github.com/expressjs/cors#configuration-options) to fine tune the behavior or `true` to allow any origin. **DANGER** Setting `server.cors` to `true` allows any website to send requests to your dev server and download your source code and content. We recommend always using an explicit list of allowed origins. ## server.headers * **Type:** `OutgoingHttpHeaders` Specify server response headers. ## server.hmr * **Type:** `boolean | { protocol?: string, host?: string, port?: number, path?: string, timeout?: number, overlay?: boolean, clientPort?: number, server?: Server }` Disable or configure HMR connection (in cases where the HMR websocket must use a different address from the http server). Set `server.hmr.overlay` to `false` to disable the server error overlay. `protocol` sets the WebSocket protocol used for the HMR connection: `ws` (WebSocket) or `wss` (WebSocket Secure). `clientPort` is an advanced option that overrides the port only on the client side, allowing you to serve the websocket on a different port than the client code looks for it on. When `server.hmr.server` is defined, Vite will process the HMR connection requests through the provided server. If not in middleware mode, Vite will attempt to process HMR connection requests through the existing server. This can be helpful when using self-signed certificates or when you want to expose Vite over a network on a single port. Check out [`vite-setup-catalogue`](https://github.com/sapphi-red/vite-setup- catalogue) for some examples. **NOTE** With the default configuration, reverse proxies in front of Vite are expected to support proxying WebSocket. If the Vite HMR client fails to connect WebSocket, the client will fall back to connecting the WebSocket directly to the Vite HMR server bypassing the reverse proxies: Direct websocket connection fallback. Check out https://vite.dev/config/server-options.html#server-hmr to remove the previous connection error. The error that appears in the Browser when the fallback happens can be ignored. To avoid the error by directly bypassing reverse proxies, you could either: * configure the reverse proxy to proxy WebSocket too * set `server.strictPort = true` and set `server.hmr.clientPort` to the same value with `server.port` * set `server.hmr.port` to a different value from `server.port` ## server.warmup * **Type:** `{ clientFiles?: string[], ssrFiles?: string[] }` * **Related:** [Warm Up Frequently Used Files](../guide/performance#warm-up-frequently-used-files) Warm up files to transform and cache the results in advance. This improves the initial page load during server starts and prevents transform waterfalls. `clientFiles` are files that are used in the client only, while `ssrFiles` are files that are used in SSR only. They accept an array of file paths or [`tinyglobby`](https://github.com/SuperchupuDev/tinyglobby) patterns relative to the `root`. Make sure to only add files that are frequently used to not overload the Vite dev server on startup. export default defineConfig({ server: { warmup: { clientFiles: ['./src/components/*.vue', './src/utils/big-utils.js'], ssrFiles: ['./src/server/modules/*.js'], }, }, }) ## server.watch * **Type:** `object | null` File system watcher options to pass on to [chokidar](https://github.com/paulmillr/chokidar/tree/3.6.0#api). The Vite server watcher watches the `root` and skips the `.git/`, `node_modules/`, and Vite's `cacheDir` and `build.outDir` directories by default. When updating a watched file, Vite will apply HMR and update the page only if needed. If set to `null`, no files will be watched. `server.watcher` will provide a compatible event emitter, but calling `add` or `unwatch` will have no effect. **Watching files in`node_modules`** It's currently not possible to watch files and packages in `node_modules`. For further progress and workarounds, you can follow [issue #8619](https://github.com/vitejs/vite/issues/8619). **Using Vite on Windows Subsystem for Linux (WSL) 2** When running Vite on WSL2, file system watching does not work when a file is edited by Windows applications (non-WSL2 process). This is due to [a WSL2 limitation](https://github.com/microsoft/WSL/issues/4739). This also applies to running on Docker with a WSL2 backend. To fix it, you could either: * **Recommended** : Use WSL2 applications to edit your files. * It is also recommended to move the project folder outside of a Windows filesystem. Accessing Windows filesystem from WSL2 is slow. Removing that overhead will improve performance. * Set `{ usePolling: true }`. * Note that [`usePolling` leads to high CPU utilization](https://github.com/paulmillr/chokidar/tree/3.6.0#performance). ## server.middlewareMode * **Type:** `boolean` * **Default:** `false` Create Vite server in middleware mode. * **Related:** [appType](shared-options#apptype), [SSR - Setting Up the Dev Server](../guide/ssr#setting-up-the-dev-server) * **Example:** import express from 'express' import { createServer as createViteServer } from 'vite' async function createServer() { const app = express() // Create Vite server in middleware mode const vite = await createViteServer({ server: { middlewareMode: true }, // don't include Vite's default HTML handling middlewares appType: 'custom', }) // Use vite's connect instance as middleware app.use(vite.middlewares) app.use('*', async (req, res) => { // Since `appType` is `'custom'`, should serve response here. // Note: if `appType` is `'spa'` or `'mpa'`, Vite includes middlewares // to handle HTML requests and 404s so user middlewares should be added // before Vite's middlewares to take effect instead }) } createServer() ## server.fs.strict * **Type:** `boolean` * **Default:** `true` (enabled by default since Vite 2.7) Restrict serving files outside of workspace root. ## server.fs.allow * **Type:** `string[]` Restrict files that could be served via `/@fs/`. When `server.fs.strict` is set to `true`, accessing files outside this directory list that aren't imported from an allowed file will result in a 403. Both directories and files can be provided. Vite will search for the root of the potential workspace and use it as default. A valid workspace met the following conditions, otherwise will fall back to the [project root](../guide/index#index-html-and-project-root). * contains `workspaces` field in `package.json` * contains one of the following file * `lerna.json` * `pnpm-workspace.yaml` Accepts a path to specify the custom workspace root. Could be a absolute path or a path relative to [project root](../guide/index#index-html-and-project- root). For example: export default defineConfig({ server: { fs: { // Allow serving files from one level up to the project root allow: ['..'], }, }, }) When `server.fs.allow` is specified, the auto workspace root detection will be disabled. To extend the original behavior, a utility `searchForWorkspaceRoot` is exposed: import { defineConfig, searchForWorkspaceRoot } from 'vite' export default defineConfig({ server: { fs: { allow: [ // search up for workspace root searchForWorkspaceRoot(process.cwd()), // your custom rules '/path/to/custom/allow_directory', '/path/to/custom/allow_file.demo', ], }, }, }) ## server.fs.deny * **Type:** `string[]` * **Default:** `['.env', '.env.*', '*.{crt,pem}', '**/.git/**']` Blocklist for sensitive files being restricted to be served by Vite dev server. This will have higher priority than `server.fs.allow`. [picomatch patterns](https://github.com/micromatch/picomatch#globbing-features) are supported. **NOTE** This blocklist does not apply to [the public directory](../guide/assets#the- public-directory). All files in the public directory are served without any filtering, since they are copied directly to the output directory during build. ## server.origin * **Type:** `string` Defines the origin of the generated asset URLs during development. export default defineConfig({ server: { origin: 'http://127.0.0.1:8080', }, }) ## server.sourcemapIgnoreList * **Type:** `false | (sourcePath: string, sourcemapPath: string) => boolean` * **Default:** `(sourcePath) => sourcePath.includes('node_modules')` Whether or not to ignore source files in the server sourcemap, used to populate the [`x_google_ignoreList` source map extension](https://developer.chrome.com/articles/x-google-ignore-list/). `server.sourcemapIgnoreList` is the equivalent of [`build.rollupOptions.output.sourcemapIgnoreList`](https://rollupjs.org/configuration- options/#output-sourcemapignorelist) for the dev server. A difference between the two config options is that the rollup function is called with a relative path for `sourcePath` while `server.sourcemapIgnoreList` is called with an absolute path. During dev, most modules have the map and the source in the same folder, so the relative path for `sourcePath` is the file name itself. In these cases, absolute paths makes it convenient to be used instead. By default, it excludes all paths containing `node_modules`. You can pass `false` to disable this behavior, or, for full control, a function that takes the source path and sourcemap path and returns whether to ignore the source path. export default defineConfig({ server: { // This is the default value, and will add all files with node_modules // in their paths to the ignore list. sourcemapIgnoreList(sourcePath, sourcemapPath) { return sourcePath.includes('node_modules') }, }, }) **Note** `server.sourcemapIgnoreList` and [`build.rollupOptions.output.sourcemapIgnoreList`](https://rollupjs.org/configuration- options/#output-sourcemapignorelist) need to be set independently. `server.sourcemapIgnoreList` is a server only config and doesn't get its default value from the defined rollup options. # Shared Options Unless noted, the options in this section are applied to all dev, build, and preview. ## root * **Type:** `string` * **Default:** `process.cwd()` Project root directory (where `index.html` is located). Can be an absolute path, or a path relative to the current working directory. See [Project Root](../guide/index#index-html-and-project-root) for more details. ## base * **Type:** `string` * **Default:** `/` * **Related:** [`server.origin`](server-options#server-origin) Base public path when served in development or production. Valid values include: * Absolute URL pathname, e.g. `/foo/` * Full URL, e.g. `https://bar.com/foo/` (The origin part won't be used in development so the value is the same as `/foo/`) * Empty string or `./` (for embedded deployment) See [Public Base Path](../guide/build#public-base-path) for more details. ## mode * **Type:** `string` * **Default:** `'development'` for serve, `'production'` for build Specifying this in config will override the default mode for **both serve and build**. This value can also be overridden via the command line `--mode` option. See [Env Variables and Modes](../guide/env-and-mode) for more details. ## define * **Type:** `Record` Define global constant replacements. Entries will be defined as globals during dev and statically replaced during build. Vite uses [esbuild defines](https://esbuild.github.io/api/#define) to perform replacements, so value expressions must be a string that contains a JSON- serializable value (null, boolean, number, string, array, or object) or a single identifier. For non-string values, Vite will automatically convert it to a string with `JSON.stringify`. **Example:** export default defineConfig({ define: { __APP_VERSION__: JSON.stringify('v1.0.0'), __API_URL__: 'window.__backend_api_url', }, }) **NOTE** For TypeScript users, make sure to add the type declarations in the `vite- env.d.ts` file to get type checks and Intellisense. Example: // vite-env.d.ts declare const __APP_VERSION__: string ## plugins * **Type:** `(Plugin | Plugin[] | Promise)[]` Array of plugins to use. Falsy plugins are ignored and arrays of plugins are flattened. If a promise is returned, it would be resolved before running. See [Plugin API](../guide/api-plugin) for more details on Vite plugins. ## publicDir * **Type:** `string | false` * **Default:** `"public"` Directory to serve as plain static assets. Files in this directory are served at `/` during dev and copied to the root of `outDir` during build, and are always served or copied as-is without transform. The value can be either an absolute file system path or a path relative to project root. Defining `publicDir` as `false` disables this feature. See [The `public` Directory](../guide/assets#the-public-directory) for more details. ## cacheDir * **Type:** `string` * **Default:** `"node_modules/.vite"` Directory to save cache files. Files in this directory are pre-bundled deps or some other cache files generated by vite, which can improve the performance. You can use `--force` flag or manually delete the directory to regenerate the cache files. The value can be either an absolute file system path or a path relative to project root. Default to `.vite` when no package.json is detected. ## resolve.alias * **Type:**`Record | Array<{ find: string | RegExp, replacement: string, customResolver?: ResolverFunction | ResolverObject }>` Will be passed to `@rollup/plugin-alias` as its [entries option](https://github.com/rollup/plugins/tree/master/packages/alias#entries). Can either be an object, or an array of `{ find, replacement, customResolver }` pairs. When aliasing to file system paths, always use absolute paths. Relative alias values will be used as-is and will not be resolved into file system paths. More advanced custom resolution can be achieved through [plugins](../guide/api-plugin). **Using with SSR** If you have configured aliases for [SSR externalized dependencies](../guide/ssr#ssr-externals), you may want to alias the actual `node_modules` packages. Both [Yarn](https://classic.yarnpkg.com/en/docs/cli/add/#toc-yarn-add-alias) and [pnpm](https://pnpm.io/aliases/) support aliasing via the `npm:` prefix. ## resolve.dedupe * **Type:** `string[]` If you have duplicated copies of the same dependency in your app (likely due to hoisting or linked packages in monorepos), use this option to force Vite to always resolve listed dependencies to the same copy (from project root). **SSR + ESM** For SSR builds, deduplication does not work for ESM build outputs configured from `build.rollupOptions.output`. A workaround is to use CJS build outputs until ESM has better plugin support for module loading. ## resolve.conditions * **Type:** `string[]` * **Default:** `['module', 'browser', 'development|production']` (`defaultClientConditions`) Additional allowed conditions when resolving [Conditional Exports](https://nodejs.org/api/packages.html#packages_conditional_exports) from a package. A package with conditional exports may have the following `exports` field in its `package.json`: { "exports": { ".": { "import": "./index.mjs", "require": "./index.js" } } } Here, `import` and `require` are "conditions". Conditions can be nested and should be specified from most specific to least specific. `development|production` is a special value that is replaced with `production` or `development` depending on the value of `process.env.NODE_ENV`. It is replaced with `production` when `process.env.NODE_ENV === 'production'` and `development` otherwise. Note that `import`, `require`, `default` conditions are always applied if the requirements are met. ## resolve.mainFields * **Type:** `string[]` * **Default:** `['browser', 'module', 'jsnext:main', 'jsnext']` (`defaultClientMainFields`) List of fields in `package.json` to try when resolving a package's entry point. Note this takes lower precedence than conditional exports resolved from the `exports` field: if an entry point is successfully resolved from `exports`, the main field will be ignored. ## resolve.extensions * **Type:** `string[]` * **Default:** `['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']` List of file extensions to try for imports that omit extensions. Note it is **NOT** recommended to omit extensions for custom import types (e.g. `.vue`) since it can interfere with IDE and type support. ## resolve.preserveSymlinks * **Type:** `boolean` * **Default:** `false` Enabling this setting causes vite to determine file identity by the original file path (i.e. the path without following symlinks) instead of the real file path (i.e. the path after following symlinks). * **Related:** [esbuild#preserve-symlinks](https://esbuild.github.io/api/#preserve-symlinks), [webpack#resolve.symlinks ](https://webpack.js.org/configuration/resolve/#resolvesymlinks) ## html.cspNonce * **Type:** `string` * **Related:** [Content Security Policy (CSP)](../guide/features#content-security-policy-csp) A nonce value placeholder that will be used when generating script / style tags. Setting this value will also generate a meta tag with nonce value. ## css.modules * **Type:** interface CSSModulesOptions { getJSON?: ( cssFileName: string, json: Record, outputFileName: string, ) => void scopeBehaviour?: 'global' | 'local' globalModulePaths?: RegExp[] exportGlobals?: boolean generateScopedName?: | string | ((name: string, filename: string, css: string) => string) hashPrefix?: string /** * default: undefined */ localsConvention?: | 'camelCase' | 'camelCaseOnly' | 'dashes' | 'dashesOnly' | (( originalClassName: string, generatedClassName: string, inputFile: string, ) => string) } Configure CSS modules behavior. The options are passed on to [postcss- modules](https://github.com/css-modules/postcss-modules). This option doesn't have any effect when using [Lightning CSS](../guide/features#lightning-css). If enabled, [`css.lightningcss.cssModules`](https://lightningcss.dev/css-modules.html) should be used instead. ## css.postcss * **Type:** `string | (postcss.ProcessOptions & { plugins?: postcss.AcceptedPlugin[] })` Inline PostCSS config or a custom directory to search PostCSS config from (default is project root). For inline PostCSS config, it expects the same format as `postcss.config.js`. But for `plugins` property, only [array format](https://github.com/postcss/postcss-load- config/blob/main/README.md#array) can be used. The search is done using [postcss-load- config](https://github.com/postcss/postcss-load-config) and only the supported config file names are loaded. Config files outside the workspace root (or the [project root](../guide/index#index-html-and-project-root) if no workspace is found) are not searched by default. You can specify a custom path outside of the root to load the specific config file instead if needed. Note if an inline config is provided, Vite will not search for other PostCSS config sources. ## css.preprocessorOptions * **Type:** `Record` Specify options to pass to CSS pre-processors. The file extensions are used as keys for the options. The supported options for each preprocessor can be found in their respective documentation: * `sass`/`scss`: * Uses `sass-embedded` if installed, otherwise uses `sass`. For the best performance, it's recommended to install the `sass-embedded` package. * [Options](https://sass-lang.com/documentation/js-api/interfaces/stringoptions/) * `less`: [Options](https://lesscss.org/usage/#less-options). * `styl`/`stylus`: Only [`define`](https://stylus-lang.com/docs/js.html#define-name-node) is supported, which can be passed as an object. **Example:** export default defineConfig({ css: { preprocessorOptions: { less: { math: 'parens-division', }, styl: { define: { $specialColor: new stylus.nodes.RGBA(51, 197, 255, 1), }, }, scss: { importers: [ // ... ], }, }, }, }) ### css.preprocessorOptions[extension].additionalData * **Type:** `string | ((source: string, filename: string) => (string | { content: string; map?: SourceMap }))` This option can be used to inject extra code for each style content. Note that if you include actual styles and not just variables, those styles will be duplicated in the final bundle. **Example:** export default defineConfig({ css: { preprocessorOptions: { scss: { additionalData: `$injectedColor: orange;`, }, }, }, }) ## css.preprocessorMaxWorkers * **Type:** `number | true` * **Default:** `true` Specifies the maximum number of threads CSS preprocessors can use. `true` means up to the number of CPUs minus 1. When set to `0`, Vite will not create any workers and will run the preprocessors in the main thread. Depending on the preprocessor options, Vite may run the preprocessors on the main thread even if this option is not set to `0`. ## css.devSourcemap * **Experimental:** [Give Feedback](https://github.com/vitejs/vite/discussions/13845) * **Type:** `boolean` * **Default:** `false` Whether to enable sourcemaps during dev. ## css.transformer * **Experimental:** [Give Feedback](https://github.com/vitejs/vite/discussions/13835) * **Type:** `'postcss' | 'lightningcss'` * **Default:** `'postcss'` Selects the engine used for CSS processing. Check out [Lightning CSS](../guide/features#lightning-css) for more information. **Duplicate`@import`s** Note that postcss (postcss-import) has a different behavior with duplicated `@import` from browsers. See [postcss/postcss- import#462](https://github.com/postcss/postcss-import/issues/462). ## css.lightningcss * **Experimental:** [Give Feedback](https://github.com/vitejs/vite/discussions/13835) * **Type:** import type { CSSModulesConfig, Drafts, Features, NonStandard, PseudoClasses, Targets, } from 'lightningcss' { targets?: Targets include?: Features exclude?: Features drafts?: Drafts nonStandard?: NonStandard pseudoClasses?: PseudoClasses unusedSymbols?: string[] cssModules?: CSSModulesConfig, // ... } Configures Lightning CSS. Full transform options can be found in [the Lightning CSS repo](https://github.com/parcel- bundler/lightningcss/blob/master/node/index.d.ts). ## json.namedExports * **Type:** `boolean` * **Default:** `true` Whether to support named imports from `.json` files. ## json.stringify * **Type:** `boolean | 'auto'` * **Default:** `'auto'` If set to `true`, imported JSON will be transformed into `export default JSON.parse("...")` which is significantly more performant than Object literals, especially when the JSON file is large. If set to `'auto'`, the data will be stringified only if [the data is bigger than 10kB](https://v8.dev/blog/cost-of- javascript-2019#json:~:text=A%20good%20rule%20of%20thumb%20is%20to%20apply%20this%20technique%20for%20objects%20of%2010%20kB%20or%20larger). ## esbuild * **Type:** `ESBuildOptions | false` `ESBuildOptions` extends [esbuild's own transform options](https://esbuild.github.io/api/#transform). The most common use case is customizing JSX: export default defineConfig({ esbuild: { jsxFactory: 'h', jsxFragment: 'Fragment', }, }) By default, esbuild is applied to `ts`, `jsx` and `tsx` files. You can customize this with `esbuild.include` and `esbuild.exclude`, which can be a regex, a [picomatch](https://github.com/micromatch/picomatch#globbing- features) pattern, or an array of either. In addition, you can also use `esbuild.jsxInject` to automatically inject JSX helper imports for every file transformed by esbuild: export default defineConfig({ esbuild: { jsxInject: `import React from 'react'`, }, }) When [`build.minify`](build-options#build-minify) is `true`, all minify optimizations are applied by default. To disable [certain aspects](https://esbuild.github.io/api/#minify) of it, set any of `esbuild.minifyIdentifiers`, `esbuild.minifySyntax`, or `esbuild.minifyWhitespace` options to `false`. Note the `esbuild.minify` option can't be used to override `build.minify`. Set to `false` to disable esbuild transforms. ## assetsInclude * **Type:** `string | RegExp | (string | RegExp)[]` * **Related:** [Static Asset Handling](../guide/assets) Specify additional [picomatch patterns](https://github.com/micromatch/picomatch#globbing-features) to be treated as static assets so that: * They will be excluded from the plugin transform pipeline when referenced from HTML or directly requested over `fetch` or XHR. * Importing them from JS will return their resolved URL string (this can be overwritten if you have a `enforce: 'pre'` plugin to handle the asset type differently). The built-in asset type list can be found [here](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/constants.ts). **Example:** export default defineConfig({ assetsInclude: ['**/*.gltf'], }) ## logLevel * **Type:** `'info' | 'warn' | 'error' | 'silent'` Adjust console output verbosity. Default is `'info'`. ## customLogger * **Type:** interface Logger { info(msg: string, options?: LogOptions): void warn(msg: string, options?: LogOptions): void warnOnce(msg: string, options?: LogOptions): void error(msg: string, options?: LogErrorOptions): void clearScreen(type: LogType): void hasErrorLogged(error: Error | RollupError): boolean hasWarned: boolean } Use a custom logger to log messages. You can use Vite's `createLogger` API to get the default logger and customize it to, for example, change the message or filter out certain warnings. import { createLogger, defineConfig } from 'vite' const logger = createLogger() const loggerWarn = logger.warn logger.warn = (msg, options) => { // Ignore empty CSS files warning if (msg.includes('vite:css') && msg.includes(' is empty')) return loggerWarn(msg, options) } export default defineConfig({ customLogger: logger, }) ## clearScreen * **Type:** `boolean` * **Default:** `true` Set to `false` to prevent Vite from clearing the terminal screen when logging certain messages. Via command line, use `--clearScreen false`. ## envDir * **Type:** `string | false` * **Default:** `root` The directory from which `.env` files are loaded. Can be an absolute path, or a path relative to the project root. `false` will disable the `.env` file loading. See [here](../guide/env-and-mode#env-files) for more about environment files. ## envPrefix * **Type:** `string | string[]` * **Default:** `VITE_` Env variables starting with `envPrefix` will be exposed to your client source code via `import.meta.env`. **SECURITY NOTES** `envPrefix` should not be set as `''`, which will expose all your env variables and cause unexpected leaking of sensitive information. Vite will throw an error when detecting `''`. If you would like to expose an unprefixed variable, you can use define to expose it: define: { 'import.meta.env.ENV_VARIABLE': JSON.stringify(process.env.ENV_VARIABLE) } ## appType * **Type:** `'spa' | 'mpa' | 'custom'` * **Default:** `'spa'` Whether your application is a Single Page Application (SPA), a [Multi Page Application (MPA)](../guide/build#multi-page-app), or Custom Application (SSR and frameworks with custom HTML handling): * `'spa'`: include HTML middlewares and use SPA fallback. Configure [sirv](https://github.com/lukeed/sirv) with `single: true` in preview * `'mpa'`: include HTML middlewares * `'custom'`: don't include HTML middlewares Learn more in Vite's [SSR guide](../guide/ssr#vite-cli). Related: [`server.middlewareMode`](server-options#server-middlewaremode). ## future * **Type:** `Record` * **Related:** [Breaking Changes](../changes/index) Enable future breaking changes to prepare for a smooth migration to the next major version of Vite. The list may be updated, added, or removed at any time as new features are developed. See the [Breaking Changes](../changes/index) page for details of the possible options. # SSR Options Unless noted, the options in this section are applied to both dev and build. ## ssr.external * **Type:** `string[] | true` * **Related:** [SSR Externals](../guide/ssr#ssr-externals) Externalize the given dependencies and their transitive dependencies for SSR. By default, all dependencies are externalized except for linked dependencies (for HMR). If you prefer to externalize the linked dependency, you can pass its name to this option. If `true`, all dependencies including linked dependencies are externalized. Note that the explicitly listed dependencies (using `string[]` type) will always take priority if they're also listed in `ssr.noExternal` (using any type). ## ssr.noExternal * **Type:** `string | RegExp | (string | RegExp)[] | true` * **Related:** [SSR Externals](../guide/ssr#ssr-externals) Prevent listed dependencies from being externalized for SSR, which they will get bundled in build. By default, only linked dependencies are not externalized (for HMR). If you prefer to externalize the linked dependency, you can pass its name to the `ssr.external` option. If `true`, no dependencies are externalized. However, dependencies explicitly listed in `ssr.external` (using `string[]` type) can take priority and still be externalized. If `ssr.target: 'node'` is set, Node.js built-ins will also be externalized by default. Note that if both `ssr.noExternal: true` and `ssr.external: true` are configured, `ssr.noExternal` takes priority and no dependencies are externalized. ## ssr.target * **Type:** `'node' | 'webworker'` * **Default:** `node` Build target for the SSR server. ## ssr.resolve.conditions * **Type:** `string[]` * **Default:** `['module', 'node', 'development|production']` (`defaultServerConditions`) (`['module', 'browser', 'development|production']` (`defaultClientConditions`) for `ssr.target === 'webworker'`) * **Related:** [Resolve Conditions](shared-options#resolve-conditions) These conditions are used in the plugin pipeline, and only affect non- externalized dependencies during the SSR build. Use `ssr.resolve.externalConditions` to affect externalized imports. ## ssr.resolve.externalConditions * **Type:** `string[]` * **Default:** `['node']` Conditions that are used during ssr import (including `ssrLoadModule`) of externalized direct dependencies (external dependencies imported by Vite). **TIP** When using this option, make sure to run Node with [`--conditions` flag](https://nodejs.org/docs/latest/api/cli.html#-c-condition--- conditionscondition) with the same values in both dev and build to get a consistent behavior. For example, when setting `['node', 'custom']`, you should run `NODE_OPTIONS=' --conditions custom' vite` in dev and `NODE_OPTIONS="--conditions custom" node ./dist/server.js` after build. ## ssr.resolve.mainFields * **Type:** `string[]` * **Default:** `['module', 'jsnext:main', 'jsnext']` List of fields in `package.json` to try when resolving a package's entry point. Note this takes lower precedence than conditional exports resolved from the `exports` field: if an entry point is successfully resolved from `exports`, the main field will be ignored. This setting only affect non- externalized dependencies. # Worker Options Unless noted, the options in this section are applied to all dev, build, and preview. ## worker.format * **Type:** `'es' | 'iife'` * **Default:** `'iife'` Output format for worker bundle. ## worker.plugins * **Type:** [`() => (Plugin | Plugin[])[]`](shared-options#plugins) Vite plugins that apply to the worker bundles. Note that [config.plugins](shared-options#plugins) only applies to workers in dev, it should be configured here instead for build. The function should return new plugin instances as they are used in parallel rollup worker builds. As such, modifying `config.worker` options in the `config` hook will be ignored. ## worker.rollupOptions * **Type:** [`RollupOptions`](https://rollupjs.org/configuration-options/) Rollup options to build worker bundle. # Environment API for Frameworks **Experimental** Environment API is experimental. We'll still maintain stability in the APIs between major releases to allow the ecosystem to experiment and build upon them. We plan to stabilize these new APIs (with potential breaking changes) in a future major release once downstream projects have had time to experiment with the new features and validate them. 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. 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: 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](ssr#setting-up-the-dev-server), let's implement the SSR middleware using the environment API. Remember that it doesn't have to be called `ssr`, so we'll name it `server` in this example. Error handling is omitted. 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 viteServer = 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 serverEnvironment = viteServer.environments.server 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 viteServer.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 serverEnvironment.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. **`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: // 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. // 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: 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. // 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: export default { builder: { buildApp: async (builder) => { const environments = Object.values(builder.environments) return Promise.all( environments.map((environment) => builder.build(environment)), ) }, }, } Plugins can also define a `buildApp` hook. Order `'pre'` and `null` are executed before the configured `builder.buildApp`, and order `'post'` hooks are executed after it. `environment.isBuilt` can be used to check if an environment has already being build. ## 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) to learn about how to build environment aware plugins. # Using `Environment` Instances **Experimental** Environment API is experimental. We'll still maintain stability in the APIs between major releases to allow the ecosystem to experiment and build upon them. We plan to stabilize these new APIs (with potential breaking changes) in a future major release once downstream projects have had time to experiment with the new features and validate them. 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 Environments During dev, the available environments in a dev server can be accessed using `server.environments`: // create the server, or get it from the configureServer hook const server = await createServer(/* options */) const clientEnvironment = server.environments.client clientEnvironment.transformRequest(url) console.log(server.environments.ssr.moduleGraph) You can also access the current environment from plugins. See the [Environment API for Plugins](api-environment-plugins#accessing-the-current-environment-in- hooks) for more details. ## `DevEnvironment` class During dev, each environment is an instance of the `DevEnvironment` class: class DevEnvironment { /** * Unique identifier for the environment in a Vite server. * By default Vite exposes 'client' and 'ssr' environments. */ name: string /** * Communication channel to send and receive messages from the * associated module runner in the target runtime. */ hot: NormalizedHotChannel /** * Graph of module nodes, with the imported relationship between * processed modules and the cached result of the processed code. */ moduleGraph: EnvironmentModuleGraph /** * Resolved plugins for this environment, including the ones * created using the per-environment `create` hook */ plugins: Plugin[] /** * Allows to resolve, load, and transform code through the * environment plugins pipeline */ pluginContainer: EnvironmentPluginContainer /** * Resolved config options for this environment. Options at the server * global scope are taken as defaults for all environments, and can * be overridden (resolve conditions, external, optimizedDeps) */ config: ResolvedConfig & ResolvedDevEnvironmentOptions constructor( name: string, config: ResolvedConfig, context: DevEnvironmentContext, ) /** * Resolve the URL to an id, load it, and process the code using the * plugins pipeline. The module graph is also updated. */ async transformRequest(url: string): Promise /** * Register a request to be processed with low priority. This is useful * to avoid waterfalls. The Vite server has information about the * imported modules by other requests, so it can warmup the module graph * so the modules are already processed when they are requested. */ async warmupRequest(url: string): Promise } With `DevEnvironmentContext` being: interface DevEnvironmentContext { hot: boolean transport?: HotChannel | WebSocketServer options?: EnvironmentOptions remoteRunner?: { inlineSourceMap?: boolean } depsOptimizer?: DepsOptimizer } and with `TransformResult` being: interface TransformResult { code: string map: SourceMap | { mappings: '' } | null etag?: string deps?: string[] dynamicDeps?: string[] } An environment instance in the Vite server lets you process a URL using the `environment.transformRequest(url)` method. This function will use the plugin pipeline to resolve the `url` to a module `id`, load it (reading the file from the file system or through a plugin that implements a virtual module), and then transform the code. While transforming the module, imports and other metadata will be recorded in the environment module graph by creating or updating the corresponding module node. When processing is done, the transform result is also stored in the module. **transformRequest naming** We are using `transformRequest(url)` and `warmupRequest(url)` in the current version of this proposal so it is easier to discuss and understand for users used to Vite's current API. Before releasing, we can take the opportunity to review these names too. For example, it could be named `environment.processModule(url)` or `environment.loadModule(url)` taking a page from Rollup's `context.load(id)` in plugin hooks. For the moment, we think keeping the current names and delaying this discussion is better. ## Separate Module Graphs Each environment has an isolated module graph. All module graphs have the same signature, so generic algorithms can be implemented to crawl or query the graph without depending on the environment. `hotUpdate` is a good example. When a file is modified, the module graph of each environment will be used to discover the affected modules and perform HMR for each environment independently. **INFO** Vite v5 had a mixed Client and SSR module graph. Given an unprocessed or invalidated node, it isn't possible to know if it corresponds to the Client, SSR, or both environments. Module nodes have some properties prefixed, like `clientImportedModules` and `ssrImportedModules` (and `importedModules` that returns the union of both). `importers` contains all importers from both the Client and SSR environment for each module node. A module node also has `transformResult` and `ssrTransformResult`. A backward compatibility layer allows the ecosystem to migrate from the deprecated `server.moduleGraph`. Each module is represented by a `EnvironmentModuleNode` instance. Modules may be registered in the graph without yet being processed (`transformResult` would be `null` in that case). `importers` and `importedModules` are also updated after the module is processed. class EnvironmentModuleNode { environment: string url: string id: string | null = null file: string | null = null type: 'js' | 'css' importers = new Set() importedModules = new Set() importedBindings: Map> | null = null info?: ModuleInfo meta?: Record transformResult: TransformResult | null = null acceptedHmrDeps = new Set() acceptedHmrExports: Set | null = null isSelfAccepting?: boolean lastHMRTimestamp = 0 lastInvalidationTimestamp = 0 } `environment.moduleGraph` is an instance of `EnvironmentModuleGraph`: export class EnvironmentModuleGraph { environment: string urlToModuleMap = new Map() idToModuleMap = new Map() etagToModuleMap = new Map() fileToModulesMap = new Map>() constructor( environment: string, resolveId: (url: string) => Promise, ) async getModuleByUrl( rawUrl: string, ): Promise getModuleById(id: string): EnvironmentModuleNode | undefined getModulesByFile(file: string): Set | undefined onFileChange(file: string): void onFileDelete(file: string): void invalidateModule( mod: EnvironmentModuleNode, seen: Set = new Set(), timestamp: number = monotonicDateNow(), isHmr: boolean = false, ): void invalidateAll(): void async ensureEntryFromUrl( rawUrl: string, setIsSelfAccepting = true, ): Promise createFileOnlyEntry(file: string): EnvironmentModuleNode async resolveUrl(url: string): Promise updateModuleTransformResult( mod: EnvironmentModuleNode, result: TransformResult | null, ): void getModuleByEtag(etag: string): EnvironmentModuleNode | undefined } # Environment API for Plugins **Experimental** Environment API is experimental. We'll still maintain stability in the APIs between major releases to allow the ecosystem to experiment and build upon them. We plan to stabilize these new APIs (with potential breaking changes) in a future major release once downstream projects have had time to experiment with the new features and validate them. 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`). 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)): 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. 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: 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: 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: 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): if (import.meta.hot) { import.meta.hot.on('special-update', (data) => { // perform custom update }) } ## Per-environment State in Plugins Given that the same plugin instance is used for different environments, the plugin state needs to be keyed with `this.environment`. This is the same pattern the ecosystem has already been using to keep state about modules using the `ssr` boolean as key to avoid mixing client and ssr modules state. A `Map` can be used to keep the state for each environment separately. Note that for backward compatibility, `buildStart` and `buildEnd` are only called for the client environment without the `perEnvironmentStartEndDuringDev: true` flag. function PerEnvironmentCountTransformedModulesPlugin() { const state = new Map() return { name: 'count-transformed-modules', perEnvironmentStartEndDuringDev: true, buildStart() { state.set(this.environment, { count: 0 }) }, transform(id) { state.get(this.environment).count++ }, buildEnd() { console.log(this.environment.name, state.get(this.environment).count) } } } ## Per-environment Plugins A plugin can define what are the environments it should apply to with the `applyToEnvironment` function. 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. 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: import { nonShareablePlugin } from 'non-shareable-plugin' export default defineConfig({ plugins: [ perEnvironmentPlugin('per-environment-plugin', (environment) => nonShareablePlugin({ outputName: environment.name }), ), ], }) The `applyToEnvironment` hook is called at config time, currently after `configResolved` due to projects in the ecosystem modifying the plugins in it. Environment plugins resolution may be moved before `configResolved` in the future. ## 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, we could have complete alignment: * **During both dev and build:** plugins are shared, with per-environment filtering 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: 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, } } # Environment API for Runtimes **Experimental** Environment API is experimental. We'll still maintain stability in the APIs between major releases to allow the ecosystem to experiment and build upon them. We plan to stabilize these new APIs (with potential breaking changes) in a future major release once downstream projects have had time to experiment with the new features and validate them. 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. 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: 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: 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. 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:** 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:** 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` 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:** 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:** 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: worker.jsserver.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(), ) 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: 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: 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. # Environment API **Experimental** Environment API is experimental. We'll still maintain stability in the APIs between major releases to allow the ecosystem to experiment and build upon them. We plan to stabilize these new APIs (with potential breaking changes) in a future major release once downstream projects have had time to experiment with the new features and validate them. 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. * `ssr`: 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. 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. 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. 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. 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. 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) 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) explains how to offer custom environment to be consumed by frameworks and users. # HMR API **Note** This is the client HMR API. For handling HMR update in plugins, see [handleHotUpdate](api-plugin#handlehotupdate). The manual HMR API is primarily intended for framework and tooling authors. As an end user, HMR is likely already handled for you in the framework specific starter templates. Vite exposes its manual HMR API via the special `import.meta.hot` object: interface ImportMeta { readonly hot?: ViteHotContext } interface ViteHotContext { readonly data: any accept(): void accept(cb: (mod: ModuleNamespace | undefined) => void): void accept(dep: string, cb: (mod: ModuleNamespace | undefined) => void): void accept( deps: readonly string[], cb: (mods: Array) => void, ): void dispose(cb: (data: any) => void): void prune(cb: (data: any) => void): void invalidate(message?: string): void on( event: T, cb: (payload: InferCustomEventPayload) => void, ): void off( event: T, cb: (payload: InferCustomEventPayload) => void, ): void send( event: T, data?: InferCustomEventPayload, ): void } ## Required Conditional Guard First of all, make sure to guard all HMR API usage with a conditional block so that the code can be tree-shaken in production: if (import.meta.hot) { // HMR code } ## IntelliSense for TypeScript Vite provides type definitions for `import.meta.hot` in [`vite/client.d.ts`](https://github.com/vitejs/vite/blob/main/packages/vite/client.d.ts). You can create an `vite-env.d.ts` in the `src` directory so TypeScript picks up the type definitions: vite-env.d.ts /// ## `hot.accept(cb)` For a module to self-accept, use `import.meta.hot.accept` with a callback which receives the updated module: export const count = 1 if (import.meta.hot) { import.meta.hot.accept((newModule) => { if (newModule) { // newModule is undefined when SyntaxError happened console.log('updated: count is now ', newModule.count) } }) } A module that "accepts" hot updates is considered an **HMR boundary**. Vite's HMR does not actually swap the originally imported module: if an HMR boundary module re-exports imports from a dep, then it is responsible for updating those re-exports (and these exports must be using `let`). In addition, importers up the chain from the boundary module will not be notified of the change. This simplified HMR implementation is sufficient for most dev use cases, while allowing us to skip the expensive work of generating proxy modules. Vite requires that the call to this function appears as `import.meta.hot.accept(` (whitespace-sensitive) in the source code in order for the module to accept update. This is a requirement of the static analysis that Vite does to enable HMR support for a module. ## `hot.accept(deps, cb)` A module can also accept updates from direct dependencies without reloading itself: import { foo } from './foo.js' foo() if (import.meta.hot) { import.meta.hot.accept('./foo.js', (newFoo) => { // the callback receives the updated './foo.js' module newFoo?.foo() }) // Can also accept an array of dep modules: import.meta.hot.accept( ['./foo.js', './bar.js'], ([newFooModule, newBarModule]) => { // The callback receives an array where only the updated module is // non null. If the update was not successful (syntax error for ex.), // the array is empty }, ) } ## `hot.dispose(cb)` A self-accepting module or a module that expects to be accepted by others can use `hot.dispose` to clean-up any persistent side effects created by its updated copy: function setupSideEffect() {} setupSideEffect() if (import.meta.hot) { import.meta.hot.dispose((data) => { // cleanup side effect }) } ## `hot.prune(cb)` Register a callback that will call when the module is no longer imported on the page. Compared to `hot.dispose`, this can be used if the source code cleans up side-effects by itself on updates and you only need to clean-up when it's removed from the page. Vite currently uses this for `.css` imports. function setupOrReuseSideEffect() {} setupOrReuseSideEffect() if (import.meta.hot) { import.meta.hot.prune((data) => { // cleanup side effect }) } ## `hot.data` The `import.meta.hot.data` object is persisted across different instances of the same updated module. It can be used to pass on information from a previous version of the module to the next one. Note that re-assignment of `data` itself is not supported. Instead, you should mutate properties of the `data` object so information added from other handlers are preserved. // ok import.meta.hot.data.someValue = 'hello' // not supported import.meta.hot.data = { someValue: 'hello' } ## `hot.decline()` This is currently a noop and is there for backward compatibility. This could change in the future if there is a new usage for it. To indicate that the module is not hot-updatable, use `hot.invalidate()`. ## `hot.invalidate(message?: string)` A self-accepting module may realize during runtime that it can't handle a HMR update, and so the update needs to be forcefully propagated to importers. By calling `import.meta.hot.invalidate()`, the HMR server will invalidate the importers of the caller, as if the caller wasn't self-accepting. This will log a message both in the browser console and in the terminal. You can pass a message to give some context on why the invalidation happened. Note that you should always call `import.meta.hot.accept` even if you plan to call `invalidate` immediately afterwards, or else the HMR client won't listen for future changes to the self-accepting module. To communicate your intent clearly, we recommend calling `invalidate` within the `accept` callback like so: import.meta.hot.accept((module) => { // You may use the new module instance to decide whether to invalidate. if (cannotHandleUpdate(module)) { import.meta.hot.invalidate() } }) ## `hot.on(event, cb)` Listen to an HMR event. The following HMR events are dispatched by Vite automatically: * `'vite:beforeUpdate'` when an update is about to be applied (e.g. a module will be replaced) * `'vite:afterUpdate'` when an update has just been applied (e.g. a module has been replaced) * `'vite:beforeFullReload'` when a full reload is about to occur * `'vite:beforePrune'` when modules that are no longer needed are about to be pruned * `'vite:invalidate'` when a module is invalidated with `import.meta.hot.invalidate()` * `'vite:error'` when an error occurs (e.g. syntax error) * `'vite:ws:disconnect'` when the WebSocket connection is lost * `'vite:ws:connect'` when the WebSocket connection is (re-)established Custom HMR events can also be sent from plugins. See [handleHotUpdate](api- plugin#handlehotupdate) for more details. ## `hot.off(event, cb)` Remove callback from the event listeners. ## `hot.send(event, data)` Send custom events back to Vite's dev server. If called before connected, the data will be buffered and sent once the connection is established. See [Client-server Communication](api-plugin#client-server-communication) for more details, including a section on [Typing Custom Events](api- plugin#typescript-for-custom-events). ## Further Reading If you'd like to learn more about how to use the HMR API and how it works under-the-hood. Check out these resources: * [Hot Module Replacement is Easy](https://bjornlu.com/blog/hot-module-replacement-is-easy) # JavaScript API Vite's JavaScript APIs are fully typed, and it's recommended to use TypeScript or enable JS type checking in VS Code to leverage the intellisense and validation. ## `createServer` **Type Signature:** async function createServer(inlineConfig?: InlineConfig): Promise **Example Usage:** import { fileURLToPath } from 'node:url' import { createServer } from 'vite' const __dirname = fileURLToPath(new URL('.', import.meta.url)) const server = await createServer({ // any valid user config options, plus `mode` and `configFile` configFile: false, root: __dirname, server: { port: 1337, }, }) await server.listen() server.printUrls() server.bindCLIShortcuts({ print: true }) **NOTE** When using `createServer` and `build` in the same Node.js process, both functions rely on `process.env.NODE_ENV` to work properly, which also depends on the `mode` config option. To prevent conflicting behavior, set `process.env.NODE_ENV` or the `mode` of the two APIs to `development`. Otherwise, you can spawn a child process to run the APIs separately. **NOTE** When using [middleware mode](../config/server-options#server-middlewaremode) combined with [proxy config for WebSocket](../config/server-options#server- proxy), the parent http server should be provided in `middlewareMode` to bind the proxy correctly. Example import http from 'http' import { createServer } from 'vite' const parentServer = http.createServer() // or express, koa, etc. const vite = await createServer({ server: { // Enable middleware mode middlewareMode: { // Provide the parent http server for proxy WebSocket server: parentServer, }, proxy: { '/ws': { target: 'ws://localhost:3000', // Proxying WebSocket ws: true, }, }, }, }) parentServer.use(vite.middlewares) ## `InlineConfig` The `InlineConfig` interface extends `UserConfig` with additional properties: * `configFile`: specify config file to use. If not set, Vite will try to automatically resolve one from project root. Set to `false` to disable auto resolving. ## `ResolvedConfig` The `ResolvedConfig` interface has all the same properties of a `UserConfig`, except most properties are resolved and non-undefined. It also contains utilities like: * `config.assetsInclude`: A function to check if an `id` is considered an asset. * `config.logger`: Vite's internal logger object. ## `ViteDevServer` interface ViteDevServer { /** * The resolved Vite config object. */ config: ResolvedConfig /** * A connect app instance * - Can be used to attach custom middlewares to the dev server. * - Can also be used as the handler function of a custom http server * or as a middleware in any connect-style Node.js frameworks. * * https://github.com/senchalabs/connect#use-middleware */ middlewares: Connect.Server /** * Native Node http server instance. * Will be null in middleware mode. */ httpServer: http.Server | null /** * Chokidar watcher instance. If `config.server.watch` is set to `null`, * it will not watch any files and calling `add` or `unwatch` will have no effect. * https://github.com/paulmillr/chokidar/tree/3.6.0#api */ watcher: FSWatcher /** * Web socket server with `send(payload)` method. */ ws: WebSocketServer /** * Rollup plugin container that can run plugin hooks on a given file. */ pluginContainer: PluginContainer /** * Module graph that tracks the import relationships, url to file mapping * and hmr state. */ moduleGraph: ModuleGraph /** * The resolved urls Vite prints on the CLI (URL-encoded). Returns `null` * in middleware mode or if the server is not listening on any port. */ resolvedUrls: ResolvedServerUrls | null /** * Programmatically resolve, load and transform a URL and get the result * without going through the http request pipeline. */ transformRequest( url: string, options?: TransformOptions, ): Promise /** * Apply Vite built-in HTML transforms and any plugin HTML transforms. */ transformIndexHtml( url: string, html: string, originalUrl?: string, ): Promise /** * Load a given URL as an instantiated module for SSR. */ ssrLoadModule( url: string, options?: { fixStacktrace?: boolean }, ): Promise> /** * Fix ssr error stacktrace. */ ssrFixStacktrace(e: Error): void /** * Triggers HMR for a module in the module graph. You can use the `server.moduleGraph` * API to retrieve the module to be reloaded. If `hmr` is false, this is a no-op. */ reloadModule(module: ModuleNode): Promise /** * Start the server. */ listen(port?: number, isRestart?: boolean): Promise /** * Restart the server. * * @param forceOptimize - force the optimizer to re-bundle, same as --force cli flag */ restart(forceOptimize?: boolean): Promise /** * Stop the server. */ close(): Promise /** * Bind CLI shortcuts */ bindCLIShortcuts(options?: BindCLIShortcutsOptions): void /** * Calling `await server.waitForRequestsIdle(id)` will wait until all static imports * are processed. If called from a load or transform plugin hook, the id needs to be * passed as a parameter to avoid deadlocks. Calling this function after the first * static imports section of the module graph has been processed will resolve immediately. * @experimental */ waitForRequestsIdle: (ignoredId?: string) => Promise } **INFO** `waitForRequestsIdle` is meant to be used as a escape hatch to improve DX for features that can't be implemented following the on-demand nature of the Vite dev server. It can be used during startup by tools like Tailwind to delay generating the app CSS classes until the app code has been seen, avoiding flashes of style changes. When this function is used in a load or transform hook, and the default HTTP1 server is used, one of the six http channels will be blocked until the server processes all static imports. Vite's dependency optimizer currently uses this function to avoid full-page reloads on missing dependencies by delaying loading of pre-bundled dependencies until all imported dependencies have been collected from static imported sources. Vite may switch to a different strategy in a future major release, setting `optimizeDeps.crawlUntilStaticImports: false` by default to avoid the performance hit in large applications during cold start. ## `build` **Type Signature:** async function build( inlineConfig?: InlineConfig, ): Promise **Example Usage:** vite.config.js import path from 'node:path' import { fileURLToPath } from 'node:url' import { build } from 'vite' const __dirname = fileURLToPath(new URL('.', import.meta.url)) await build({ root: path.resolve(__dirname, './project'), base: '/foo/', build: { rollupOptions: { // ... }, }, }) ## `preview` **Type Signature:** async function preview(inlineConfig?: InlineConfig): Promise **Example Usage:** import { preview } from 'vite' const previewServer = await preview({ // any valid user config options, plus `mode` and `configFile` preview: { port: 8080, open: true, }, }) previewServer.printUrls() previewServer.bindCLIShortcuts({ print: true }) ## `PreviewServer` interface PreviewServer { /** * The resolved vite config object */ config: ResolvedConfig /** * A connect app instance. * - Can be used to attach custom middlewares to the preview server. * - Can also be used as the handler function of a custom http server * or as a middleware in any connect-style Node.js frameworks * * https://github.com/senchalabs/connect#use-middleware */ middlewares: Connect.Server /** * native Node http server instance */ httpServer: http.Server /** * The resolved urls Vite prints on the CLI (URL-encoded). Returns `null` * if the server is not listening on any port. */ resolvedUrls: ResolvedServerUrls | null /** * Print server urls */ printUrls(): void /** * Bind CLI shortcuts */ bindCLIShortcuts(options?: BindCLIShortcutsOptions): void } ## `resolveConfig` **Type Signature:** async function resolveConfig( inlineConfig: InlineConfig, command: 'build' | 'serve', defaultMode = 'development', defaultNodeEnv = 'development', isPreview = false, ): Promise The `command` value is `serve` in dev and preview, and `build` in build. ## `mergeConfig` **Type Signature:** function mergeConfig( defaults: Record, overrides: Record, isRoot = true, ): Record Deeply merge two Vite configs. `isRoot` represents the level within the Vite config which is being merged. For example, set `false` if you're merging two `build` options. **NOTE** `mergeConfig` accepts only config in object form. If you have a config in callback form, you should call it before passing into `mergeConfig`. You can use the `defineConfig` helper to merge a config in callback form with another config: export default defineConfig((configEnv) => mergeConfig(configAsCallback(configEnv), configAsObject), ) ## `searchForWorkspaceRoot` **Type Signature:** function searchForWorkspaceRoot( current: string, root = searchForPackageRoot(current), ): string **Related:** [server.fs.allow](../config/server-options#server-fs-allow) Search for the root of the potential workspace if it meets the following conditions, otherwise it would fallback to `root`: * contains `workspaces` field in `package.json` * contains one of the following file * `lerna.json` * `pnpm-workspace.yaml` ## `loadEnv` **Type Signature:** function loadEnv( mode: string, envDir: string, prefixes: string | string[] = 'VITE_', ): Record **Related:** [`.env` Files](env-and-mode#env-files) Load `.env` files within the `envDir`. By default, only env variables prefixed with `VITE_` are loaded, unless `prefixes` is changed. ## `normalizePath` **Type Signature:** function normalizePath(id: string): string **Related:** [Path Normalization](api-plugin#path-normalization) Normalizes a path to interoperate between Vite plugins. ## `transformWithEsbuild` **Type Signature:** async function transformWithEsbuild( code: string, filename: string, options?: EsbuildTransformOptions, inMap?: object, ): Promise Transform JavaScript or TypeScript with esbuild. Useful for plugins that prefer matching Vite's internal esbuild transform. ## `loadConfigFromFile` **Type Signature:** async function loadConfigFromFile( configEnv: ConfigEnv, configFile?: string, configRoot: string = process.cwd(), logLevel?: LogLevel, customLogger?: Logger, ): Promise<{ path: string config: UserConfig dependencies: string[] } | null> Load a Vite config file manually with esbuild. ## `preprocessCSS` * **Experimental:** [Give Feedback](https://github.com/vitejs/vite/discussions/13815) **Type Signature:** async function preprocessCSS( code: string, filename: string, config: ResolvedConfig, ): Promise interface PreprocessCSSResult { code: string map?: SourceMapInput modules?: Record deps?: Set } Pre-processes `.css`, `.scss`, `.sass`, `.less`, `.styl` and `.stylus` files to plain CSS so it can be used in browsers or parsed by other tools. Similar to the [built-in CSS pre-processing support](features#css-pre-processors), the corresponding pre-processor must be installed if used. The pre-processor used is inferred from the `filename` extension. If the `filename` ends with `.module.{ext}`, it is inferred as a [CSS module](https://github.com/css-modules/css-modules) and the returned result will include a `modules` object mapping the original class names to the transformed ones. Note that pre-processing will not resolve URLs in `url()` or `image-set()`. # Plugin API Vite plugins extends Rollup's well-designed plugin interface with a few extra Vite-specific options. As a result, you can write a Vite plugin once and have it work for both dev and build. **It is recommended to go through[Rollup's plugin documentation](https://rollupjs.org/plugin-development/) first before reading the sections below.** ## Authoring a Plugin Vite strives to offer established patterns out of the box, so before creating a new plugin make sure that you check the [Features guide](features) to see if your need is covered. Also review available community plugins, both in the form of a [compatible Rollup plugin](https://github.com/rollup/awesome) and [Vite Specific plugins](https://github.com/vitejs/awesome-vite#plugins) When creating a plugin, you can inline it in your `vite.config.js`. There is no need to create a new package for it. Once you see that a plugin was useful in your projects, consider sharing it to help others [in the ecosystem](https://chat.vite.dev). **TIP** When learning, debugging, or authoring plugins, we suggest including [vite- plugin-inspect](https://github.com/antfu/vite-plugin-inspect) in your project. It allows you to inspect the intermediate state of Vite plugins. After installing, you can visit `localhost:5173/__inspect/` to inspect the modules and transformation stack of your project. Check out install instructions in the [vite-plugin-inspect docs](https://github.com/antfu/vite-plugin-inspect). ## Conventions If the plugin doesn't use Vite specific hooks and can be implemented as a Compatible Rollup Plugin, then it is recommended to use the [Rollup Plugin naming conventions](https://rollupjs.org/plugin-development/#conventions). * Rollup Plugins should have a clear name with `rollup-plugin-` prefix. * Include `rollup-plugin` and `vite-plugin` keywords in package.json. This exposes the plugin to be also used in pure Rollup or WMR based projects For Vite only plugins * Vite Plugins should have a clear name with `vite-plugin-` prefix. * Include `vite-plugin` keyword in package.json. * Include a section in the plugin docs detailing why it is a Vite only plugin (for example, it uses Vite specific plugin hooks). If your plugin is only going to work for a particular framework, its name should be included as part of the prefix * `vite-plugin-vue-` prefix for Vue Plugins * `vite-plugin-react-` prefix for React Plugins * `vite-plugin-svelte-` prefix for Svelte Plugins See also Virtual Modules Convention. ## Plugins Config Users will add plugins to the project `devDependencies` and configure them using the `plugins` array option. vite.config.js import vitePlugin from 'vite-plugin-feature' import rollupPlugin from 'rollup-plugin-feature' export default defineConfig({ plugins: [vitePlugin(), rollupPlugin()], }) Falsy plugins will be ignored, which can be used to easily activate or deactivate plugins. `plugins` also accepts presets including several plugins as a single element. This is useful for complex features (like framework integration) that are implemented using several plugins. The array will be flattened internally. // framework-plugin import frameworkRefresh from 'vite-plugin-framework-refresh' import frameworkDevtools from 'vite-plugin-framework-devtools' export default function framework(config) { return [frameworkRefresh(config), frameworkDevTools(config)] } vite.config.js import { defineConfig } from 'vite' import framework from 'vite-plugin-framework' export default defineConfig({ plugins: [framework()], }) ## Simple Examples **TIP** It is common convention to author a Vite/Rollup plugin as a factory function that returns the actual plugin object. The function can accept options which allows users to customize the behavior of the plugin. ### Transforming Custom File Types const fileRegex = /\.(my-file-ext)$/ export default function myPlugin() { return { name: 'transform-file', transform(src, id) { if (fileRegex.test(id)) { return { code: compileFileToJS(src), map: null, // provide source map if available } } }, } } ### Importing a Virtual File See the example in the next section. ## Virtual Modules Convention Virtual modules are a useful scheme that allows you to pass build time information to the source files using normal ESM import syntax. export default function myPlugin() { const virtualModuleId = 'virtual:my-module' const resolvedVirtualModuleId = '\0' + virtualModuleId return { name: 'my-plugin', // required, will show up in warnings and errors resolveId(id) { if (id === virtualModuleId) { return resolvedVirtualModuleId } }, load(id) { if (id === resolvedVirtualModuleId) { return `export const msg = "from virtual module"` } }, } } Which allows importing the module in JavaScript: import { msg } from 'virtual:my-module' console.log(msg) Virtual modules in Vite (and Rollup) are prefixed with `virtual:` for the user-facing path by convention. If possible the plugin name should be used as a namespace to avoid collisions with other plugins in the ecosystem. For example, a `vite-plugin-posts` could ask users to import a `virtual:posts` or `virtual:posts/helpers` virtual modules to get build time information. Internally, plugins that use virtual modules should prefix the module ID with `\0` while resolving the id, a convention from the rollup ecosystem. This prevents other plugins from trying to process the id (like node resolution), and core features like sourcemaps can use this info to differentiate between virtual modules and regular files. `\0` is not a permitted char in import URLs so we have to replace them during import analysis. A `\0{id}` virtual id ends up encoded as `/@id/__x00__{id}` during dev in the browser. The id will be decoded back before entering the plugins pipeline, so this is not seen by plugins hooks code. Note that modules directly derived from a real file, as in the case of a script module in a Single File Component (like a .vue or .svelte SFC) don't need to follow this convention. SFCs generally generate a set of submodules when processed but the code in these can be mapped back to the filesystem. Using `\0` for these submodules would prevent sourcemaps from working correctly. ## Universal Hooks During dev, the Vite dev server creates a plugin container that invokes [Rollup Build Hooks](https://rollupjs.org/plugin-development/#build-hooks) the same way Rollup does it. The following hooks are called once on server start: * [`options`](https://rollupjs.org/plugin-development/#options) * [`buildStart`](https://rollupjs.org/plugin-development/#buildstart) The following hooks are called on each incoming module request: * [`resolveId`](https://rollupjs.org/plugin-development/#resolveid) * [`load`](https://rollupjs.org/plugin-development/#load) * [`transform`](https://rollupjs.org/plugin-development/#transform) These hooks also have an extended `options` parameter with additional Vite- specific properties. You can read more in the [SSR documentation](ssr#ssr- specific-plugin-logic). Some `resolveId` calls' `importer` value may be an absolute path for a generic `index.html` at root as it's not always possible to derive the actual importer due to Vite's unbundled dev server pattern. For imports handled within Vite's resolve pipeline, the importer can be tracked during the import analysis phase, providing the correct `importer` value. The following hooks are called when the server is closed: * [`buildEnd`](https://rollupjs.org/plugin-development/#buildend) * [`closeBundle`](https://rollupjs.org/plugin-development/#closebundle) Note that the [`moduleParsed`](https://rollupjs.org/plugin- development/#moduleparsed) hook is **not** called during dev, because Vite avoids full AST parses for better performance. [Output Generation Hooks](https://rollupjs.org/plugin-development/#output- generation-hooks) (except `closeBundle`) are **not** called during dev. You can think of Vite's dev server as only calling `rollup.rollup()` without calling `bundle.generate()`. ## Vite Specific Hooks Vite plugins can also provide hooks that serve Vite-specific purposes. These hooks are ignored by Rollup. ### `config` * **Type:** `(config: UserConfig, env: { mode: string, command: string }) => UserConfig | null | void` * **Kind:** `async`, `sequential` Modify Vite config before it's resolved. The hook receives the raw user config (CLI options merged with config file) and the current config env which exposes the `mode` and `command` being used. It can return a partial config object that will be deeply merged into existing config, or directly mutate the config (if the default merging cannot achieve the desired result). **Example:** // return partial config (recommended) const partialConfigPlugin = () => ({ name: 'return-partial', config: () => ({ resolve: { alias: { foo: 'bar', }, }, }), }) // mutate the config directly (use only when merging doesn't work) const mutateConfigPlugin = () => ({ name: 'mutate-config', config(config, { command }) { if (command === 'build') { config.root = 'foo' } }, }) **Note** User plugins are resolved before running this hook so injecting other plugins inside the `config` hook will have no effect. ### `configResolved` * **Type:** `(config: ResolvedConfig) => void | Promise` * **Kind:** `async`, `parallel` Called after the Vite config is resolved. Use this hook to read and store the final resolved config. It is also useful when the plugin needs to do something different based on the command being run. **Example:** const examplePlugin = () => { let config return { name: 'read-config', configResolved(resolvedConfig) { // store the resolved config config = resolvedConfig }, // use stored config in other hooks transform(code, id) { if (config.command === 'serve') { // dev: plugin invoked by dev server } else { // build: plugin invoked by Rollup } }, } } Note that the `command` value is `serve` in dev (in the cli `vite`, `vite dev`, and `vite serve` are aliases). ### `configureServer` * **Type:** `(server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>` * **Kind:** `async`, `sequential` * **See also:** [ViteDevServer](api-javascript#vitedevserver) Hook for configuring the dev server. The most common use case is adding custom middlewares to the internal [connect](https://github.com/senchalabs/connect) app: const myPlugin = () => ({ name: 'configure-server', configureServer(server) { server.middlewares.use((req, res, next) => { // custom handle request... }) }, }) **Injecting Post Middleware** The `configureServer` hook is called before internal middlewares are installed, so the custom middlewares will run before internal middlewares by default. If you want to inject a middleware **after** internal middlewares, you can return a function from `configureServer`, which will be called after internal middlewares are installed: const myPlugin = () => ({ name: 'configure-server', configureServer(server) { // return a post hook that is called after internal middlewares are // installed return () => { server.middlewares.use((req, res, next) => { // custom handle request... }) } }, }) **Storing Server Access** In some cases, other plugin hooks may need access to the dev server instance (e.g. accessing the web socket server, the file system watcher, or the module graph). This hook can also be used to store the server instance for access in other hooks: const myPlugin = () => { let server return { name: 'configure-server', configureServer(_server) { server = _server }, transform(code, id) { if (server) { // use server... } }, } } Note `configureServer` is not called when running the production build so your other hooks need to guard against its absence. ### `configurePreviewServer` * **Type:** `(server: PreviewServer) => (() => void) | void | Promise<(() => void) | void>` * **Kind:** `async`, `sequential` * **See also:** [PreviewServer](api-javascript#previewserver) Same as [`configureServer`](api-plugin#configureserver) but for the preview server. Similarly to `configureServer`, the `configurePreviewServer` hook is called before other middlewares are installed. If you want to inject a middleware **after** other middlewares, you can return a function from `configurePreviewServer`, which will be called after internal middlewares are installed: const myPlugin = () => ({ name: 'configure-preview-server', configurePreviewServer(server) { // return a post hook that is called after other middlewares are // installed return () => { server.middlewares.use((req, res, next) => { // custom handle request... }) } }, }) ### `transformIndexHtml` * **Type:** `IndexHtmlTransformHook | { order?: 'pre' | 'post', handler: IndexHtmlTransformHook }` * **Kind:** `async`, `sequential` Dedicated hook for transforming HTML entry point files such as `index.html`. The hook receives the current HTML string and a transform context. The context exposes the [`ViteDevServer`](api-javascript#vitedevserver) instance during dev, and exposes the Rollup output bundle during build. The hook can be async and can return one of the following: * Transformed HTML string * An array of tag descriptor objects (`{ tag, attrs, children }`) to inject to the existing HTML. Each tag can also specify where it should be injected to (default is prepending to ``) * An object containing both as `{ html, tags }` By default `order` is `undefined`, with this hook applied after the HTML has been transformed. In order to inject a script that should go through the Vite plugins pipeline, `order: 'pre'` will apply the hook before processing the HTML. `order: 'post'` applies the hook after all hooks with `order` undefined are applied. **Basic Example:** const htmlPlugin = () => { return { name: 'html-transform', transformIndexHtml(html) { return html.replace( /(.*?)<\/title>/, `<title>Title replaced!`, ) }, } } **Full Hook Signature:** type IndexHtmlTransformHook = ( html: string, ctx: { path: string filename: string server?: ViteDevServer bundle?: import('rollup').OutputBundle chunk?: import('rollup').OutputChunk }, ) => | IndexHtmlTransformResult | void | Promise type IndexHtmlTransformResult = | string | HtmlTagDescriptor[] | { html: string tags: HtmlTagDescriptor[] } interface HtmlTagDescriptor { tag: string attrs?: Record children?: string | HtmlTagDescriptor[] /** * default: 'head-prepend' */ injectTo?: 'head' | 'body' | 'head-prepend' | 'body-prepend' } **Note** This hook won't be called if you are using a framework that has custom handling of entry files (for example [SvelteKit](https://github.com/sveltejs/kit/discussions/8269#discussioncomment-4509145)). ### `handleHotUpdate` * **Type:** `(ctx: HmrContext) => Array | void | Promise | void>` * **See also:** [HMR API](api-hmr) Perform custom HMR update handling. The hook receives a context object with the following signature: interface HmrContext { file: string timestamp: number modules: Array read: () => string | Promise server: ViteDevServer } * `modules` is an array of modules 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: handleHotUpdate({ server, modules, timestamp }) { // Invalidate modules manually const invalidatedModules = new Set() for (const mod of modules) { server.moduleGraph.invalidateModule( mod, invalidatedModules, timestamp, true ) } server.ws.send({ type: 'full-reload' }) return [] } * Return an empty array and perform complete custom HMR handling by sending custom events to the client: handleHotUpdate({ server }) { server.ws.send({ type: 'custom', event: 'special-update', data: {} }) return [] } Client code should register corresponding handler using the [HMR API](api-hmr) (this could be injected by the same plugin's `transform` hook): if (import.meta.hot) { import.meta.hot.on('special-update', (data) => { // perform custom update }) } ## Plugin Ordering A Vite plugin can additionally specify an `enforce` property (similar to webpack loaders) to adjust its application order. The value of `enforce` can be either `"pre"` or `"post"`. The resolved plugins will be in the following order: * Alias * User plugins with `enforce: 'pre'` * Vite core plugins * User plugins without enforce value * Vite build plugins * User plugins with `enforce: 'post'` * Vite post build plugins (minify, manifest, reporting) Note that this is separate from hooks ordering, those are still separately subject to their `order` attribute [as usual for Rollup hooks](https://rollupjs.org/plugin-development/#build-hooks). ## Conditional Application By default plugins are invoked for both serve and build. In cases where a plugin needs to be conditionally applied only during serve or build, use the `apply` property to only invoke them during `'build'` or `'serve'`: function myPlugin() { return { name: 'build-only', apply: 'build', // or 'serve' } } A function can also be used for more precise control: apply(config, { command }) { // apply only on build but not for SSR return command === 'build' && !config.build.ssr } ## Rollup Plugin Compatibility A fair number of Rollup plugins will work directly as a Vite plugin (e.g. `@rollup/plugin-alias` or `@rollup/plugin-json`), but not all of them, since some plugin hooks do not make sense in an unbundled dev server context. In general, as long as a Rollup plugin fits the following criteria then it should just work as a Vite plugin: * It doesn't use the [`moduleParsed`](https://rollupjs.org/plugin-development/#moduleparsed) hook. * It doesn't have strong coupling between bundle-phase hooks and output-phase hooks. If a Rollup plugin only makes sense for the build phase, then it can be specified under `build.rollupOptions.plugins` instead. It will work the same as a Vite plugin with `enforce: 'post'` and `apply: 'build'`. You can also augment an existing Rollup plugin with Vite-only properties: vite.config.js import example from 'rollup-plugin-example' import { defineConfig } from 'vite' export default defineConfig({ plugins: [ { ...example(), enforce: 'post', apply: 'build', }, ], }) ## Path Normalization Vite normalizes paths while resolving ids to use POSIX separators ( / ) while preserving the volume in Windows. On the other hand, Rollup keeps resolved paths untouched by default, so resolved ids have win32 separators ( \ ) in Windows. However, Rollup plugins use a [`normalizePath` utility function](https://github.com/rollup/plugins/tree/master/packages/pluginutils#normalizepath) from `@rollup/pluginutils` internally, which converts separators to POSIX before performing comparisons. This means that when these plugins are used in Vite, the `include` and `exclude` config pattern and other similar paths against resolved ids comparisons work correctly. So, for Vite plugins, when comparing paths against resolved ids it is important to first normalize the paths to use POSIX separators. An equivalent `normalizePath` utility function is exported from the `vite` module. import { normalizePath } from 'vite' normalizePath('foo\\bar') // 'foo/bar' normalizePath('foo/bar') // 'foo/bar' ## Filtering, include/exclude pattern Vite exposes [`@rollup/pluginutils`'s `createFilter`](https://github.com/rollup/plugins/tree/master/packages/pluginutils#createfilter) function to encourage Vite specific plugins and integrations to use the standard include/exclude filtering pattern, which is also used in Vite core itself. ## Client-server Communication Since Vite 2.9, we provide some utilities for plugins to help handle the communication with clients. ### Server to Client On the plugin side, we could use `server.ws.send` to broadcast events to the client: vite.config.js export default defineConfig({ plugins: [ { // ... configureServer(server) { server.ws.on('connection', () => { server.ws.send('my:greetings', { msg: 'hello' }) }) }, }, ], }) **NOTE** We recommend **always prefixing** your event names to avoid collisions with other plugins. On the client side, use [`hot.on`](api-hmr#hot-on-event-cb) to listen to the events: // client side if (import.meta.hot) { import.meta.hot.on('my:greetings', (data) => { console.log(data.msg) // hello }) } ### Client to Server To send events from the client to the server, we can use [`hot.send`](api- hmr#hot-send-event-payload): // client side if (import.meta.hot) { import.meta.hot.send('my:from-client', { msg: 'Hey!' }) } Then use `server.ws.on` and listen to the events on the server side: vite.config.js export default defineConfig({ plugins: [ { // ... configureServer(server) { server.ws.on('my:from-client', (data, client) => { console.log('Message from client:', data.msg) // Hey! // reply only to the client (if needed) client.send('my:ack', { msg: 'Hi! I got your message!' }) }) }, }, ], }) ### TypeScript for Custom Events Internally, vite infers the type of a payload from the `CustomEventMap` interface, it is possible to type custom events by extending the interface: **Note** Make sure to include the `.d.ts` extension when specifying TypeScript declaration files. Otherwise, Typescript may not know which file the module is trying to extend. events.d.ts import 'vite/types/customEvent.d.ts' declare module 'vite/types/customEvent.d.ts' { interface CustomEventMap { 'custom:foo': { msg: string } // 'event-key': payload } } This interface extension is utilized by `InferCustomEventPayload` to infer the payload type for event `T`. For more information on how this interface is utilized, refer to the [HMR API Documentation](api-hmr#hmr-api). type CustomFooPayload = InferCustomEventPayload<'custom:foo'> import.meta.hot?.on('custom:foo', (payload) => { // The type of payload will be { msg: string } }) import.meta.hot?.on('unknown:event', (payload) => { // The type of payload will be any }) # Static Asset Handling * Related: [Public Base Path](build#public-base-path) * Related: [`assetsInclude` config option](../config/shared-options#assetsinclude) ## Importing Asset as URL Importing a static asset will return the resolved public URL when it is served: import imgUrl from './img.png' document.getElementById('hero-img').src = imgUrl For example, `imgUrl` will be `/src/img.png` during development, and become `/assets/img.2d8efhg.png` in the production build. 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](../config/shared-options#assetsinclude). * 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](../config/build-options#build-assetsinlinelimit) will be inlined as base64 data URLs. * Git LFS placeholders are automatically excluded from inlining because they do not contain the content of the file they represent. To get inlining, make sure to download the file contents via Git LFS before building. * TypeScript, by default, does not recognize static asset imports as valid modules. To fix this, include [`vite/client`](features#client-types). **Inlining SVGs through`url()`** When passing a URL of SVG to a manually constructed `url()` by JS, the variable should be wrapped within double quotes. import imgUrl from './img.svg' document.getElementById('hero-img').style.background = `url("${imgUrl}")` ### Explicit URL Imports Assets that are not included in the internal list or in `assetsInclude`, can be explicitly imported as a URL using the `?url` suffix. This is useful, for example, to import [Houdini Paint Worklets](https://developer.mozilla.org/en- US/docs/Web/API/CSS/paintWorklet_static). import workletURL from 'extra-scalloped-border/worklet.js?url' CSS.paintWorklet.addModule(workletURL) ### Explicit Inline Handling Assets can be explicitly imported with inlining or no inlining using the `?inline` or `?no-inline` suffix respectively. import imgUrl1 from './img.svg?no-inline' import imgUrl2 from './img.png?inline' ### Importing Asset as String Assets can be imported as strings using the `?raw` suffix. import shaderString from './shader.glsl?raw' ### Importing Script as a Worker Scripts can be imported as web workers with the `?worker` or `?sharedworker` suffix. // Separate chunk in the production build import Worker from './shader.js?worker' const worker = new Worker() // sharedworker import SharedWorker from './shader.js?sharedworker' const sharedWorker = new SharedWorker() // Inlined as base64 strings import InlineWorker from './shader.js?worker&inline' Check out the [Web Worker section](features#web-workers) for more details. ## 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. The directory defaults to `/public`, but can be configured via the [`publicDir` option](../config/shared-options#publicdir). 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`. ## new URL(url, import.meta.url) [import.meta.url](https://developer.mozilla.org/en- US/docs/Web/JavaScript/Reference/Statements/import.meta) is a native ESM feature that exposes the current module's URL. Combining it with the native [URL constructor](https://developer.mozilla.org/en-US/docs/Web/API/URL), we can obtain the full, resolved URL of a static asset using relative path from a JavaScript module: const imgUrl = new URL('./img.png', import.meta.url).href document.getElementById('hero-img').src = imgUrl This works natively in modern browsers - in fact, Vite doesn't need to process this code at all during development! This pattern also supports dynamic URLs via template literals: function getImageUrl(name) { // note that this does not include files in subdirectories return new URL(`./dir/${name}.png`, import.meta.url).href } During the production build, Vite will perform necessary transforms so that the URLs still point to the correct location even after bundling and asset hashing. However, the URL string must be static so it can be analyzed, otherwise the code will be left as is, which can cause runtime errors if `build.target` does not support `import.meta.url` // Vite will not transform this const imgUrl = new URL(imagePath, import.meta.url).href How it works Vite will transform the `getImageUrl` function to: import __img0png from './dir/img0.png' import __img1png from './dir/img1.png' function getImageUrl(name) { const modules = { './dir/img0.png': __img0png, './dir/img1.png': __img1png, } return new URL(modules[`./dir/${name}.png`], import.meta.url).href } **Does not work with SSR** This pattern does not work if you are using Vite for Server-Side Rendering, because `import.meta.url` have different semantics in browsers vs. Node.js. The server bundle also cannot determine the client host URL ahead of time. # Backend Integration **Note** If you want to serve the HTML using a traditional backend (e.g. Rails, Laravel) but use Vite for serving assets, check for existing integrations listed in [Awesome Vite](https://github.com/vitejs/awesome-vite#integrations- with-backends). If you need a custom integration, you can follow the steps in this guide to configure it manually 1. In your Vite config, configure the entry and enable build manifest: vite.config.js export default defineConfig({ server: { cors: { // the origin you will be accessing via browser origin: 'http://my-backend.example.com', }, }, build: { // generate .vite/manifest.json in outDir manifest: true, rollupOptions: { // overwrite default .html entry input: '/path/to/main.js', }, }, }) If you haven't disabled the [module preload polyfill](../config/build- options#build-polyfillmodulepreload), you also need to import the polyfill in your entry // add the beginning of your app entry import 'vite/modulepreload-polyfill' 2. For development, inject the following in your server's HTML template (substitute `http://localhost:5173` with the local URL Vite is running at): In order to properly serve assets, you have two options: * Make sure the server is configured to proxy static assets requests to the Vite server * Set [`server.origin`](../config/server-options#server-origin) so that generated asset URLs will be resolved using the back-end server URL instead of a relative path This is needed for assets such as images to load properly. Note if you are using React with `@vitejs/plugin-react`, you'll also need to add this before the above scripts, since the plugin is not able to modify the HTML you are serving (substitute `http://localhost:5173` with the local URL Vite is running at): 3. For production: after running `vite build`, a `.vite/manifest.json` file will be generated alongside other asset files. An example manifest file looks like this: .vite/manifest.json { "_shared-B7PI925R.js": { "file": "assets/shared-B7PI925R.js", "name": "shared", "css": ["assets/shared-ChJ_j-JJ.css"] }, "_shared-ChJ_j-JJ.css": { "file": "assets/shared-ChJ_j-JJ.css", "src": "_shared-ChJ_j-JJ.css" }, "baz.js": { "file": "assets/baz-B2H3sXNv.js", "name": "baz", "src": "baz.js", "isDynamicEntry": true }, "views/bar.js": { "file": "assets/bar-gkvgaI9m.js", "name": "bar", "src": "views/bar.js", "isEntry": true, "imports": ["_shared-B7PI925R.js"], "dynamicImports": ["baz.js"] }, "views/foo.js": { "file": "assets/foo-BRBmoGS9.js", "name": "foo", "src": "views/foo.js", "isEntry": true, "imports": ["_shared-B7PI925R.js"], "css": ["assets/foo-5UjPuW-k.css"] } } * The manifest has a `Record` structure * For entry or dynamic entry chunks, the key is the relative src path from project root. * For non entry chunks, the key is the base name of the generated file prefixed with `_`. * For the CSS file generated when [`build.cssCodeSplit`](../config/build-options#build-csscodesplit) is `false`, the key is `style.css`. * Chunks will contain information on its static and dynamic imports (both are keys that map to the corresponding chunk in the manifest), and also its corresponding CSS and asset files (if any). 4. You can use this file to render links or preload directives with hashed filenames. Here is an example HTML template to render the proper links. The syntax here is for explanation only, substitute with your server templating language. The `importedChunks` function is for illustration and isn't provided by Vite. Specifically, a backend generating HTML should include the following tags given a manifest file and an entry point: * A `` tag for each file in the entry point chunk's `css` list * Recursively follow all chunks in the entry point's `imports` list and include a `` tag for each CSS file of each imported chunk. * A tag for the `file` key of the entry point chunk (` While the following should be included for the entry point `views/bar.js`: Pseudo implementation of `importedChunks` An example pseudo implementation of `importedChunks` in TypeScript (This will need to be adapted for your programming language and templating language): import type { Manifest, ManifestChunk } from 'vite' export default function importedChunks( manifest: Manifest, name: string, ): ManifestChunk[] { const seen = new Set() function getImportedChunks(chunk: ManifestChunk): ManifestChunk[] { const chunks: ManifestChunk[] = [] for (const file of chunk.imports ?? []) { const importee = manifest[file] if (seen.has(file)) { continue } seen.add(file) chunks.push(...getImportedChunks(importee)) chunks.push(importee) } return chunks } return getImportedChunks(manifest[name]) } # Building for Production When it is time to deploy your app for production, simply run the `vite build` command. By default, it uses `/index.html` as the build entry point, and produces an application bundle that is suitable to be served over a static hosting service. Check out the [Deploying a Static Site](static-deploy) for guides about popular services. ## Browser Compatibility By default, the production bundle assumes a modern browser that is included in the [Baseline](https://web-platform-dx.github.io/web-features/) Widely Available targets. The default browser support range is: * Chrome >=107 * Edge >=107 * Firefox >=104 * Safari >=16 You can specify custom targets via the [`build.target` config option](../config/build-options#build-target), where the lowest target is `es2015`. If a lower target is set, Vite will still require these minimum browser support ranges as it relies on [native ESM dynamic import](https://caniuse.com/es6-module-dynamic-import), and [`import.meta`](https://caniuse.com/mdn-javascript_operators_import_meta): * Chrome >=64 * Firefox >=67 * Safari >=11.1 * Edge >=79 Note that by default, Vite only handles syntax transforms and **does not cover polyfills**. You can check out which automatically generates polyfill bundles based on the user's browser UserAgent string. Legacy browsers can be supported via [@vitejs/plugin- legacy](https://github.com/vitejs/vite/tree/main/packages/plugin-legacy), which will automatically generate legacy chunks and corresponding ES language feature polyfills. The legacy chunks are conditionally loaded only in browsers that do not have native ESM support. ## Public Base Path * Related: [Asset Handling](assets) If you are deploying your project under a nested public path, simply specify the [`base` config option](../config/shared-options#base) and all asset paths will be rewritten accordingly. This option can also be specified as a command line flag, e.g. `vite build --base=/my/public/path/`. JS-imported asset URLs, CSS `url()` references, and asset references in your `.html` files are all automatically adjusted to respect this option during build. The exception is when you need to dynamically concatenate URLs on the fly. In this case, you can use the globally injected `import.meta.env.BASE_URL` variable which will be the public base path. Note this variable is statically replaced during build so it must appear exactly as-is (i.e. `import.meta.env['BASE_URL']` won't work). For advanced base path control, check out Advanced Base Options. ### Relative base If you don't know the base path in advance, you may set a relative base path with `"base": "./"` or `"base": ""`. This will make all generated URLs to be relative to each file. **Support for older browsers when using relative bases** `import.meta` support is required for relative bases. If you need to support [browsers that do not support `import.meta`](https://caniuse.com/mdn- javascript_operators_import_meta), you can use [the `legacy` plugin](https://github.com/vitejs/vite/tree/main/packages/plugin-legacy). ## Customizing the Build The build can be customized via various [build config options](../config/build-options). Specifically, you can directly adjust the underlying [Rollup options](https://rollupjs.org/configuration-options/) via `build.rollupOptions`: vite.config.js export default defineConfig({ build: { rollupOptions: { // https://rollupjs.org/configuration-options/ }, }, }) For example, you can specify multiple Rollup outputs with plugins that are only applied during build. ## Chunking Strategy You can configure how chunks are split using `build.rollupOptions.output.manualChunks` (see [Rollup docs](https://rollupjs.org/configuration-options/#output-manualchunks)). If you use a framework, refer to their documentation for configuring how chunks are split. ## Load Error Handling Vite emits `vite:preloadError` event when it fails to load dynamic imports. `event.payload` contains the original import error. If you call `event.preventDefault()`, the error will not be thrown. window.addEventListener('vite:preloadError', (event) => { window.location.reload() // for example, refresh the page }) When a new deployment occurs, the hosting service may delete the assets from previous deployments. As a result, a user who visited your site before the new deployment might encounter an import error. This error happens because the assets running on that user's device are outdated and it tries to import the corresponding old chunk, which is deleted. This event is useful for addressing this situation. ## Rebuild on Files Changes You can enable rollup watcher with `vite build --watch`. Or, you can directly adjust the underlying [`WatcherOptions`](https://rollupjs.org/configuration- options/#watch) via `build.watch`: vite.config.js export default defineConfig({ build: { watch: { // https://rollupjs.org/configuration-options/#watch }, }, }) With the `--watch` flag enabled, changes to the `vite.config.js`, as well as any files to be bundled, will trigger a rebuild. ## Multi-Page App Suppose you have the following source code structure: ├── package.json ├── vite.config.js ├── index.html ├── main.js └── nested ├── index.html └── nested.js During dev, simply navigate or link to `/nested/` \- it works as expected, just like for a normal static file server. During build, all you need to do is to specify multiple `.html` files as entry points: vite.config.js import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { defineConfig } from 'vite' const __dirname = dirname(fileURLToPath(import.meta.url)) export default defineConfig({ build: { rollupOptions: { input: { main: resolve(__dirname, 'index.html'), nested: resolve(__dirname, 'nested/index.html'), }, }, }, }) If you specify a different root, remember that `__dirname` will still be the folder of your vite.config.js file when resolving the input paths. Therefore, you will need to add your `root` entry to the arguments for `resolve`. Note that for HTML files, Vite ignores the name given to the entry in the `rollupOptions.input` object and instead respects the resolved id of the file when generating the HTML asset in the dist folder. This ensures a consistent structure with the way the dev server works. ## Library Mode When you are developing a browser-oriented library, you are likely spending most of the time on a test/demo page that imports your actual library. With Vite, you can use your `index.html` for that purpose to get the smooth development experience. When it is time to bundle your library for distribution, use the [`build.lib` config option](../config/build-options#build-lib). Make sure to also externalize any dependencies that you do not want to bundle into your library, e.g. `vue` or `react`: vite.config.js (single entry)vite.config.js (multiple entries) import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { defineConfig } from 'vite' const __dirname = dirname(fileURLToPath(import.meta.url)) export default defineConfig({ build: { lib: { entry: resolve(__dirname, 'lib/main.js'), name: 'MyLib', // the proper extensions will be added fileName: 'my-lib', }, rollupOptions: { // make sure to externalize deps that shouldn't be bundled // into your library external: ['vue'], output: { // Provide global variables to use in the UMD build // for externalized deps globals: { vue: 'Vue', }, }, }, }, }) import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { defineConfig } from 'vite' const __dirname = dirname(fileURLToPath(import.meta.url)) export default defineConfig({ build: { lib: { entry: { 'my-lib': resolve(__dirname, 'lib/main.js'), secondary: resolve(__dirname, 'lib/secondary.js'), }, name: 'MyLib', }, rollupOptions: { // make sure to externalize deps that shouldn't be bundled // into your library external: ['vue'], output: { // Provide global variables to use in the UMD build // for externalized deps globals: { vue: 'Vue', }, }, }, }, }) The entry file would contain exports that can be imported by users of your package: lib/main.js import Foo from './Foo.vue' import Bar from './Bar.vue' export { Foo, Bar } Running `vite build` with this config uses a Rollup preset that is oriented towards shipping libraries and produces two bundle formats: * `es` and `umd` (for single entry) * `es` and `cjs` (for multiple entries) The formats can be configured with the [`build.lib.formats`](../config/build- options#build-lib) option. $ vite build building for production... dist/my-lib.js 0.08 kB / gzip: 0.07 kB dist/my-lib.umd.cjs 0.30 kB / gzip: 0.16 kB Recommended `package.json` for your lib: package.json (single entry)package.json (multiple entries) { "name": "my-lib", "type": "module", "files": ["dist"], "main": "./dist/my-lib.umd.cjs", "module": "./dist/my-lib.js", "exports": { ".": { "import": "./dist/my-lib.js", "require": "./dist/my-lib.umd.cjs" } } } { "name": "my-lib", "type": "module", "files": ["dist"], "main": "./dist/my-lib.cjs", "module": "./dist/my-lib.js", "exports": { ".": { "import": "./dist/my-lib.js", "require": "./dist/my-lib.cjs" }, "./secondary": { "import": "./dist/secondary.js", "require": "./dist/secondary.cjs" } } } ### CSS support If your library imports any CSS, it will be bundled as a single CSS file besides the built JS files, e.g. `dist/my-lib.css`. The name defaults to `build.lib.fileName`, but can also be changed with [`build.lib.cssFileName`](../config/build-options#build-lib). You can export the CSS file in your `package.json` to be imported by users: { "name": "my-lib", "type": "module", "files": ["dist"], "main": "./dist/my-lib.umd.cjs", "module": "./dist/my-lib.js", "exports": { ".": { "import": "./dist/my-lib.js", "require": "./dist/my-lib.umd.cjs" }, "./style.css": "./dist/my-lib.css" } } **File Extensions** If the `package.json` does not contain `"type": "module"`, Vite will generate different file extensions for Node.js compatibility. `.js` will become `.mjs` and `.cjs` will become `.js`. **Environment Variables** In library mode, all [`import.meta.env.*`](env-and-mode) usage are statically replaced when building for production. However, `process.env.*` usage are not, so that consumers of your library can dynamically change it. If this is undesirable, you can use `define: { 'process.env.NODE_ENV': '"production"' }` for example to statically replace them, or use [`esm- env`](https://github.com/benmccann/esm-env) for better compatibility with bundlers and runtimes. **Advanced Usage** Library mode includes a simple and opinionated configuration for browser- oriented and JS framework libraries. If you are building non-browser libraries, or require advanced build flows, you can use [Rollup](https://rollupjs.org) or [esbuild](https://esbuild.github.io) directly. ## Advanced Base Options **WARNING** This feature is experimental. [Give Feedback](https://github.com/vitejs/vite/discussions/13834). For advanced use cases, the deployed assets and public files may be in different paths, for example to use different cache strategies. A user may choose to deploy in three different paths: * The generated entry HTML files (which may be processed during SSR) * The generated hashed assets (JS, CSS, and other file types like images) * The copied [public files](assets#the-public-directory) A single static base isn't enough in these scenarios. Vite provides experimental support for advanced base options during build, using `experimental.renderBuiltUrl`. experimental: { renderBuiltUrl(filename, { hostType }) { if (hostType === 'js') { return { runtime: `window.__toCdnUrl(${JSON.stringify(filename)})` } } else { return { relative: true } } }, }, If the hashed assets and public files aren't deployed together, options for each group can be defined independently using asset `type` included in the second `context` param given to the function. experimental: { renderBuiltUrl(filename, { hostId, hostType, type }) { if (type === 'public') { return 'https://www.domain.com/' + filename } else if (path.extname(hostId) === '.js') { return { runtime: `window.__assetsPath(${JSON.stringify(filename)})` } } else { return 'https://cdn.domain.com/assets/' + filename } }, }, Note that the `filename` passed is a decoded URL, and if the function returns a URL string, it should also be decoded. Vite will handle the encoding automatically when rendering the URLs. If an object with `runtime` is returned, encoding should be handled yourself where needed as the runtime code will be rendered as is. # Command Line Interface ## Dev server ### `vite` Start Vite dev server in the current directory. `vite dev` and `vite serve` are aliases for `vite`. #### Usage vite [root] #### Options Options | ---|--- `--host [host]` | Specify hostname (`string`) `--port ` | Specify port (`number`) `--open [path]` | Open browser on startup (`boolean | string`) `--cors` | Enable CORS (`boolean`) `--strictPort` | Exit if specified port is already in use (`boolean`) `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) `-c, --config ` | Use specified config file (`string`) `--base ` | Public base path (default: `/`) (`string`) `-l, --logLevel ` | info | warn | error | silent (`string`) `--clearScreen` | Allow/disable clear screen when logging (`boolean`) `--configLoader ` | Use `bundle` to bundle the config with esbuild, or `runner` (experimental) to process it on the fly, or `native` (experimental) to load using the native runtime (default: `bundle`) `--profile` | Start built-in Node.js inspector (check [Performance bottlenecks](troubleshooting#performance-bottlenecks)) `-d, --debug [feat]` | Show debug logs (`string | boolean`) `-f, --filter ` | Filter debug logs (`string`) `-m, --mode ` | Set env mode (`string`) `-h, --help` | Display available CLI options `-v, --version` | Display version number ## Build ### `vite build` Build for production. #### Usage vite build [root] #### Options Options | ---|--- `--target ` | Transpile target (default: `"modules"`) (`string`) `--outDir ` | Output directory (default: `dist`) (`string`) `--assetsDir ` | Directory under outDir to place assets in (default: `"assets"`) (`string`) `--assetsInlineLimit ` | Static asset base64 inline threshold in bytes (default: `4096`) (`number`) `--ssr [entry]` | Build specified entry for server-side rendering (`string`) `--sourcemap [output]` | Output source maps for build (default: `false`) (`boolean | "inline" | "hidden"`) `--minify [minifier]` | Enable/disable minification, or specify minifier to use (default: `"esbuild"`) (`boolean | "terser" | "esbuild"`) `--manifest [name]` | Emit build manifest json (`boolean | string`) `--ssrManifest [name]` | Emit ssr manifest json (`boolean | string`) `--emptyOutDir` | Force empty outDir when it's outside of root (`boolean`) `-w, --watch` | Rebuilds when modules have changed on disk (`boolean`) `-c, --config ` | Use specified config file (`string`) `--base ` | Public base path (default: `/`) (`string`) `-l, --logLevel ` | Info | warn | error | silent (`string`) `--clearScreen` | Allow/disable clear screen when logging (`boolean`) `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` (experimental) to process it on the fly (default: `bundle`) `--profile` | Start built-in Node.js inspector (check [Performance bottlenecks](troubleshooting#performance-bottlenecks)) `-d, --debug [feat]` | Show debug logs (`string | boolean`) `-f, --filter ` | Filter debug logs (`string`) `-m, --mode ` | Set env mode (`string`) `-h, --help` | Display available CLI options `--app` | Build all environments, same as `builder: {}` (`boolean`, experimental) ## Others ### `vite optimize` Pre-bundle dependencies. **Deprecated** : the pre-bundle process runs automatically and does not need to be called. #### Usage vite optimize [root] #### Options Options | ---|--- `--force` | Force the optimizer to ignore the cache and re-bundle (`boolean`) `-c, --config ` | Use specified config file (`string`) `--base ` | Public base path (default: `/`) (`string`) `-l, --logLevel ` | Info | warn | error | silent (`string`) `--clearScreen` | Allow/disable clear screen when logging (`boolean`) `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` (experimental) to process it on the fly (default: `bundle`) `-d, --debug [feat]` | Show debug logs (`string | boolean`) `-f, --filter ` | Filter debug logs (`string`) `-m, --mode ` | Set env mode (`string`) `-h, --help` | Display available CLI options ### `vite preview` Locally preview the production build. Do not use this as a production server as it's not designed for it. #### Usage vite preview [root] #### Options Options | ---|--- `--host [host]` | Specify hostname (`string`) `--port ` | Specify port (`number`) `--strictPort` | Exit if specified port is already in use (`boolean`) `--open [path]` | Open browser on startup (`boolean | string`) `--outDir ` | Output directory (default: `dist`)(`string`) `-c, --config ` | Use specified config file (`string`) `--base ` | Public base path (default: `/`) (`string`) `-l, --logLevel ` | Info | warn | error | silent (`string`) `--clearScreen` | Allow/disable clear screen when logging (`boolean`) `--configLoader ` | Use `bundle` to bundle the config with esbuild or `runner` (experimental) to process it on the fly (default: `bundle`) `-d, --debug [feat]` | Show debug logs (`string | boolean`) `-f, --filter ` | Filter debug logs (`string`) `-m, --mode ` | Set env mode (`string`) `-h, --help` | Display available CLI options # Dependency Pre-Bundling When you run `vite` for the first time, Vite prebundles your project dependencies before loading your site locally. It is done automatically and transparently by default. ## The Why This is Vite performing what we call "dependency pre-bundling". This process serves two purposes: 1. **CommonJS and UMD compatibility:** During development, Vite's dev serves all code as native ESM. Therefore, Vite must convert dependencies that are shipped as CommonJS or UMD into ESM first. When converting CommonJS dependencies, Vite performs smart import analysis so that named imports to CommonJS modules will work as expected even if the exports are dynamically assigned (e.g. React): // works as expected import React, { useState } from 'react' 2. **Performance:** Vite converts ESM dependencies with many internal modules into a single module to improve subsequent page load performance. Some packages ship their ES modules builds as many separate files importing one another. For example, [`lodash-es` has over 600 internal modules](https://unpkg.com/browse/lodash-es/)! When we do `import { debounce } from 'lodash-es'`, the browser fires off 600+ HTTP requests at the same time! Even though the server has no problem handling them, the large amount of requests create a network congestion on the browser side, causing the page to load noticeably slower. By pre-bundling `lodash-es` into a single module, we now only need one HTTP request instead! **NOTE** Dependency pre-bundling only applies in development mode, and uses `esbuild` to convert dependencies to ESM. In production builds, `@rollup/plugin- commonjs` is used instead. ## Automatic Dependency Discovery If an existing cache is not found, Vite will crawl your source code and automatically discover dependency imports (i.e. "bare imports" that expect to be resolved from `node_modules`) and use these found imports as entry points for the pre-bundle. The pre-bundling is performed with `esbuild` so it's typically very fast. After the server has already started, if a new dependency import is encountered that isn't already in the cache, Vite will re-run the dep bundling process and reload the page if needed. ## Monorepos and Linked Dependencies In a monorepo setup, a dependency may be a linked package from the same repo. Vite automatically detects dependencies that are not resolved from `node_modules` and treats the linked dep as source code. It will not attempt to bundle the linked dep, and will analyze the linked dep's dependency list instead. However, this requires the linked dep to be exported as ESM. If not, you can add the dependency to [`optimizeDeps.include`](../config/dep-optimization- options#optimizedeps-include) and [`build.commonjsOptions.include`](../config/build-options#build- commonjsoptions) in your config. vite.config.js export default defineConfig({ optimizeDeps: { include: ['linked-dep'], }, build: { commonjsOptions: { include: [/linked-dep/, /node_modules/], }, }, }) When making changes to the linked dep, restart the dev server with the `--force` command line option for the changes to take effect. ## Customizing the Behavior The default dependency discovery heuristics may not always be desirable. In cases where you want to explicitly include/exclude dependencies from the list, use the [`optimizeDeps` config options](../config/dep-optimization-options). A typical use case for `optimizeDeps.include` or `optimizeDeps.exclude` is when you have an import that is not directly discoverable in the source code. For example, maybe the import is created as a result of a plugin transform. This means Vite won't be able to discover the import on the initial scan - it can only discover it after the file is requested by the browser and transformed. This will cause the server to immediately re-bundle after server start. Both `include` and `exclude` can be used to deal with this. If the dependency is large (with many internal modules) or is CommonJS, then you should include it; If the dependency is small and is already valid ESM, you can exclude it and let the browser load it directly. You can further customize esbuild too with the [`optimizeDeps.esbuildOptions` option](../config/dep-optimization-options#optimizedeps-esbuildoptions). For example, adding an esbuild plugin to handle special files in dependencies or changing the [build `target`](https://esbuild.github.io/api/#target). ## Caching ### File System Cache Vite caches the pre-bundled dependencies in `node_modules/.vite`. It determines whether it needs to re-run the pre-bundling step based on a few sources: * Package manager lockfile content, e.g. `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` or `bun.lockb`. * Patches folder modification time. * Relevant fields in your `vite.config.js`, if present. * `NODE_ENV` value. The pre-bundling step will only need to be re-run when one of the above has changed. If for some reason you want to force Vite to re-bundle deps, you can either start the dev server with the `--force` command line option, or manually delete the `node_modules/.vite` cache directory. ### Browser Cache Resolved dependency requests are strongly cached with HTTP 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 (as reflected in your package manager lockfile). If you want to debug your dependencies by making local edits, you can: 1. Temporarily disable cache via the Network tab of your browser devtools. 2. Restart Vite dev server with the `--force` flag to re-bundle the deps. 3. Reload the page. # 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 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#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#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. 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#envprefix) option. **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#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 **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 **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. 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: 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`. tsconfig.json { "lib": ["WebWorker"] } **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:

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: 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"` **`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. # 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: 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`](index), 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: 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#esbuild) option can be used, which defaults to `esnext` for minimal transpilation. In builds, the [`build.target`](../config/build-options#build- target) option takes higher priority over `esbuild.target` and can also be set if needed. **`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) **`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: /// Using `compilerOptions.types` Alternatively, you can add `vite/client` to `compilerOptions.types` inside `tsconfig.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): declare module '*.svg' { const content: React.FC> export default content } * The file containing the reference to `vite/client` (normally `vite-env.d.ts`): /// /// ## HTML HTML files stand [front-and-center](index#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](build#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 ` To opt-out of HTML processing on certain elements, you can add the `vite- ignore` attribute on the element, which can be useful when referencing external assets or CDN. ## Frameworks All modern frameworks maintain integrations with Vite. Most framework plugins are maintained by each framework team, with the exception of the official Vue and React Vite plugins that are maintained in the vite org: * Vue support via [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue) * Vue JSX support via [@vitejs/plugin-vue-jsx](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue-jsx) * React support via [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react) * React using SWC support via [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc) Check out the [Plugins Guide](https://vite.dev/plugins) for more information. ## JSX `.jsx` and `.tsx` files are also supported out of the box. JSX transpilation is also handled via [esbuild](https://esbuild.github.io). Your framework of choice will already configure JSX out of the box (for example, Vue users should use the official [@vitejs/plugin-vue- jsx](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue- jsx) plugin, which provides Vue 3 specific features including HMR, global component resolving, directives and slots). If using JSX with your own framework, custom `jsxFactory` and `jsxFragment` can be configured using the [`esbuild` option](../config/shared- options#esbuild). For example, the Preact plugin would use: vite.config.js import { defineConfig } from 'vite' export default defineConfig({ esbuild: { jsxFactory: 'h', jsxFragment: 'Fragment', }, }) More details in [esbuild docs](https://esbuild.github.io/content-types/#jsx). You can inject the JSX helpers using `jsxInject` (which is a Vite-only option) to avoid manual imports: vite.config.js import { defineConfig } from 'vite' export default defineConfig({ esbuild: { jsxInject: `import React from 'react'`, }, }) ## CSS Importing `.css` files will inject its content to the page via a `