引言
为什么前端项目需要配置不同的环境?
在公司开发实际项目时,你的项目一定要区分开发环境 、测试环境 和生产环境 。这两个环境是最基本的,再规范一点,还会分出预演环境。
最最基本的一个原因:在不同的环境下,需要使用不同的环境变量。举个最简单的例子,请求服务端提供的接口服务地址是不可能相同的,最佳的做法就是使用环境变量在不同的环境下自动帮我们使用对应的服务地址。
如上图:在封装axios时,使用process.env.VUE_APP_BASE_API
这个VUE_APP_BASE_API
就是环境变量。
那么在不同环境给这个环境变量设置不同的服务地址。
这样我们不需要频繁的去设置使用哪一个服务地址,只需要运行不同的执行命令,它自动会帮我们切换使用对应的环境变量。
环境变量的配置使用
不同的项目框架在配置和使用方面是有一点小小的区别 ,比如Vite和Vue-cli脚手架,他们在使用环境变量是有所不同的。 但是 无论是Vite还是Vue-cli脚手架,它们获取使用环境变量的本质都是基于
NodeJs环境
。
Vue-cli创建的项目
用脚手架命令vue create env-test
创建一个全新的项目,此时它的目录结构是这样的
然后在main.js
中输出
js
console.log('环境变量VUE_APP_BASE_API的值:',process.env)
这个process.env
是NodeJs中的一个全局对象,它包含了当前运行环境的环境变量。
需要添加环境变量,则在项目根目录创建用于设置环境变量的文件,文件名必须以.env
开头。
.env
文件会被所有环境应用到- 默认
.env.development
文件配置开发环境。 - 默认
.env.production
文件配置生产环境。 - 要配置其他环境,创建
.env.envName
文件,envName
名可以随你自己定义。 - 如果是自定义其它环境,在执行命令运行项目时,就要在后面加上命令行参数
--mode eneName
。 - 环境变量必须以
VUE_APP_
字符串开头,才会添加NodeJs的全局对象上。
举例说明
配置测试环境的环境变量,根目录下创建.env.test
文件,内部填充代码如下:
js
NODE_ENV = test
VUE_APP_BASE_API = 'http://localhost:3000'
那么就可以在package.json 文件中新添加一个脚本命令build:test
,运行npm run build:test
就是以测试环境的环境变量打包。
js
"scripts": {
...,
+ "build:test":"vue-cli-service build --mode"
},
通过process.env.VUE_APP_BASE_API
获取到测试环境用的服务地址。
Vite创建的项目
用npm create vite env-test
创建一个全新的项目,项目结构和Vue-cli的目录结构区别不大。而且创建环境变量文件配置环境变量的规则也相同,不同的是在访问环境变量的部分。
Vite添加环境变量也是使用.env
文件,规则与Vue-cli项目规则类似:
.env
文件会被所有环境应用到- 默认
.env.development
文件配置开发环境。 - 默认
.env.production
文件配置生产环境。 - 要配置其他环境,创建
.env.envName
文件,envName
名可以随你自己定义。 - 如果是自定义其它环境,在执行命令运行项目时,就要在后面加上命令行参数
--mode eneName
。 - 环境变量必须以
VITE_
字符串开头,才会添加NodeJs的全局对象上。
不同之处在于访问环境变量 使用import.meta.env
访问
import.meta.env
是一个特殊的对象,它暴露了当前运行环境的元信息和环境变量。Vite 在构建过程中会注入这些信息到 import.meta.env
对象中。
比如,添加环境变量VITE_BASE_API = 'http://test:3000'
,访问输出VITE_BASE_API
js
console.log(import.meta.env.VITE_BASE_API)
NodeJS环境process.env解析
前面简单介绍了环境变量的配置及使用,本质上都是基于NodeJs环境。那继续探讨下NodeJs是如何读取到这些环境变量的。
NodeJs中有一个全局对象process.env
,它包含了当前运行环境的环境变量。环境变量是操作系统级别的设置,它们是一组命名值对,用于存储系统配置、用户偏好等信息。
但是 初始时它内部并不包含在.env
文件中添加的环境变量,比如新建一个index.js文件,内部直接输出代码console.log(process.env)
。使用node环境运行这个文件,它输出的内容如下:
(不用关注各属性代表什么,只需要知道内部并没有自定义的环境变量)
js
{
ALLUSERSPROFILE: 'C:\\ProgramData',
APPDATA: 'C:\\Users\\A\\AppData\\Roaming',
CHROME_CRASHPAD_PIPE_NAME: '\\\\.\\pipe\\crashpad_8476_OTFEPFDJFQOBPKJS',
CommonProgramFiles: 'C:\\Program Files\\Common Files',
'CommonProgramFiles(x86)': 'C:\\Program Files (x86)\\Common Files',
CommonProgramW6432: 'C:\\Program Files\\Common Files',
COMPUTERNAME: 'DESKTOP-LK436MS',
ComSpec: 'C:\\WINDOWS\\system32\\cmd.exe',
DriverData: 'C:\\Windows\\System32\\Drivers\\DriverData',
EFC_13324: '1',
FPS_BROWSER_APP_PROFILE_STRING: 'Internet Explorer',
FPS_BROWSER_USER_PROFILE_STRING: 'Default',
HOMEDRIVE: 'C:',
HOMEPATH: '\\Users\\A',
INTEL_DEV_REDIST: 'C:\\Program Files (x86)\\Common Files\\Intel\\Shared Libraries\\',
LOCALAPPDATA: 'C:\\Users\\A\\AppData\\Local',
LOGONSERVER: '\\\\DESKTOP-LK436MS',
MIC_LD_LIBRARY_PATH: '%INTEL_DEV_REDIST%compiler\\lib\\mic',
NUMBER_OF_PROCESSORS: '12',
OneDrive: 'C:\\Users\\A\\OneDrive',
ORIGINAL_XDG_CURRENT_DESKTOP: 'undefined',
OS: 'Windows_NT',
Path: '%INTEL_DEV_REDIST%redist\\intel64\\compiler;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\WINDOWS\\System32\\OpenSSH\\;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;C:\\Program Files\\NVIDIA Corporation\\NVIDIA NvDLISR;C:\\Program Files\\Git\\cmd;D:\\program files\\微信web开发者工具\\dll;C:\\Program Files\\MySQL\\MySQL Server 8.0\\bin;C:\\Users\\A\\AppData\\Local\\Yarn\\bin;C:\\Program Files\\nodejs\\;C:\\Users\\A\\AppData\\Local\\Microsoft\\WindowsApps;D:\\program files\\Microsoft VS Code\\bin;C:\\Users\\A\\AppData\\Roaming\\npm',
PATHEXT: '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL',
PROCESSOR_ARCHITECTURE: 'AMD64',
PROCESSOR_IDENTIFIER: 'Intel64 Family 6 Model 151 Stepping 5, GenuineIntel',
PROCESSOR_LEVEL: '6',
PROCESSOR_REVISION: '9705',
ProgramData: 'C:\\ProgramData',
ProgramFiles: 'C:\\Program Files',
'ProgramFiles(x86)': 'C:\\Program Files (x86)',
ProgramW6432: 'C:\\Program Files',
PSModulePath: 'C:\\Users\\A\\Documents\\WindowsPowerShell\\Modules;C:\\Program Files\\WindowsPowerShell\\Modules;C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules',
PUBLIC: 'C:\\Users\\Public',
SESSIONNAME: 'Console',
SystemDrive: 'C:',
SystemRoot: 'C:\\WINDOWS',
TEMP: 'C:\\Users\\A\\AppData\\Local\\Temp',
TMP: 'C:\\Users\\A\\AppData\\Local\\Temp',
USERDOMAIN: 'DESKTOP-LK436MS',
USERDOMAIN_ROAMINGPROFILE: 'DESKTOP-LK436MS',
USERNAME: 'A',
USERPROFILE: 'C:\\Users\\A',
windir: 'C:\\WINDOWS',
TERM_PROGRAM: 'vscode',
TERM_PROGRAM_VERSION: '1.85.0',
LANG: 'en_US.UTF-8',
COLORTERM: 'truecolor',
GIT_ASKPASS: 'd:\\program files\\Microsoft VS Code\\resources\\app\\extensions\\git\\dist\\askpass.sh',
VSCODE_GIT_ASKPASS_NODE: 'D:\\program files\\Microsoft VS Code\\Code.exe',
VSCODE_GIT_ASKPASS_EXTRA_ARGS: '--ms-enable-electron-run-as-node',
VSCODE_GIT_ASKPASS_MAIN: 'd:\\program files\\Microsoft VS Code\\resources\\app\\extensions\\git\\dist\\askpass-main.js',
VSCODE_GIT_IPC_HANDLE: '\\\\.\\pipe\\vscode-git-75f3939000-sock',
VSCODE_INJECTION: '1'
}
添加环境变量到process.env对象
那么如果想要添加一个自定义的变量到process.env
对象上如何来做?
不同的操作系统会使用不同的shell命令给Nodejs的process.env
添加自定义的环境变量。
- windows系统:
set BASE_API=http://xxxx.com
- macOS或Linux:
export BASE_API=http://xxxx.com
比如我们想要访问process.env
对象上的BASE_API
这个环境变量,因为默认是不在的,那么可以先运行set
设置在运行文件。
此时需要借助packages.json 中的脚本命令,这样就先设置在启动index.js
文件。
js
"scripts": {
"dev": "set BASE_API=http://development&&node index.js"
},
为了兼容性,可以安装cross-env
这个三方库帮助添加环境变量
安装
shell
npm install cross-env --save-dev
使用
js
"scripts": {
"dev": "cross-env BASE_API=http://development&&node index.js"
},
cross-env源码实现
cross-env
其实也就是对各个操作系统、终端的命令写法做了一个整合兼容。大致的源码实现如下:
js
const isWindows = process.platform === 'win32';
function setEnvironmentVariable(key, value) {
if (isWindows) {
return `set ${key}=${value}`;
} else {
return `export ${key}="${value}"`;
}
}
function runCommand(command) {
let cmd = command;
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith('CROSS_')) {
cmd = `${setEnvironmentVariable(key.slice(5), value)} && ${cmd}`;
}
}
return cmd;
}
module.exports = runCommand;
读取.env
文件的内容
在vue-cli项目、vite项目并不是直接以命令参数的方式设置添加环境变量,而是创建.env
文件来添加环境变量,那么这种是如何实现的呢?
关于这部分的源码原理其实就是用NodeJs读取本地文件,这里它是指定读取.env
的文件,再将内部自定义的环境变量添加到process.env
对象中。
这里我们可以来简单实现一下:需要用到的就是一个fs
文件系统模块,用fs模块读取指定的文件内容,然后再做一系列的处理。
比如.env
的内容如上图,解析大致步骤如下:
- 声明一个
dotenv()
方法,这个方法接收文件参数,默认是字符串".env" - 尝试用fs模块读取这个文件内容,将内容以换行符分割成数组
- 遍历这个数组,也就是遍历每一行,筛选过滤掉不符合的行
- 将符合的环境变量添加到
process.env
上
nodejs实现读取:
js
const fs = require('fs');
const path = require('path');
function dotenv(filePath = '.env') {
const envFile = path.resolve(filePath);
if (!fs.existsSync(envFile)) {
console.warn(`找不到 ${filePath} 文件`);
return;
}
const lines = fs.readFileSync(envFile, 'utf8').split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (!line || line.startsWith('#')) {
continue;
}
const parts = line.split('=');
if (parts.length !== 2) {
console.error(`错误的环境变量格式: ${line}`);
continue;
}
process.env[parts[0]] = parts[1];
}
}
module.exports = dotenv;
其实不管是Vue-cli还是Vite,在其内部都使用了一个三方库 dotenv
来处理环境变量
如何将环境变量的值在业务代码中使用
在Vue-cli或vite项目中,你可以把环境变量在业务代码中使用。如下,二次封装axios时通常会把baseURL
的值设置成process.env
对象下的环境变量。
js
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 10000 // request timeout
})
这里以process.env为例
但是这里你要思考一个问题,process.env
是NodeJ环境下的,在浏览器环境下能取到这个全局对象吗?答案是否定的,那么为什么能够在业务代码中访问到process.env
呢?
实际上在业务代码中访问的process.env
对象并不是真正的NodeJs环境下的process.env
。
业务代码中访问的process.env
是在打包构建时注入进去的一个同名的对象,这个对象内的属性是从NodeJs的全局process.env
对象中筛选过滤取到的值。
这也是为什么Vue-cli项目添加的环境变量为什么必须是以VUE_APP
开头。
那么它是如何实现的呢?
Webpack中的DefinePlugin配置
DefinePlugin
允许在编译时将代码中的变量替换为其它值或表达式,我们可以通过它注入process.env
。
DefinePlugin
是webpack内部提供的一个Plugins插件,我们可以直接使用它。比如在业务代码中访问一个变量BASE_API ,那可以使用这个DefinePlugin
注入。
js
const path = require('path')
const { DefinePlugin } = require('webpack')
module.exports = {
...,
plugins:[
new DefinePlugin({
'BASE_API':'"http://localhost:3000"'
})
]
}
如上使用方式,那么现在在业务代码中就可以直接访问输出这个变量BASE_API了
js
console.log(BASE_API) // http://localhost:3000
打包构建时会将变量名替换成真正的值,最终得到的打包文件就是输出一个值,如下:
那么基于此,我们同样可以注入一个process.env
对象,步骤分成三步:
- 首先在
webpack.config.js
中是能访问到NodeJs环境的process.env
对象的,那就能获取到这个对象下的所有属性及其属性值。 - 然后需要做的就是筛选过滤出以
VUE_APP
开头的属性名及其值,并且作为一个新对象的属性 - 最后把这个对象注入进去即可
代码实现
js
require('dotenv').config()
const path = require('path')
const { DefinePlugin } = require('webpack')
const processObj = {}
const reg = /^VUE_APP_/
Object.keys(process.env).forEach(key => {
if(reg.test(key)){
processObj[key]=process.env[key]
}
})
console.log(processObj)
module.exports = {
...,
plugins:[
new DefinePlugin({
'process.env':JSON.stringify(processObj)
})
]
}