vite/docs/guide/api-environment.md
patak 915cd7f11a
docs: top-level client environment config (#18474)
Co-authored-by: bluwy <bjornlu.dev@gmail.com>
2024-10-28 10:44:55 +01:00

8.4 KiB

Environment API

:::warning Experimental Initial work for this API was introduced in Vite 5.1 with the name "Vite Runtime API". This guide describes a revised API, renamed to Environment API. This API will be released in Vite 6 as experimental. You can already test it in the latest vite@6.0.0-beta.x version.

Resources:

Please share with us your feedback as you test the proposal. :::

Formalizing Environments

Vite 6 formalizes the concept of Environments. Until Vite 5, there were two implicit Environments (client, and optionally ssr). The new Environment API allows users and framework authors to create as many environments as needed to map the way their apps work in production. This new capability required a big internal refactoring, but a lot of effort has been placed on backward compatibility. The initial goal of Vite 6 is to move the ecosystem to the new major as smoothly as possible, delaying the adoption of these new experimental APIs until enough users have migrated and frameworks and plugin authors have validated the new design.

Closing the gap between build and dev

For a simple SPA/MPA, no new APIs around environments are exposed to the config. Internally, Vite will apply the options to a client environment, but it's not necessary to know of this concept when configuring Vite. The config and behavior from Vite 5 should work seamlessly here.

When we move to a typical server side rendered (SSR) app, we'll have two environments:

  • client: runs the app in the browser.
  • server: runs the app in node (or other server runtimes) which renders pages before sending them to the browser.

In dev, Vite executes the server code in the same Node process as the Vite dev server, giving a close approximation to the production environment. However, it is also possible for servers to run in other JS runtimes, like Cloudflare's workerd which have different constrains. 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.

Vite Environments

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 inherit 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<string, any>
  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 their SSR environment, it could name it server for example.

interface UserConfig extends EnvironmentOptions {
  environments: Record<string, EnvironmentOptions>
  // 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:

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 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 to learn about the Environment API programmatic side.

For Runtime providers, the Environment API Runtimes Guide explains how to offer custom environment to be consumed by frameworks and users.