Files
hjc-web/scripts/with-env.mjs
T
2026-09-19 00:52:04 +08:00

103 lines
4.1 KiB
JavaScript
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 文件,然后启动 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 可见。