Skip to content环境变量:
多环境管理:
Vite 插件与环境变量
插件体系概览
Vite 的插件本质上是一个包含 name 和若干钩子函数的对象。它扩展了 Rolldown 的插件接口,同时加入了 Vite 专属的 hook,使得同一个插件在开发服务器和构建阶段都能工作,不需要为两种场景分别编写逻辑。
与 Rollup 插件相比,Vite 插件多出一组 Vite 特有的钩子,例如 config、configResolved、configureServer、transformIndexHtml 等。这些钩子可以介入 Vite 自身的配置解析、开发服务器创建以及 HTML 处理流程,而不必去模拟 Rollup 的构建管线。
在 vite.config.js 中,通过 plugins 字段注册插件。数组元素可以是单个插件对象,也可以是包含多个插件的预设数组。false 或 null 等假值会被自动忽略,便于条件加载。简单场景下也可以直接在配置文件中内联一个插件,不必先发布成 npm 包。社区发布的 Vite 专属插件推荐使用 vite-plugin- 前缀,并在 package.json 的 keywords 中加入 vite-plugin;框架专属插件则使用 vite-plugin-vue- 等二级前缀。
开发或调试插件时,vite-plugin-inspect 可以展示每个模块经过各个插件转换后的中间状态,直观地看到插件对代码做了什么。
使用官方插件
以 Vue 单文件组件支持为例,需要安装 @vitejs/plugin-vue:
bash
npm install -D @vitejs/plugin-vue然后在配置文件中挂载:
js
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()]
})@vitejs/plugin-vue 内部实现了 .vue 文件的解析、样式块提取、HMR 逻辑等一系列钩子。添加该插件后,Vite 才能在浏览器中以 ESM 方式提供 .vue 模块。类似地,@vitejs/plugin-react 为 React 提供了 JSX 转换和 Fast Refresh 支持。
启动 npx vite dev 后,在浏览器 Network 面板可以看到 .vue 文件被实时编译成 JavaScript,这是插件在开发服务器阶段通过 transform 钩子处理的结果。
编写自定义插件:注入构建时间
插件的极简形式是返回一个带有 name 和钩子的对象。下面的插件向全局注入 __BUILD_TIME__ 常量,值取当前时间:
js
function buildTimePlugin() {
return {
name: 'build-time',
config() {
return {
define: {
__BUILD_TIME__: JSON.stringify(new Date().toISOString())
}
}
}
}
}name 必须提供,调试和追踪时靠它识别插件。config 钩子在 Vite 解析配置时调用,这里返回的 define 对象会与已有配置合并。define 选项用于声明全局常量替换,源码中写入 console.log(__BUILD_TIME__) 就会被替换成时间字符串。
注册到 vite.config.js:
js
import { defineConfig } from 'vite'
import buildTimePlugin from './build-time-plugin'
export default defineConfig({
plugins: [buildTimePlugin()]
})启动 dev 或执行 build 后,__BUILD_TIME__ 会变成类似 "2025-08-09T12:00:00.000Z" 的字面量。JSON.stringify 确保时间字符串被正确序列化——define 的值必须能够被 JSON 序列化,或者是单一标识符的字符串。
插件钩子
Vite 插件可用的钩子分为两类:从 Rollup(Rolldown)继承来的通用钩子,以及 Vite 特有的钩子。
通用钩子覆盖整个构建阶段,例如:
resolveId— 模块路径解析load— 加载模块内容transform— 转换代码
它们的行为与 Rollup 插件几乎一致。
Vite 特有钩子则面向开发服务器和 HTML 处理:
config— 在 Vite 解析配置时调用,可返回部分配置对象来补充或改写现有配置。configResolved— 配置完全确定后调用,适合读取最终配置并据此调整其他工具。configureServer— 开发服务器启动时调用,能向内部 Connect 实例添加中间件,常用于自定义 API 代理或 mock。transformIndexHtml— 用于转换 HTML 入口文件内容,例如注入脚本标签。
一个典型的调试插件可能这样利用 configResolved 和 configureServer:
js
function debugPlugin() {
return {
name: 'debug',
configResolved(resolvedConfig) {
console.log('Resolved config:', resolvedConfig)
},
configureServer(server) {
server.middlewares.use((req, res, next) => {
console.log(`${req.method} ${req.url}`)
next()
})
}
}
}load 和 transform 常用于处理非标准模块。例如,在 load 中读取 .graphql 文件并通过 export default 返回字符串,实现 GraphQL 文件的直接导入。
虚拟模块也依赖这些钩子。插件可以通过 virtual: 前缀暴露不存在的模块,内部使用 \0 前缀避免被其他插件处理,这是 Rollup 生态的既有约定。
插件的执行顺序
如果多个插件都实现了同一个钩子,执行次序由 enforce 属性控制。不设置该属性时,插件处于 normal 阶段。可以设为 'pre' 使插件先于其他普通插件执行,或设为 'post' 使其在最后执行。
运行顺序为:pre → normal → post。同一 enforce 值内部,插件按注册顺序执行。
假设有两个插件都实现了 transform,一个做语法降级,另一个做代码压缩。降级应当在压缩之前执行,可以这样安排:
js
plugins: [
{ name: 'down-level', enforce: 'pre', transform(code, id) { /* ... */ } },
{ name: 'minify', enforce: 'post', transform(code, id) { /* ... */ } }
]如果省略 enforce,minify 插件会因其注册顺序在后而先于 down-level 执行(post 阶段在所有 normal 之后运行,而 pre 在 normal 之前,所以 down-level 先于所有 normal,minify 晚于所有 normal)。这是一个需要留心的地方。
同一个插件的不同钩子也会按特定顺序触发(例如 config 一定早于 configResolved),但 enforce 仅影响不同插件之间同类型钩子的顺序。
环境变量:import.meta.env
Vite 通过 import.meta.env 暴露了一组内置常量:
MODE— 当前运行模式。开发服务器默认'development',vite build默认'production'。BASE_URL— 应用部署的基础路径,对应base配置项,默认为'/'。DEV— 是否处于开发模式(布尔值)。PROD— 是否处于生产模式(与DEV相反)。SSR— 是否运行在服务端渲染上下文。
开发阶段这些值通过 import.meta 注入,本质上是全局变量;构建阶段它们会被静态替换成对应的字面量,这样条件分支不会留下运行时判断开销。
除了内置常量,所有以 VITE_ 开头的环境变量也会被暴露给客户端代码。例如在系统环境变量或 .env 文件中设置 VITE_API_HOST=api.example.com,代码中就可以通过 import.meta.env.VITE_API_HOST 取到 'api.example.com'。
需要注意的是,import.meta.env 中的所有值都是字符串。即使通过 .env 文件定义 VITE_PORT=3000,读取到的也是字符串 '3000',需要手动 Number 转换。
TypeScript 用户可以为自定义变量补充类型声明:
ts
// src/env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_HOST: string
// 其他变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}多环境管理:.env 文件与 mode 参数
Vite 启动时会从项目根目录加载 .env 文件,规则如下:
.env— 所有模式共享。.env.local— 本地覆盖,Git 忽略,所有模式。.env.[mode]— 特定模式文件,如.env.development。.env.[mode].local— 特定模式的本地覆盖。
优先级从低到高为:通用 → 通用 local → 模式特定 → 模式特定 local。进程已有的环境变量优先级最高,不会被 .env 文件覆盖。
文件格式是一行一个键值对:KEY=value,例如:
# .env
VITE_APP_TITLE=My App修改 .env 后需要重启开发服务器,因为文件只在启动时加载一次。
通过 --mode 参数可以指定模式:
bash
npx vite build --mode staging这时 Vite 会加载 .env.staging 和 .env.staging.local(以及基础的 .env、.env.local),同时 import.meta.env.MODE 的值变成 'staging'。这样就能在同一个代码库中通过不同模式切换 API 地址、功能开关等。
.env 中的变量同样受 VITE_ 前缀规则约束,只有前缀匹配的才会进入客户端。
在插件中操控环境变量
有时需要在插件中动态注入变量,而不直接写在 .env 文件里。一个常见做法是通过 config 钩子设置 define,把计算后的值塞进客户端代码。
例如,根据模式决定是否注入 “debug” 标记:
js
function debugFlagPlugin() {
return {
name: 'debug-flag',
config(config, { mode }) {
if (mode === 'development') {
return {
define: {
__DEBUG__: 'true'
}
}
}
}
}
}config 钩子的第二个参数携带 mode 信息,可以直接在钩子内部判断。返回的 define 对象会合并到现有配置中,源码里通过 __DEBUG__ 即可区分环境。
还有一种场景:插件需要用到 .env 文件中的变量值来影响配置(例如根据某个变量决定是否启用某功能)。配置文件的执行时机早于 .env 文件的加载,因此直接读取 process.env 不会包含 .env 的内容。这时需要使用 Vite 提供的 loadEnv 函数手动加载:
js
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
// 第三个参数 '' 表示加载所有变量,而不只是 VITE_ 前缀
if (env.ENABLE_ANALYZER === 'true') {
// ...
}
})loadEnv 默认只加载 VITE_ 前缀的变量,通过第三个参数可以调整前缀或置空以加载全部变量。但不要随意将非 VITE_ 变量注入到浏览器端,否则会造成泄露。
安全边界
VITE_ 前缀是一种有意的“暴露边界”。凡是通过 import.meta.env 可访问的值,最终都会被打包进客户端 JS 产物中,任何拿到产物的用户都能直接读取这些变量的值。因此,绝对不能把服务端密钥、数据库密码、内部 API Token 这类敏感信息放入 VITE_* 变量。
前端代码需要区分的只是“公开的、与环境相关的配置”,比如:
- API 服务的域名
- 第三方 SDK 的公开 App ID
- 功能特性开关
任何需要保密的凭据应留在服务端,或至少在边缘函数 / BFF 层控制,客户端通过安全的认证机制获取受限资源。
Vite 的 envPrefix 选项可以改变暴露前缀,比如设为 APP_。但如果设置成空字符串 '',Vite 会直接报错,因为这会导致所有进程环境变量都被暴露给前端。
同样,在 define 中注入常量时,也要确保值不携带敏感信息。
环境变量切换 API 地址
假设后端 API 地址在开发时指向本地 http://localhost:4000,构建后指向生产地址 https://api.example.com。
创建两个 .env 文件:
# .env.development
VITE_API_BASE=http://localhost:4000# .env.production
VITE_API_BASE=https://api.example.com在应用代码中请求:
js
const base = import.meta.env.VITE_API_BASE
async function fetchUsers() {
const res = await fetch(`${base}/users`)
return res.json()
}npx vite 默认使用 development 模式,加载 .env.development,VITE_API_BASE 得到本地地址。npx vite build 默认 production 模式,构建产物中 VITE_API_BASE 会被替换成 "https://api.example.com"。如果新增预发环境 staging,只需创建 .env.staging 并设置 VITE_API_BASE=https://api-staging.example.com,构建时通过 --mode staging 切换。
若还需要根据模式切换其他行为(比如是否启用 mock),可以在插件中读取 MODE 或利用 define 注入标识变量。这种组合方式让环境差异完全由配置驱动,源代码保持统一。
