103 lines
4.1 KiB
JavaScript
103 lines
4.1 KiB
JavaScript
/**
|
||
* 按环境加载 .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 可见。
|