Files
Docker/uniapp/youjiala/src/md/environment.md
T
2026-10-13 10:47:39 +08:00

6.7 KiB
Raw Blame History

环境变量分层(.env 与 WebSitePolicy 的分工)

适用范围:项目根 .env / .env.development / .env.production、src/types/env.d.ts、src/policy/custom/webSitePolicy.ts、src/common/http.interceptor.ts 的地址取值。 本文记录"为什么这样分层""三端读取为什么必须写字面量",以及新增一个变量的固定步骤。错误处理口径见 error-handling.md,路由与权限见 page-routing.md、permission.md。


1. 分层模型:谁覆盖谁

vite.config.ts 没有设置 envDir / envPrefix,所以走 Vite 默认:

  • 加载目录 = 项目根(uniapp/youjiala/,即 index.html 所在层),不是 src/;
  • 只有 VITE_ 前缀的键会注入 import.meta.env,其它键不会(也不该)出现在前端产物里。
文件 何时生效 是否提交
.env 所有 mode 的基底 提交
.env.development uni / uni -p xxx(mode=development) 提交
.env.production uni build / uni build -p xxx(mode=production) 提交
.env.local、.env.development.local、.env.production.local 同名 mode 下优先级最高 不提交(.gitignore 里 *.local 已覆盖)

mode 由 uni 命令决定(要指定别的 mode 得显式加 --mode):

uni / uni -p mp-weixin / uni -p app    =>  development  =>  .env + .env.development(+.local)
uni build / uni build -p xxx           =>  production   =>  .env + .env.production(+.local)

同名项后者覆盖前者;.env.development / .env.production 里只列与 .env 不同的项,重复抄一遍只会让三处值互相打架。

2. 只放"换环境要动"的东西

当前只有三个键,全部是后端根地址:

键 生效场景 缺省回落值
VITE_WEB_API_ROOT 发布构建(三端共用) https://uat.youjiala.com
VITE_WEB_API_ROOT_DEV 开发构建,H5 http://localhost:5235
VITE_WEB_API_ROOT_DEV_APP 开发构建,App 真机/模拟器 https://uat.youjiala.com

分工纪律:

  • 品牌与文案(Title / Copy / Beian)、路由常量(Dashboard / Web401~404)、时间格式留在 webSitePolicy.ts 的常量里,不进 .env:它们跟着版本走,不跟着环境走。
  • 密钥、token、数据库口令一律禁止进 .env。前端产物人人可读,.env 又被提交,任何写进来的凭据等同于公开发布。
  • 本机差异(自己的后端端口、局域网 IP)写 .env.*.local,不要提交本机地址。

3. fallback:删掉 .env 也必须能构建

webSitePolicy.ts 里每个地址都写成"env 优先、常量兜底":

/** 取环境变量:非字符串、空串、纯空白都算未配置,回落 fallback */
function env(value: string | undefined, fallback: string): string {
  const text = String(value ?? "").trim();
  return text.length > 0 ? text : fallback;
}

export class WebSitePolicy {
  static readonly WebApiRootDev: string = env(
    import.meta.env.VITE_WEB_API_ROOT_DEV,
    "http://localhost:5235",
  );
}

所以:

  • .env 缺失、键漏写、值写成空串或只有空格 —— 都落到文件里的常量,构建不报错、产物可运行;
  • 文件里的常量是最后一道防线,不是摆设,改动它等于改默认环境,需要同期更新本文与 README 的表格。

http.interceptor.ts 按端挑选常量(#ifdef APP-PLUS 是 uni 的条件编译,只有 App 构建会把那行编进去):

let devBaseUrl = WebSitePolicy.WebApiRootDev;
// #ifdef APP-PLUS
devBaseUrl = WebSitePolicy.WebApiRootDevApp;
// #endif
export const baseUrl = import.meta.env.DEV ? devBaseUrl : WebSitePolicy.WebApiRoot;

4. 硬规矩:只能用字面量读

import.meta.env.VITE_WEB_API_ROOT;   // ✅ 三端都正常
import.meta.env[key];                // ❌ 小程序 / App 真机上拿到 undefined

H5 走浏览器 ESM,import.meta.env 是个真实对象,动态取键也能 work;小程序与 App 端由 uni 编译器在构建期把 import.meta.env.VITE_XXX 静态替换成字符串字面量,动态键没人替换,运行时对象根本不存在。

src/types/env.d.ts 显式声明这三个键(vite/client 自带的 ImportMetaEnv 有 [key: string]: any 索引签名,不声明时拼错键名是 any、编译器一声不响)。新增变量必须同时补这份声明,否则失去拼写保护。

5. 新增一个环境变量的固定四步

  1. 项目根 .env 加 VITE_XXX=默认值,只在需要按 mode 区分的文件里写覆盖;
  2. src/types/env.d.ts 的 ImportMetaEnv 补一行 readonly VITE_XXX: string;;
  3. 消费点写成字面量 import.meta.env.VITE_XXX;若是地址/配置类,走 webSitePolicy 的 env() 包裹以保留 fallback,业务代码继续只读 WebSitePolicy.*;
  4. 更新本文 §2 的表格与 README 的"后端地址"一节。

6. 实测结论(哨兵法)

改动 .env 后不能只看代码,要验证值真的进了产物。做法:把 .env.production 的 VITE_WEB_API_ROOT 临时改成一个必然不可能出现在源码里的域名(如 https://env-probe.invalid),跑 npm run build 与 npm run build:mp-weixin,然后在产物里搜这个域名。

本轮结果(2026-10-13):

端 产物命中哨兵 残留 import.meta 结论
H5 是 否 env 生效
mp-weixin 是(dist/build/mp-weixin/policy/custom/webSitePolicy.js) 否 编译器已静态替换,小程序同样吃 .env

验证完必须把 .env.production 恢复原值。搜索小程序产物要用递归方式:Get-ChildItem -Recurse | Select-String,Select-String -Path 'dist\**\*.js' 这种通配在 PowerShell 里不可靠,会给出假的"0 命中"。

7. 常见坑

  • 改了 .env 不生效:Vite 不会热更新 env 文件,必须重启 uni / uni -p 进程。
  • 小程序请求发不出去:平台侧要求 https 且域名需在小程序后台"request 合法域名"里登记;本地联调用 urlCheck: false(manifest.json 的 mp-weixin.setting)只是让开发者工具不拦,正式包仍受平台约束。
  • H5 连本地后端报 CORS:是后端未放行开发端口,不是 .env 的问题;改 VITE_WEB_API_ROOT_DEV 换到已放行的地址即可。
  • App 真机连不上:真机/模拟器上没有宿主机的 localhost,走 VITE_WEB_API_ROOT_DEV_APP(默认已指向 UAT),需要连本机后端时把它改成局域网 IP。
  • 别在 .env 里写注释续行:dotenv 只认整行 # 注释,一行末尾换行再接裸文本会被当成键值行解析。