关于项目环境变量配置的相关问题解答
我们通常会使用 CLI 模板创建初始项目,项目中的 .env
文件用于配置项目的环境变量,比如开发环境、测试环境、生产环境等。示例如下:
ini
NODE_ENV=production
VUE_APP_API_URL=/api
在项目中可以通过 process.env.NODE_ENV
来获取当前的环境变量,示例代码:
ini
const isProduction = process.env.NODE_ENV === 'production';
此外,也能在 vue.config.js
里配置项目的环境变量,示例如下:
arduino
// 根据不同环境配置生产环境的资源路径
publicPath: process.env.NODE_ENV === 'production' ? '/prod/' : '/',
// 根据不同环境配置生产环境的输出目录
outputDir: process.env.NODE_ENV === 'production' ? 'dist-prod' : 'dist-dev'
但有时候,对其原理的理解不是很深入,所以就有了一些疑问。 下面来详细解答一些关于环境变量配置的疑问。
仅考虑 Webpack 配置文件的情况
1. process.env.NODE_ENV
是什么?
process
是 Node.js 提供的全局变量,其内部包含了一些全局对象,如 process.env
、process.argv
、process.stdin
、process.stdout
、process.stderr
等。process.env
是其中一个对象,NODE_ENV
本身并不存在,需要我们自行赋值。
2. 如何设置这个属性,何时设置,使用什么方法?
Node 命令行中:
ini
process.env.NODE_ENV = 'development';
Webpack 项目里,在 package.json
中:
json
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config webpack.config.js"
}
}
cross-env
用于管理项目中的环境变量,能解决不同环境系统间设置环境变量时命令行不同的问题。在 Windows 中使用 "SET NODE_ENV=development"
,在 Unix/Linux 系统中使用 "EXPORT NODE_ENV=development"
。
借助 dotenv
之类的工具,在 .env
文件中:
ini
NODE_ENV=development
3. 在 Webpack 中,如何获取这个属性?
ini
const webpack = require('webpack');
const mode = process.env.NODE_ENV || 'development';
module.exports = {
mode: mode,
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(mode)
})
]
};
在项目中就可以通过 process.env.NODE_ENV
来获取当前的环境变量,示例代码:
arduino
if (process.env.NODE_ENV === 'development') {
console.log('当前处于开发环境');
} else if (process.env.NODE_ENV === 'production') {
console.log('当前处于生产环境');
}
考虑 .env
文件的情况
其实就是将上述设置方法换成从文件中读取,主要分为两种情况:使用 dotenv
或者读取文件。
1. 使用 dotenv
**安装 **dotenv
:
css
npm install dotenv --save-dev
在 package.json
中:
json
{
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config webpack.config.js",
"prod": "cross-env NODE_ENV=production webpack --config webpack.config.js"
}
}
webpack.config.js
** 配置**:
ini
const dotenv = require('dotenv');
const webpack = require('webpack');
// 根据 NODE_ENV 选择环境变量文件
const envFile = process.env.NODE_ENV === 'production' ? '.env.prod' : '.env.dev';
// 加载环境变量
const envConfig = dotenv.config({ path: envFile }).parsed;
// 将环境变量注入到 Webpack 中
const envKeys = Object.keys(envConfig).reduce((prev, next) => {
prev[\`process.env.\${next}\`] = JSON.stringify(envConfig[next]);
return prev;
}, {});
module.exports = {
// 其他 Webpack 配置
plugins: [
new webpack.DefinePlugin(envKeys)
]
};
解释:这里将 NODE_ENV
属性交由 cross-env
来管理,从文件中读取其他属性,如 VUE_APP_API_URL
等。如果要在 .env
中设置 NODE_ENV
,可以这样做:
json
{
"scripts": {
"dev": "cross-env ENV_FILE=.env.dev webpack --config webpack.config.js",
"prod": "cross-env ENV_FILE=.env.prod webpack --config webpack.config.js"
}
}
arduino
// 获取要读取的环境变量文件
const envFile = process.env.ENV_FILE || '.env';
// 加载环境变量
const envConfig = dotenv.config({ path: envFile }).parsed;
// 其他代码与上面一致
2. 读取文件
ini
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
// 手动读取 .env 文件
const envFile = process.env.NODE_ENV === 'production' ? '.env.prod' : '.env.dev';
const envPath = path.resolve(__dirname, envFile);
const envFileContent = fs.readFileSync(envPath, 'utf8');
// 解析 .env 文件内容
const envVars = {};
envFileContent.split('\n').forEach(line => {
const [key, value] = line.split('=');
if (key && value) {
envVars[key.trim()] = value.trim();
}
});
// 使用 .env 文件中的 NODE_ENV 变量
const nodeEnv = envVars.NODE_ENV;
console.log('当前环境:', nodeEnv);
Vue CLI 项目中直接使用 .env
文件的实现原理
在 package.json
中:
json
{
"scripts": {
"serve": "vue-cli-service serve --mode dev"
}
}
启动服务时,会运行 vue-cli-service
命令,该命令会去 node_modules/.bin
目录下查找 vue-cli-service
命令并执行。
vue-cli-service
主要代码
arduino
const Service = require('../lib/Service')
const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())
const rawArgv = process.argv.slice(2)
const args = require('minimist')(rawArgv, {
boolean: [
// build
'modern',
'report',
'report-json',
'inline-vue',
'watch',
// serve
'open',
'copy',
'https',
// inspect
'verbose'
]
})
const command = args._[0]
// 获取一些参数,调用 service.run 方法。
service.run(command, args, rawArgv).catch(err => {
console.error(err)
process.exit(1)
})
Service.js
中的 run
方法
kotlin
async run (name, args = {}, rawArgv = []) {
// 优先使用命令行内联的 --mode 参数
// 如果定义了 --watch 参数,则回退到从插件解析出的默认模式,否则使用开发模式
const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])
// --skip-plugins 参数可能包含在初始化期间应跳过的插件
this.setPluginsToSkip(args)
// 加载环境变量,加载用户配置,应用插件
this.init(mode)
// 其他代码
}
Service.js
中的 init
方法
kotlin
init (mode = process.env.VUE_CLI_MODE) {
if (this.initialized) {
return
}
this.initialized = true
this.mode = mode
// 主要看 loadEnv
// load mode .env
if (mode) {
this.loadEnv(mode)
}
// load base .env
this.loadEnv()
// 其他代码
}
Service.js
中的 loadEnv
方法
arduino
/\*\*
\* 加载指定模式的环境变量文件
\* @param {string} mode - 环境模式,如 'development', 'production' 等
\*/
loadEnv (mode) {
// 创建一个调试日志记录器,用于记录环境变量加载相关信息
const logger = debug('vue:env')
// 解析基础环境变量文件的路径,根据模式决定文件名是否包含模式后缀
const basePath = path.resolve(this.context, \`.env\${mode ? \`.\${mode}\` : \`\`}\`)
// 解析本地环境变量文件的路径,即在基础路径后添加 .local 后缀
const localPath = \`\${basePath}.local\`
const load = envPath => {
try {
// 使用 dotenv 加载指定路径的环境变量文件,并开启调试模式(根据环境变量 DEBUG 决定)
const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })
// 扩展环境变量,解析其中的变量引用
dotenvExpand(env)
// 使用日志记录器记录加载的环境变量文件路径和加载结果
logger(envPath, env)
} catch (err) {
// 仅在文件不存在时忽略错误
if (err.toString().indexOf('ENOENT') < 0) {
// 若不是文件不存在的错误,则输出错误信息
console.error(err)
}
}
}
// 优先加载本地环境变量文件
load(localPath)
// 再加载基础环境变量文件
load(basePath)
// 默认情况下,除非模式为 production 或 test,否则 NODE_ENV 和 BABEL_ENV 会被设置为 "development"。
// 不过 .env 文件中的值优先级更高。
if (mode) {
// 始终在测试期间设置 NODE_ENV,因为这对于避免测试相互影响是必要的
const shouldForceDefaultEnv = (
process.env.VUE_CLI_TEST &&
!process.env.VUE_CLI_TEST_TESTING_ENV
)
// 根据模式确定默认的 NODE_ENV 值
const defaultNodeEnv = (mode === 'production' || mode === 'test')
? mode
: 'development'
// 如果需要强制使用默认环境变量,或者当前未设置 NODE_ENV,则设置 NODE_ENV
if (shouldForceDefaultEnv || process.env.NODE_ENV == null) {
process.env.NODE_ENV = defaultNodeEnv
}
// 如果需要强制使用默认环境变量,或者当前未设置 BABEL_ENV,则设置 BABEL_ENV
if (shouldForceDefaultEnv || process.env.BABEL_ENV == null) {
process.env.BABEL_ENV = defaultNodeEnv
}
}
}
其中,dotenvExpand
是一个库,用于解析 .env
文件中的变量引用并将其扩展到环境变量中。
dotenvExpand
函数定义
ini
var dotenvExpand = function (config) {
// 如果配置中设置了忽略 process.env,则使用空对象,否则使用 process.env
var environment = config.ignoreProcessEnv ? {} : process.env;
// 遍历 config.parsed 中的每个环境变量键
for (var key in config.parsed) {
if (config.parsed.hasOwnProperty(key)) {
// 获取当前键对应的值
var value = config.parsed[key];
// 将该值赋给 config.parsed 中对应的键
config.parsed[key] = value;
// 将该值同步到 environment 中
environment[key] = value;
}
}
return config;
};
module.exports = dotenvExpand;
再回到命令 vue-cli-service serve --mode dev
,mode
是 dev
,所以会加载 .env.dev
文件,然后将 .env.dev
文件中的属性注入到 process.env
中。