功能说明
parseDotenv 把 .env 文件内容解析为普通对象,支持注释、空行、export 前缀、单双引号、双引号转义与跨行值。它只做解析,不写入 process.env,便于在加载前做校验、合并不同来源或在测试中构造配置。适合 CLI 工具、本地开发启动器、配置检查脚本和多环境部署工具。
关键代码
先把换行统一为 LF,再逐行处理。去除首尾空白后跳过空行与注释,识别 export 前缀并用正则校验键名与等号。双引号值会持续向后拼接行,直到找到未被反斜杠转义的闭合引号,再还原换行、制表符、引号与反斜杠转义;单引号不做转义;裸值只去掉空白后的行内注释。解析结果以键值对象返回。
使用注意
该实现不做变量插值,类似美元符号加花括号写法的引用会保持字面值;需要展开时应显式实现并限制递归深度。重复键以后出现的值为准,这与部分 dotenv 实现的先到先得不同。裸值中的井号只有在空白后才视为注释,值内部井号会保留。解析成功不代表配置合法,键名与值仍需用 Schema 校验后再注入环境。
验证方式
测试注释、空行、export、裸值、单引号、双引号、转义换行与多行双引号。验证裸值中的井号与空白注释的区分、重复键覆盖、键名包含点号与连字符。测试缺少等号、非法键名、未闭合引号会抛错。最后确认函数不会读写 process.env,且 CRLF 与 LF 输入得到相同结果。
代码示例
export function parseDotenv(input: string): Record<string, string> {
if (typeof input !== "string") throw new TypeError("input must be a string");
const escapes: Record<string, string> = { "\\": "\\", n: "\n", r: "\r", t: "\t", '"': '"' };
const result: Record<string, string> = {};
const lines = input.replace(/\r\n?/g, "\n").split("\n");
for (let index = 0; index < lines.length; index += 1) {
let line = lines[index]!.trim();
if (line === "" || line.startsWith("#")) continue;
if (line.startsWith("export ")) line = line.slice("export ".length).trim();
const match = /^([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.*)$/.exec(line);
if (!match) throw new SyntaxError("invalid dotenv line " + (index + 1));
const key = match[1]!;
let raw = match[2]!;
let value: string;
if (raw.startsWith('"')) {
while (!/(^|[^\\])"\s*(#.*)?$/.test(raw)) {
index += 1;
if (index >= lines.length) throw new SyntaxError("unterminated quoted value for " + key);
raw += "\n" + lines[index]!;
}
const end = raw.lastIndexOf('"');
value = raw.slice(1, end).replace(/\\([\\nrt"])/g, (_, character: string) => {
return escapes[character] ?? character;
});
} else if (raw.startsWith("'")) {
let end = 1;
while (end < raw.length) {
if (raw[end] === "'") {
if (raw[end + 1] === "'") {
end += 2;
continue;
}
break;
}
end += 1;
}
if (end >= raw.length) throw new SyntaxError("unterminated quoted value for " + key);
value = raw.slice(1, end).replace(/''/g, "'");
} else {
value = raw.replace(/\s+#.*$/, "").trim();
}
result[key] = value;
}
return result;
}