开发 / 生产双配置

This commit is contained in:
2026-09-19 00:52:04 +08:00
parent 1ff4dfffb9
commit de412297fb
17 changed files with 303 additions and 591 deletions
+102
View File
@@ -0,0 +1,102 @@
/**
* 按环境加载 .env 文件,然后启动 Nuxt。
*
* 用法(推荐,跨平台):
* node --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs dev --env-name=development
* node --import ./scripts/with-env.mjs ./node_modules/nuxt/bin/nuxt.mjs build --env-name=production
*
* 为什么不用 `NUXT_ENV=production node ...` 这种内联赋值:
* npm 在 Windows 上通过 cmd.exe 执行脚本,`FOO=bar cmd` 不是合法语法,
* 会报 "'FOO' is not recognized as an internal or external command"。
* 也不要写成 `--import ./with-env.mjs .env.production --`Node 会把
* `--import` 之后、`--` 之前的每个裸参数都当成要加载的模块,
* `.env.production` 会触发 ERR_UNKNOWN_FILE_EXTENSION。
* 因此改用命令行参数 `--env-name=<name>` 传参,平台无关。
*
* 加载顺序(先加载的优先级更高,已存在的变量不会被后续文件覆盖):
* 1. 进程已有的环境变量 ← 最高(CI / 命令行传入)
* 2. .env.<name>.local ← 个人针对某环境的覆盖
* 3. .env.<name>development / production ← 团队共享配置
* 4. .env.local ← 个人通用覆盖
* 5. .env ← 最低(历史遗留的个人配置)
*
* 说明:Nuxt 内部通过 c12 读取 .env,而 c12 只在变量「尚未存在」时才写入
* process.env,因此这里先注入的值会被保留,不会被打包过程顶掉。
*/
import { existsSync, readFileSync } from 'node:fs'
import { resolve } from 'node:path'
/** 解析 .env 文本,返回键值对。支持 export 前缀、引号与 # 注释。 */
function parseEnv (text) {
const out = {}
for (const rawLine of text.split(/\r?\n/)) {
const line = rawLine.trim()
if (!line || line.startsWith('#')) continue
const eq = line.indexOf('=')
if (eq === -1) continue
const key = line.slice(0, eq).trim().replace(/^export\s+/, '')
if (!key) continue
let value = line.slice(eq + 1).trim()
// 去掉成对的引号(单引号内不做转义处理)
if (
(value.startsWith('"') && value.endsWith('"') && value.length >= 2) ||
(value.startsWith("'") && value.endsWith("'") && value.length >= 2)
) {
const quote = value[0]
value = value.slice(1, -1)
if (quote === '"') {
value = value
.replace(/\\n/g, '\n')
.replace(/\\r/g, '\r')
.replace(/\\t/g, '\t')
}
} else {
// 未加引号时,行尾注释按 dotenv 规则去掉(# 前需有空白)
value = value.replace(/\s+#.*$/, '').trim()
}
out[key] = value
}
return out
}
/** 读取文件并注入 process.env,已存在的键不覆盖。 */
function apply (file) {
const abs = resolve(process.cwd(), file)
if (!existsSync(abs)) return false
for (const [key, value] of Object.entries(parseEnv(readFileSync(abs, 'utf8')))) {
if (process.env[key] === undefined) process.env[key] = value
}
return true
}
// 从命令行参数中取 --env-name=<name>
const envArgIndex = process.argv.findIndex(a => a.startsWith('--env-name='))
const envName = envArgIndex === -1
? ''
: process.argv[envArgIndex].slice('--env-name='.length)
// 读完后从 argv 中移除,避免把这个自定义参数透传给 Nuxt(nuxi 只认 --envName
if (envArgIndex !== -1) process.argv.splice(envArgIndex, 1)
// 高优先级在前:由于「已存在则不覆盖」,先加载者胜出。
// 环境专属文件必须排在 .env 之前,否则历史遗留的 .env 会顶掉生产配置。
const files = []
if (envName) files.push(`.env.${envName}.local`)
if (envName) files.push(`.env.${envName}`)
files.push('.env.local')
files.push('.env')
files.forEach(apply)
if (envName && !existsSync(resolve(process.cwd(), `.env.${envName}`))) {
console.warn(
`[with-env] 警告:未找到 .env.${envName},将使用 nuxt.config.ts 内置默认值`
)
}
// 本模块只负责准备环境变量;Nuxt 由 Node 在加载本文件后继续执行,
// 两者同进程,因此 process.env 的修改对 Nuxt 可见。