为什么要用 .env?
在前端开发中,我们经常需要区分不同环境下的配置,比如:
- 开发环境(开发调试用的 API 地址、本地调试开关);
- 测试环境(给测试同学用的接口域名、测试账号);
- 生产环境(线上稳定接口、CDN 域名、是否启用 mock 数据等)。
如果把这些配置直接写死在代码里,不仅管理麻烦,还容易出现「改配置 → 重新打包 → 发错环境」的坑。
这时候 .env
文件就派上用场了。
👉 它能把不同环境的变量抽离出来,统一放在一个文件里,通过工具在打包或运行时注入到代码中,大大提升了项目的可维护性。
环境准备
我们先来简单准备下环境。
如果想直接看 dotenv
的源码,可以克隆官方项目:
bash
git clone https://github.com/motdotla/dotenv.git
# cd dotenv && yarn i
# VSCode 打开当前项目
code .
dotenv 的作用
官方项目地址:
dotenv
是一个零依赖模块,用来把 .env
文件中的环境变量加载到 process.env
中。
如果需要进一步使用变量展开,还可以配合 dotenv-expand 一起使用。
在前端项目里,.env
文件非常常见,比如 vue-cli
和 create-react-app
都支持它。
.env 文件写法示例
ini
NAME=gengyun
AGE=18
VITE_APP_ENV=production
VITE_ROUTER_BASE_URL=/ai-form
VITE_APP_PRO_USE_MOCK=true
从上面这个 .env
文件就能看出来,.env
的主要用途就是:
- 读取
.env
文件; - 解析成
key=value
形式的对象; - 挂载到
process.env
上; - 最后返回解析好的对象,方便使用。
一个简易版实现
下面写一个简单的实现,核心就是 用 Node.js 读取文件,解析成对象,然后赋值给 process.env
。
javascript
const fs = require('fs');
const path = require('path');
const parse = function (src) {
const obj = {};
src.toString().split('\n').forEach(function (line) {
const keyValueArr = line.split('=');
const key = keyValueArr[0];
const val = keyValueArr[1] || '';
obj[key] = val;
});
return obj;
}
const config = function () {
const dotenvPath = path.resolve(process.cwd(), '.env');
const parsed = parse(fs.readFileSync(dotenvPath, 'utf-8'));
Object.keys(parsed).forEach(function (key) {
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
process.env[key] = parsed[key];
}
});
return parsed;
};
console.log(config());
console.log(process.env);
module.exports.config = config;
module.exports.parse = parse;
虽然能用,但这个 config
函数还很简陋,缺少一些常见功能,比如:
- 支持用户自定义
.env
文件路径; - 支持自定义编码;
- 添加
debug
模式,方便输出提示; - 更友好的报错信息(毕竟
.env
文件写法很随意)。
升级版实现
官方的 dotenv
库在源码里对这些功能做了处理,比如路径解析、编码设置、调试日志等。我们也可以稍微扩展一下:
ini
const fs = require('fs');
const os = require('os');
const path = require('path');
function resolveHome(envPath) {
return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath;
}
const config = function (options) {
let dotenvPath = path.resolve(process.cwd(), '.env');
let encoding = 'utf8';
let debug = false;
if (options) {
if (options.path != null) {
dotenvPath = resolveHome(options.path);
}
if (options.encoding != null) {
encoding = options.encoding;
}
if (options.debug != null) {
debug = true;
}
}
try {
const parsed = parse(fs.readFileSync(dotenvPath, { encoding }), { debug });
Object.keys(parsed).forEach(function (key) {
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
process.env[key] = parsed[key];
} else if (debug) {
console.log(`"${key}" is already defined in process.env and will not be overwritten`);
}
});
return parsed;
} catch (e) {
return { error: e };
}
};
而 dotenv
官方的 parse
函数还处理了更多细节,比如:
- 正则匹配;
- 支持单双引号;
- 跨平台兼容(Windows/Linux 换行符不同)。
一句话总结
dotenv
的原理很简单:
👉 用 fs.readFileSync
读取 .env
文件,解析成 key=value
对象,然后把对象里的变量挂到 process.env
上。
前端项目里的额外说明
不过要注意:
在 Node.js 环境 (如服务端)下,直接挂到 process.env
就能用了。
但在 前端项目 (浏览器环境)里,浏览器本身是访问不到 process
的。
所以像 vue-cli
或 vite
这样的工具,会在构建阶段借助 webpack 的 DefinePlugin
(或 Vite 内置的变量替换机制)把 .env
文件里的变量直接替换成具体值,注入到代码里。
总结 & 扩展思考
.env
文件的核心价值在于:
- 配置解耦:不用把不同环境的配置写死在代码里;
- 方便管理:一套代码,多套环境;
- 安全性:敏感信息可以集中管理,避免硬编码到仓库。
扩展思考:
- 在 前端项目 中,哪些变量适合放在
.env
?哪些不能?(比如 API 地址 可以,秘钥 一定不要!) - 在 多人协作 时,如何管理不同成员的本地
.env
文件?(比如.env.local
不要提交到 git) - 除了
dotenv
,有没有类似的工具?
这些问题都值得在实际项目中思考和总结。