前端项目中.env文件,很常见,.env配置项目的环境变量,一般被放在项目的根目录里;在服务配置文件里的process.env
对象中就可以获取配置的变量
- .env配置变量
//.env
VUE_APP_DEV_VAR = 'development'
- 此处vue项目为例
// vue.config.js
const VAR = process.env.VUE_APP_VAR
- 一般我们在
vue
项目中,无法找到跟.env相关的包文件的,其实我们在vue
在项目中node_modules
能找到个名叫dotenv
这个包的,这个项目的包名被加载到@vue/cli-service
中,从加载的路径中,我们可以看到,这个跟node加载有关了;那我们就打开cli-service/lib
中找到Service
文件
...
const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')
...
class Service{
...
loadEnv (mode) {
const logger = debug('vue:env')
const basePath = path.resolve(this.context, `.env${mode ? `.${mode}` : ``}`)
const localPath = `${basePath}.local`
const load = envPath => {
try {
const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })
dotenvExpand(env)
logger(envPath, env)
} catch (err) {
// only ignore error if file is not found
if (err.toString().indexOf('ENOENT') < 0) {
error(err)
}
}
}
load(localPath)
load(basePath)
...
}
...
}
- 从上面代码
loadEnv
方法可以看出是需要加载环境解析环境目录路径,通过dotenv.config
解析出环境变量,拿它是如何解析.env文件和设置到process.env对象中,那我就把他们的源码代码给拉下来,进行进一步分析;
- dotenv源码分析
- 整体看一下项目目录:lib和tests目录,它的核心源码在lib文件中
- main.js
- main.d.ts
- cli-options.js
- env.options.js
- package.json
- 在package.json中main字段配置的文件是main.js,所以main.js是出口文件,其实在main.js中
config
和parse
两个方法 - 从上面以
@vue/cli-service
项目中,就是直接使用dotenv.config
方法,那我们就先来分析一config
方法
- 先加载解析
.env
的文件路径 - 再判断
option
/options.path
和options.encoding
是不是存在 - 不存在加载默认
.env
路径和utf-8
字符编码,存在,采用用户配置的.env.XXX文件路径和字符编码 - 再通过
fs.readFileSync
同步读取.env相关的文件内容 - 再通过
parse
方法解析文件内容 - 在根据回车换行进行切割文件内容形成一个数组
const NEWLINES_MATCH = /\r\n|\n|\r/
const lines = src.toString().split(NEWLINES_MATCH)
- 对切割好的内容数组,进行遍历循环,进行下一步操作,将
=
赋值的左右两边解析城key和value值,通过正则匹配赋值等式
const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*("[^"]*"|'[^']*'|[^#]*)?(\s*|\s*#.*)?$/
for (let idx = 0; idx < lines.length; idx++) {
let line = lines[idx]
// matching "KEY' and 'VAL' in 'KEY=VAL'
const keyValueArr = line.match(RE_INI_KEY_VAL)
}
- 通过判断正则match好的返回的值,存在,返回的数组的第二个值为key值,第三个为value值
- 判断是否多行,识别是单引号还是双引号还是没有引号
- 多行表示字符串如下事例,需要用户配置options字段参数multiline参数为true即可
MULTI_SINGLE_QUOTED='THIS
IS
A
MULTILINE
STRING'
- 在vue项目中源码可知,
vue-cli-server
,并没有进行多行配置 - 多行配置时,需要对字符串进行遍历,进行累加到一起,形成一个变量字符串
- 不是多行,只需要判断单双引号,进行相应的截取
- 最后将处理好的val值,存放到字典中,并且返回
if (keyValueArr != null) {
const key = keyValueArr[1]
// default undefined or missing values to empty string
let val = (keyValueArr[2] || '')
let end = val.length - 1
const isDoubleQuoted = val[0] === '"' && val[end] === '"'
const isSingleQuoted = val[0] === "'" && val[end] === "'"
const isMultilineDoubleQuoted = val[0] === '"' && val[end] !== '"'
const isMultilineSingleQuoted = val[0] === "'" && val[end] !== "'"
// if parsing line breaks and the value starts with a quote
if (multiline && (isMultilineDoubleQuoted || isMultilineSingleQuoted)) {
const quoteChar = isMultilineDoubleQuoted ? '"' : "'"
val = val.substring(1)
while (idx++ < lines.length - 1) {
line = lines[idx]
end = line.length - 1
if (line[end] === quoteChar) {
val += NEWLINE + line.substring(0, end)
break
}
val += NEWLINE + line
}
// if single or double quoted, remove quotes
} else if (isSingleQuoted || isDoubleQuoted) {
val = val.substring(1, end)
// if double quoted, expand newlines
if (isDoubleQuoted) {
val = val.replace(RE_NEWLINES, NEWLINE)
}
} else {
// remove surrounding whitespace
val = val.trim()
}
obj[key] = val
} else if (debug) {
const trimmedLine = line.trim()
// ignore empty and commented lines
if (trimmedLine.length && trimmedLine[0] !== '#') {
log(`Failed to match key and value when parsing line ${idx + 1}: ${line}`)
}
}
- parse解析好的字典值,进行遍历操作,存在到
process.env
对象 - 最后只要通过process.env获取对应的变量。
- 这就是.env文件解析的整个过程
总结:
- 加载解析.env文件路径,判断是用户是否配置的路径和字符编码,不然采用默认值;
- 再读取.env文件内容,先通过正则匹配回车换行切割文件内容,后遍历切割好的数组,对切割好的数组进行正则匹配;
- 判断用户是否进行多行设置,进行多行设置的内容进行进行循环处理累加成单行字符串,非多行设置,识别一下引号进行截取,替换等操作,最后存放到字典返回;
- 处理好的返回值进行遍历,存放到
process.env
中,最后用户只需要通过process.env
获取相应的变量值即可。