/** * 按环境加载 .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=` 传参,平台无关。 * * 加载顺序(先加载的优先级更高,已存在的变量不会被后续文件覆盖): * 1. 进程已有的环境变量 ← 最高(CI / 命令行传入) * 2. .env..local ← 个人针对某环境的覆盖 * 3. .env.(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= 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 可见。