ow
This commit is contained in:
1 parent
6a8258ac4b
commit
a7b25d49c4
29 files changed
+1960
-649
No files matched your search
@@ -0,0 +1,118 @@
|
||||
# 环境变量分层(.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 只认整行 `#` 注释,一行末尾换行再接裸文本会被当成键值行解析。
|
||||
Reference in new issue
Block a user