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

119 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 环境变量分层(.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](./error-handling.md),路由与权限见 [page-routing.md](./page-routing.md)、[permission.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 优先、常量兜底":
```ts
/** 取环境变量:非字符串、空串、纯空白都算未配置,回落 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 构建会把那行编进去):
```ts
let devBaseUrl = WebSitePolicy.WebApiRootDev;
// #ifdef APP-PLUS
devBaseUrl = WebSitePolicy.WebApiRootDevApp;
// #endif
export const baseUrl = import.meta.env.DEV ? devBaseUrl : WebSitePolicy.WebApiRoot;
```
## 4. 硬规矩:只能用字面量读
```text
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 只认整行 `#` 注释,一行末尾换行再接裸文本会被当成键值行解析。