Vite 项目里那套 .env 环境变量机制是很多团队用得最多、也最容易用歪的一块功能。刚接触的时候觉得无非就是建个文件、加个前缀、import.meta.env 读一下可真到要同时维护三套甚至四套部署环境要在一个仓库里打出给不同客户、不同部署通道使用的包时很多人才发现变量读不到、mode 传错了、产物全堆在一个目录里、打完包发现配置改不了还得重新构建。这些问题我前后在四五个项目里全踩过一遍。这篇内容围绕 Vite 里基于 .env 文件做自定义打包这条主线把加载优先级、mode 的真实含义、非前缀变量在配置里的读取方式、产物分目录、打包后运行时改写配置、类型提示与变量校验直到常见故障排查逐段拆开讲。适合已经在用 Vite、但环境配置还停留在“一个 .env 走天下”的同学也适合准备把老项目迁到 Vite、需要先摸清这套规则边界的同学。文中代码可以照着改参数取值和踩坑原因我都会说清楚不搞只贴配置不讲道理的写法。1. 为什么打包配置不能写死在代码里1.1 从一个被注释掉三次的文件说起我接手过一个中台前端项目request.ts文件顶部躺着三行被注释掉的 baseURL对应开发、预发、生产三个不同的接口地址。每次发版负责发布的同学要手动把第一行的注释挪到第三行改完还要在群里回一句“改好了”。有一次发完版才发现有人把注释挪错了线上包打到了预发地址上排查了小半天。这类问题不是靠“谁细心一点”能解决的。只要配置项散落在源码里它就永远是靠人眼复核的脆弱环节。Vite 提供的 .env 文件机制本质是把“随环境变化的值”从“代码逻辑”里剥离出去让同一份源码在不同 mode 下产出不同产物。理解这句话很重要变的是被注入进去的字面量不变的是被打包的那套业务代码。所以判断一个配置项该不该放进 .env我一般用一条线划分凡是部署位置、服务地址、渠道标识、开关这类“同一个代码换环境就要换值”的东西进 .env凡是描述业务行为的常量、枚举、状态码老老实实写在代码或者 config 模块里不要往环境变量里塞。后者塞进去只会让代码变得难读还容易被人误改。1.2 变量是在构建那一刻被“焊死”的这是整套机制里最需要先弄明白的一点。Vite 处理import.meta.env.VITE_XXX的方式是在构建阶段做文本替换把代码里的表达式直接换成字符串字面量。构建产物里根本不存在“读环境变量”这个动作值在打包完成的那一秒就固定了。我见过有同学在服务器上改 .env期望重启容器后生效把 nginx 配置翻了一遍最后发现根本没用。因为 docker 镜像里躺着的是一份已经编译好的静态文件js 里写死的就是构建机器上的那个值。要改只能重新构建或者走后面第 3.5 节讲的运行时注入方案。反过来说这个特性也带来了好处既然是编译期替换压缩器就能做常量折叠。if (import.meta.env.VITE_FEATURE_FLAG on)这种写法在构建后可能整段被裁掉不会留下无谓的分支判断。所以真正用来“特性开关”的变量用字面量比较是比动态取值更划算的。1.3 方案设计要先定下三个硬约束动手写配置之前先想清楚三件约束能省掉后面大量返工。第一环境的枚举值必须收敛。别允许任何人随手--mode xxx传一个没定义过的 mode 名。没有对应的 .env.xxx 文件时Vite 只会加载最基础的两份变量缺失会变成 undefined 一路传到业务代码里最后在某个页面上炸掉。做法是把可用的 mode 列成枚举写进 scripts 里固定住。第二谁是变量来源的唯一出题人。我倾向让 .env.[mode] 文件成为唯一来源禁止在构建命令里再临时加一堆内联变量。原因很直接内联变量不会出现在仓库里别人构建同一个 mode 拿到的产物跟你不一样出了问题无从对比。第三配置文件和环境文件必须能对照。变量名要成体系比如全部用VITE_加上业务域前缀不要一个叫API、一个叫SERVICE_ROOT、一个叫backendPath半年后谁也不知道哪几个还在用。2. .env 文件的加载顺序与命名规则2.1 四类文件的优先级矩阵Vite 在启动或者构建时会按固定顺序去读环境文件后读到的同名变量覆盖先读到的。这个顺序是很多人记混的地方我直接列成表。文件名何时加载建议是否入库优先级.env所有 mode入库最低.env.local所有 mode不入库低于第 3 项.env.[mode]指定 mode入库高.env.[mode].local指定 mode不入库最高表里“优先级”指的是同名变量以谁为准。举个具体场景.env里写VITE_APP_TITLE通用管理台.env.staging里写VITE_APP_TITLE预发环境跑--mode staging时最终拿到的是“预发环境”。而.env.staging.local如果也写了这个 key那就是它说了算。我自己团队的约定是.env只放那些所有环境都不变的公共值比如应用标题前缀、分页大小默认值.env.[mode]放该环境的接口地址、构建产目录、渠道标识.env.local系列永远不进仓库留给开发同学本地覆盖调试用。注意.env.local在所有 mode 下都会被加载很多人以为它只对 development 生效结果在预发包里被本地的调试配置污染了。如果你有这种担心可以在.gitignore里把.env.*.local和.env.local一起忽略同时在 CI 脚本里加一步检查这两个文件是否存在存在就报错退出。2.2 mode 和 NODE_ENV 不是同一个东西这是新手最容易混淆的一对概念我面试时问过好几次答清楚的人不多。mode是 Vite 自己的概念用来决定加载哪一份.env.[mode]文件。它的默认值取决于命令vite开发时是developmentvite build时是production。你完全可以vite build --mode staging此时 mode 是 staging但它依然是一个生产构建——代码会被压缩、Tree-shaking 照常执行。NODE_ENV是 Node 生态里的约定Vite 会把它默认设成与 mode 对应的值开发对应 development构建对应 production但它不会因为你传了--mode staging就自动变成 staging。也就是说跑--mode staging构建时process.env.NODE_ENV仍然是production。那怎么判断当前是哪个环境用import.meta.env.MODE。这个内置变量就是当前 mode 的字面量在.env.staging下它等于staging。我在业务里通常这么写// src/config/env.ts const mode import.meta.env.MODE const isProdBuild import.meta.env.PROD // 只要 build 出来就是 true export const ENV_NAME (import.meta.env.VITE_ENV_NAME || mode) as dev | staging | prod export function isStaging() { return ENV_NAME staging }这里我加了一个自己的VITE_ENV_NAME而不是直接用 MODE原因是有些历史项目打包脚本把 mode 传得五花八门用自定义变量做一层映射更可控。但如果你是新项目直接用 MODE 就好少一层间接。顺带提醒MODE、BASE_URL、DEV、PROD、SSR这几个是内置变量你在 .env 文件里写同名变量是覆盖不掉的Vite 会忽略。我见过有人在 .env 里写VITE_PRODfalse想假装生产环境这当然不生效别在这上面浪费时间。2.3 VITE_ 前缀是怎么筛出来的能不能改Vite 默认只把VITE_开头的变量暴露给客户端代码其它变量留在 Node 侧。这个设计的用意是防止敏感信息被打进 bundle——比如数据库连接串、内部密钥如果也一并注入到浏览器端那就等于公开发布了。这个前缀是可以改的在vite.config.ts里配envPrefix。有些团队用公司缩写统一成APP_理由是避免和第三方库的变量撞名。// vite.config.ts export default defineConfig({ envPrefix: [APP_, VITE_], })这里有两个实操细节值得说。一是envPrefix支持传数组但数组里的每个前缀都会生效不是“找到第一个就用”。如果你同时配了APP_和VITE_两套变量都会被打进产物筛选力度反而变弱了。我倾向只留一个前缀不要贪方便。二是前缀筛选只作用于“暴露给客户端”这一步不影响loadEnv读取。也就是说即使某变量只有DB_前缀在vite.config.ts里也能读到它只是业务代码里访问不到。这个差异是第 3.3 节方案的立足点。再补一个容易被忽略的规则.env文件里的值全部会被解析成字符串。VITE_ENABLE_MOCKfalse读出来是字符串false而false在布尔判断里是真值。这是巨大坑很多人写if (import.meta.env.VITE_ENABLE_MOCK)结果明明关掉了却一直在走 mock 分支。正确写法是显式比较// 错误示范永远进 if if (import.meta.env.VITE_ENABLE_MOCK) { /* ... */ } // 正确示范 export const MOCK_ENABLED import.meta.env.VITE_ENABLE_MOCK true数字同理VITE_TIMEOUT5000读出来是5000参与运算时靠隐式转换侥幸能跑但遇上拼接就会变成5000 1000 50001000。我现在的做法是统一在src/config/env.ts里做一次解析和类型收敛业务代码只从这里面拿不直接碰import.meta.env。3. 一套能直接抄的多环境打包方案3.1 目录与文件划分先说结构。假设项目要发布到开发、测试、生产三套环境对应 mode 命名为development、staging、production开发环境直接用默认值即可不需要额外文件。我在项目根目录下这样摆# .env —— 所有环境共享 VITE_APP_TITLE运营管理台 VITE_PAGE_SIZE20 # .env.staging —— 预发环境 VITE_ENV_NAMEstaging VITE_API_BASEhttps://api-staging.example.com VITE_CDN_BASE/static/ VITE_ENABLE_MOCKfalse VITE_BUILD_OUT_DIRdist/staging # .env.production —— 生产环境 VITE_ENV_NAMEprod VITE_API_BASEhttps://api.example.com VITE_CDN_BASE/static/ VITE_ENABLE_MOCKfalse VITE_BUILD_OUT_DIRdist/prod开发环境那份.env.development里放本地 mock 地址和VITE_ENABLE_MOCKtrue构建预发和生产时不加载它天然隔离。有几点命名上的讲究。VITE_BUILD_OUT_DIR这个变量是给我自己在vite.config.ts里读的不是一个“给浏览器看的”变量。它其实可以不加VITE_前缀——加了会顺带被打进产物虽然无害但没必要。我在早期项目里加惯了后来觉得还是让它带前缀更省心因为loadEnv默认只返回带前缀的变量不带前缀要额外传参数。这是一个权衡后面 3.3 节会细说。值里如果有空格、#、一定要用引号包起来。dotenv 的解析规则里#会被当作行内注释起点VITE_APP_TITLE运营 管理台这种不加引号也可能出意外。我现在的习惯是所有含空格的值一律双引号包裹。还有一个版本要提.env支持变量引用吗Vite 底层用的解析库能力有限别指望VITE_APIhttps://${VITE_HOST}/api这种插值。要拼路径就在 config 里自己拼别在 env 文件里玩花活我试过踩坑。3.2 scripts 里的 --mode 怎么写才不出错写死 mode 是纪律问题。我的package.json一般长这样{ scripts: { dev: vite, build:staging: vite build --mode staging, build:prod: vite build --mode production, preview:staging: vite preview --mode staging --outDir dist/staging } }几个要点。第一不要给build留一个裸命令。有人习惯保留build: vite build作为默认结果 CI 里有人调npm run build打了个默认生产包扔到测试环境上去了。删掉裸命令强制每个人选一个明确的入口这是一种很低成本的防错设计。第二注意不同包管理器的传参差异。用npm run build:staging -- --mode production是能把后面的参数透传进去的会覆盖脚本里的 mode等于留了个后门。这个没法完全拦住只能靠流程和 code review。pnpm 的写法略有不同pnpm build:staging --mode production也能透传团队里统一用同一种包管理器会省掉很多沟通成本。第三vite preview读取的也是 mode但要注意它读的是构建时的产物目录。如果你的产物已经按环境分了目录preview 时得用--outDir指过去否则它去dist下找不到东西。这个点我卡过一次看着空白页以为是路由问题其实读的是空目录。3.3 vite.config.ts 里读非前缀变量的正确姿势业务代码里读import.meta.env就够了但配置文件里做自定义构建行为的时候需要主动去读 .env 文件。这里用loadEnv。// vite.config.ts import { defineConfig, loadEnv } from vite export default defineConfig(({ mode }) { // 第三个参数传 表示不做前缀过滤所有变量都返回 const env loadEnv(mode, process.cwd(), ) return { base: env.VITE_CDN_BASE || /, build: { outDir: env.VITE_BUILD_OUT_DIR || dist, sourcemap: env.VITE_ENV_NAME ! prod, rollupOptions: { output: { manualChunks: { vendor: [vue, vue-router], }, }, }, }, define: { __BUILD_TIME__: JSON.stringify(new Date().toISOString()), __API_BASE__: JSON.stringify(env.VITE_API_BASE), }, } })逐条说清楚为什么这么写。用函数式配置。defineConfig接受函数时参数里有mode这是拿到当前 mode 的唯一入口。很多人写死对象配置然后在里面硬编码base结果换环境就失效。loadEnv(mode, process.cwd(), )的第三个参数是关键。默认值只返回带前缀的变量传空字符串表示全部返回。这意味着你可以定义一个不带前缀的SENTRY_TOKEN在构建期使用同时保证它不会出现在浏览器产物里。这个技巧我在需要对接构建期上传 sourcemap 的场景下用过很实用。process.cwd()是环境文件所在目录。如果你的项目是 monorepo配置文件在子包里这里的路径要相应调整否则会读不到根目录的.env。这种情况我一般显式传path.resolve(__dirname, ../..)。base用 CDN 前缀配置。注意 base 会影响 index.html 里的资源引用路径也会影响import.meta.env.BASE_URL的值。如果 CDN 前缀是以/结尾的Vite 拼接才正确如果写成/static而不带斜杠可能拼出/staticassets/index.js这种错误路径。这个细节我在两个项目里都提醒过仍然有人栽。define用来注入编译期常量。__BUILD_TIME__做过运维的同学会喜欢页面上挂个版本号出问题一眼看出当前跑的是哪个构建。注意define的值必须是 JSON 序列化后的字符串直接写__BUILD_TIME__: new Date()会报错。3.4 让产物落在不同目录产物分目录的价值在流水线上体现得最明显。构建机连续打三个环境的包如果都输出到dist后一个会覆盖前一个上传脚本拿到的是最后一个。分目录之后上传环节只需按目录取文件。目录名从环境变量来也就是上面VITE_BUILD_OUT_DIR那个用法。要提醒的是 outDir 与 base 的配合如果 base 配的是绝对路径/static/产物放哪个目录都不影响引用如果 base 配的是相对路径./那部署时的目录层级会直接影响访问。我建议非必要不用相对 base坑太多。清理策略也要留意。build.emptyOutDir默认是 true会在构建前清空 outDir。如果你把 outDir 配成dist/${mode}那是安全的但如果哪天有人手滑把 outDir 配成了dist而你又恰好把三个环境的包都放在里面一次构建就全清了。我在 CI 里加了一道防护构建前先判断 outDir 是否等于dist根目录是的话中断并提示。3.5 打包后还要改配置运行时注入方案有类需求会突破“构建期写死”这个前提交付给客户的镜像需要现场改接口地址或者需要一份产物在多个机房复用还有些私有化部署场景客户不允许重新打包。这种时候就需要把一部分配置从构建期挪到运行期。我的做法是在public目录放一个config.js构建时它会被原样拷到产物根目录运行时由 index.html 加载挂到 window 上。// public/config.js —— 部署时可被运维直接改写 window.__APP_CONFIG__ { apiBase: /api, uploadMaxSize: 20, featureFlags: { newDashboard: false, }, }!-- index.html -- script src%BASE_URL%config.js/script script typemodule src/src/main.ts/script// src/config/runtime.ts type RuntimeConfig { apiBase: string uploadMaxSize: number featureFlags: Recordstring, boolean } const fallback: RuntimeConfig { apiBase: import.meta.env.VITE_API_BASE, uploadMaxSize: Number(import.meta.env.VITE_PAGE_SIZE) || 20, featureFlags: {}, } export const runtimeConfig: RuntimeConfig { ...fallback, ...(window as any).__APP_CONFIG__, }这套方案的关键在于把变量分成两类。构建期变量比如构建时间、版本号、打包时的环境标识留在 .env 里运行期变量接口地址、开关、外部链接走 config.js。两边的默认值要对齐config.js 加载失败时退回构建期的值页面不至于白屏。这里有个坑必须提醒config.js是静态资源浏览器会缓存它。运维改完文件刷新页面没生效的情况我遇到过好几次。解决办法要么在引用时加时间戳查询参数构建期生成要么在服务端对这个文件单独设置不缓存响应头。前者简单代价是每次构建都产生一个不同的引用反而更难缓存后者需要在部署配置里动手。我现在倾向后者让 config.js 走no-cache其它静态资源长缓存职责分得清。另外文件名带 hash 的问题也值得说一句。public目录下的文件不参与 hash 计算原样拷贝这正好符合我们的要求。如果放到src里 import就会被加上 hash运维就没法稳定找这个文件了。4. 类型提示、校验与团队协作4.1 env.d.ts 里把变量声明补全不补类型的时候import.meta.env.VITE_XXX在 TS 里是any编辑器没有补全写错变量名也不报错运行起来才拿到 undefined。补一下成本很低收益很大。// src/vite-env.d.ts /// reference typesvite/client / interface ImportMetaEnv { readonly VITE_APP_TITLE: string readonly VITE_ENV_NAME: dev | staging | prod readonly VITE_API_BASE: string readonly VITE_CDN_BASE: string readonly VITE_ENABLE_MOCK: true | false readonly VITE_BUILD_OUT_DIR: string } interface ImportMeta { readonly env: ImportMetaEnv }注意我把VITE_ENV_NAME和VITE_ENABLE_MOCK都写成了字面量联合类型而不是string。这样在业务代码里做比较时编辑器会提示可用值写错stageing立刻标红。这个细节带来的收益比想象中大尤其是团队新人多的时候。还有一个配套动作把这份声明当成契约。每次新增变量先改声明再加 .env 文件。顺序反过来容易漏。我在 code review 时的检查点之一就是“diff 里有新增的 VITE_ 变量但没有对应的类型声明”。4.2 构建前把必需变量卡住类型声明只在编译期起作用运行时变量缺失仍然会静默失败。所以在 vite.config.ts 里加一次性校验很有价值。const REQUIRED [VITE_API_BASE, VITE_ENV_NAME] as const export default defineConfig(({ mode }) { const env loadEnv(mode, process.cwd(), ) const missing REQUIRED.filter((k) !env[k]) if (missing.length) { throw new Error([env] 缺少必需变量: ${missing.join(, )}当前 mode: ${mode}) } // ... })throw会让构建直接中断CI 上立刻暴露。相比让产物打到线上再由用户发现接口 404这个成本低得多。更进一步我见过一种做法是校验变量值的“合法域”。比如VITE_ENV_NAME只允许三个值出现别的立刻报错。这防止了从别的分支复制 .env 文件时没改干净的情况——这事儿挺常见预发包带着生产地址上线属于严重事故。校验逻辑十行代码能挡掉一整类问题。如果你所在团队有 CI可以把这个校验脚本单独提出来在构建之前跑一遍甚至做成 pre-commit 钩子检查 .env 文件的变更。这类工具化的投入长期看很值。4.3 哪些变量绝对不能加 VITE_ 前缀这条规则值得单独占一节因为它涉及安全边界。凡是带VITE_前缀的变量最终都会以明文形式躺在前端 bundle 里。所以下面这些一个都不能加前缀第三方服务的密钥、带签名的私有令牌、内部服务的账号密码、任何你不希望用户在浏览器 DevTools 里搜到的内容。我遇到过有人把地图服务的 key 直接放VITE_MAP_KEY里打进产物觉得“前端用的 key 无所谓”。这个判断要看具体服务商有的 key 可以配合域名白名单限制风险可控有的 key 一旦泄露就能被随意调用产生费用。判断不了的时候一律走后端代理转发前端不持有凭证。那构建期需要用到这些密钥怎么办比如构建完成后上传 sourcemap 到错误收集平台。这时候用不带前缀的变量配合loadEnv的第三参数空字符串在vite.config.ts里读出来用用完即弃不进产物。这个模式我在两个项目里落地过效果不错。还要提醒一句.env.local的权限问题。有些团队把它提交了理由是“方便新人快速上手”。这相当于把本地调试配置变成了团队共享配置一个人的改动会影响所有人而且一旦里面有过期凭证扩散范围就大了。我坚持的做法是提交一份.env.example作为模板.env.local严格忽略。5. 常见问题排查实录5.1 高频问题速查表真跑起来之后问题基本集中在几个固定位置。我把它们整理成一张速查表遇到事先对号入座比到处翻文档快。现象大概率原因处理方式变量读出来是 undefined没加VITE_前缀或 .env 文件名拼错检查前缀与文件名的 mode 拼写改了 .env 不生效打包后值是固定的或浏览器缓存重新构建确认访问的是新产物布尔判断总是成立值是字符串false用 true显式比较本地配置污染了测试包.env.local全 mode 生效检查该文件是否被提交或残留产物目录被清空emptyOutDir生效且 outDir 重叠每个环境用独立子目录CI 上构建报变量缺失CI 环境没有 .env 文件把 .env 纳入仓库或由流水线注入部署到子路径后资源 404base与实际部署路径不一致用环境变量控制 base 并保持斜杠运维改 config.js 不生效静态资源被长缓存对该文件单独设置不缓存头表里最后一条值得多说两句因为它涉及“构建期”和“运行期”两种配置思路的切换成本。一旦项目决定用 config.js 做运行时注入就必须同步通知部署环节让他们知道这个文件的特殊性。我在一个项目里做过交接文档里专门开了一节讲这个文件能不能改、改了怎么验证、改错怎么回滚。5.2 几个靠猜猜不出来的坑第一个坑是.env文件的编码和换行符。Windows 上编辑生成的文件如果是 CRLF 换行偶尔会导致最后一行解析异常。这个不是每次都复现很难查。现在我统一在.gitattributes里指定*.env text eollf并且编辑器设置成 UTF-8 无 BOM。BOM 尤其要注意带 BOM 的文件第一行 key 会解析失败症状就是“第一个变量永远读不到”特别迷惑。第二个坑是变量名里的小写。我用过VITE_api_base这种写法在某些场景下能跑但一旦和其他工具链配合就出问题。约定上保持一致环境变量名统一大写加下划线这既是惯例也避免了跨平台的不一致。第三个坑是多入口项目里的变量作用范围。如果你的项目有多个 html 入口index.html 里能用的%VITE_XXX%替换只在各自的 html 里生效不会跨文件。我试过在一个子入口里引用只定义在主入口环境的变量结果是原样输出%VITE_XXX%字符串页面直接露馅。第四个坑是 mode 拼写错误导致的“静默降级”。传--mode prod但文件叫.env.productionVite 不会报错它只是找不到对应文件然后所有变量回退到.env和.env.local的值。如果你.env里恰好有兜底值页面能打开接口却指向了错误地址排查起来非常绕。这就是为什么我在 3.2 节强调 mode 要写死在 scripts 里——人手动敲字必然会出错。第五个坑是关于构建缓存。有些 CI 平台会缓存node_modules和构建中间产物如果缓存策略没覆盖.env文件可能出现“改了环境变量但产物没变”的情况。判断方法很简单产物里搜一下旧值还在不在在就说明缓存命中了。处理方式是在流水线里把.env*加入缓存键的计算范围。5.3 一个万能的自查流程遇到环境变量相关的故障我一般按这个顺序走一遍绝大多数情况三步内定位。第一步在vite.config.ts里临时打一行console.log(mode, env)看配置文件读到的值和预期是否一致。这一步能区分“文件没读到”和“业务代码没拿到”两类问题。第二步在浏览器控制台执行import.meta.env但这只在开发环境可行生产构建里拿不到。所以更通用的做法是在入口文件里临时加一行console.log(import.meta.env.VITE_XXX)构建后看产物内容或者页面控制台。第三步去构建产物的 js 里 grep 那个值。搜到了说明注入成功问题在使用方搜不到说明注入环节就错了回去检查前缀和文件名。这一步最直接我几乎每次都用。6. 我在几个项目里沉淀下来的做法6.1 变量命名与环境矩阵的约定做了几个项目之后我把变量命名收敛成三段式VITE_ 域 具体含义。域目前只有四个APP应用层面、API接口相关、BUILD构建行为、FEATURE功能开关。这样一眼看名字就知道该找哪个模块也方便按域做批量检查。环境矩阵也固定下来。行是环境dev、staging、prod列是变量哪些列必须在每一行都有值用一张表管起来。哪个环境缺哪个值一眼能看出来。这张表我放在项目文档里跟代码一起维护。有人觉得这是额外负担但实际省下的是排查时间——尤其是接手别人项目的时候五分钟看完矩阵就能上手。还有一条不成文的约定一个.env.[mode]文件里的变量数量控制在十五个以内。超过这个量说明有东西放错了位置多半是业务常量混进了环境配置。这条规矩执行下来我们的环境文件一直很好读。6.2 环境模板文件的维护方式.env.example这个文件我在每个项目里都放它承担两个职责新人入职时的参照物以及 CI 检查的基准。内容上我会把每个变量的用途写成注释包括取值范围和默认值。比如# 接口根地址末尾不要带斜杠 # 取值示例https://api-staging.example.com VITE_API_BASE # 构建产目录相对于项目根目录 # staging 环境用 dist/stagingprod 用 dist/prod VITE_BUILD_OUT_DIR # 是否开启本地 mock字符串 true / false VITE_ENABLE_MOCKfalse注释比变量本身重要。三个月后回来看变量名基本能猜出用途但“末尾要不要带斜杠”这种事只有当时写的人知道。我踩过一次接口地址末尾多了一个斜杠拼出来的请求路径变成双斜杠后端把路由匹配拒了查了两小时。同步机制上我做了一个简易的比对脚本读取.env.example的所有 key逐个检查各个.env.[mode]里是否存在缺了就打印出来。这个脚本挂在构建前置步骤上跑得很快能防住“新增变量只改了一份文件”这类漏改。6.3 关于这套方案适用边界的个人判断最后说说我个人的判断这套基于 .env 的自定义打包方案适合环境数量在五个以内、配置项以字符串为主的场景。超过这个规模或者配置结构变得复杂比如需要按地域分组、按租户区分我会改用一份构建期的 JSON 配置文件配合代码生成脚本而不是继续往 .env 里堆。dotenv 的格式限制太多不支持嵌套结构硬塞进去只会让解析代码越来越丑。还有一个判断是如果你的产物需要被反复部署到不同环境而不允许重新构建那从一开始就该把运行时注入的架子搭好不要等到交付前才想起来改。我见过一个项目在上线前一周才发现客户要求现场改配置临时加了 config.js结果接口请求模块、上传模块、日志模块全都要改回归测试做了一整轮。真要做好这块我觉得比配置本身更重要的是两条纪律环境文件入库且可追溯构建命令不允许临时拼参数。这两条执行到位剩下的都是细节问题遇到查一查就好。
阅读完成 · 觉得有帮助?