《超详细》前端环境变量原理解析

引言

为什么前端项目需要配置不同的环境?

在公司开发实际项目时,你的项目一定要区分开发环境测试环境生产环境 。这两个环境是最基本的,再规范一点,还会分出预演环境

最最基本的一个原因:在不同的环境下,需要使用不同的环境变量。举个最简单的例子,请求服务端提供的接口服务地址是不可能相同的,最佳的做法就是使用环境变量在不同的环境下自动帮我们使用对应的服务地址。

如上图:在封装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)
    })
  ]
}
相关推荐
顾平安1 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网1 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端
桃园码工1 小时前
4-Gin HTML 模板渲染 --[Gin 框架入门精讲与实战案例]
前端·html·gin·模板渲染
沈剑心1 小时前
如何在鸿蒙系统上实现「沉浸式」页面?
前端·harmonyos
一棵开花的树,枝芽无限靠近你1 小时前
【PPTist】组件结构设计、主题切换
前端·笔记·学习·编辑器
m0_748237051 小时前
Chrome 关闭自动添加https
前端·chrome
开心工作室_kaic1 小时前
springboot476基于vue篮球联盟管理系统(论文+源码)_kaic
前端·javascript·vue.js
川石教育1 小时前
Vue前端开发-缓存优化
前端·javascript·vue.js·缓存·前端框架·vue·数据缓存
搏博1 小时前
使用Vue创建前后端分离项目的过程(前端部分)
前端·javascript·vue.js
lv程序媛2 小时前
css让按钮放在最右侧
前端·css