1. 前言
-
本文参加了由 公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
-
这是源码共读的第40期,链接:vite 是如何解析用户配置的 .env
2. 前置知识
3. 源码解析
3.1 使用场景
参考打包环境区分,在使用vite区分环境是,我们通常定义多个env文件,如:
打包使用env环境变量则使用loadEnv进行引入
本文就vite环境变量的解析引入作分析。
3.2 解析流程
解析流程如下:
3.2 主要函数说明
对于使用的函数作简单说明,具体在调试步骤详细讲解:
-
bin/vite.js:start函数
:这个函数是用来启动 Vite 的服务器的。其中包括一些初始化的设置,使用cac
例如读取用户设置和默认配置,创建服务器等。 -
src/node/cli.ts:createServer函数
:创建一个新的 Vite 服务器的。 -
src/node/server/index.ts:_createServer函数
:这个函数是在内部实际创建 Vite 服务器的函数。它调用http.createServer()
来创建一个新的 HTTP 服务器,并设置一些基础的请求处理逻辑。 -
src/node/config.ts:resolveConfig函数
:此函数用于解析用户提供的配置,将其合并到默认配置中。如果用户没有提供配置,那么将使用默认配置。 -
src/node/env.ts:resolveEnvPrefix函数
:此函数用于从环境变量中解析出带有特定前缀的键。 -
src/node/env.ts:loadEnv函数
:这个函数用于加载.env
文件到process.env
。` -
dotenv包的parse方法
:此方法用于将字符串或缓冲区转换为一个 JavaScript 对象,对象的键值对表示环境变量的键值对。 -
dotenv-expand包的expand函数
:此函数用于处理环境变量值中嵌入的其他环境变量。比如DB_HOST=localhost
和DB_URL=$DB_HOST:5432
,它会把DB_URL
里的$DB_HOST
替换成真正的值localhost
。 -
dotenv-expand包的_interpolate函数
:此函数是expand
函数的辅助函数,用于实际执行环境变量值中的插值替换操作。 -
resolvePlugins
:此函数的主要任务是生成一个包含所有插件的列表。它接收用户配置和默认配置,然后合并这两者。如果同一个插件在用户配置和默认配置中都出现,则用户配置将覆盖默认配置。返回的是一个插件数组,这些插件按照预定的顺序排列。 -
definePlugin
:此函数用于创建新的 Vite 插件。插件是一个对象,该对象可以包含多种处理程序,用于对特定类型的文件进行操作。此处进行import.meta.env对象挂载,其他不做深究。
4. 源码调试
- 在vite项目中,我们定义两个env文件:
js
// .env
VITE_BASE = vite
VITE_BASE_EXPAND = ${VITE_BASE}_expand
// .env.dev
NODE_ENV = dev
BASE = /
VITE_BASE_URL = http://localhost:8080
-
使用node bin/vite.js --mode dev,在bin/vite.js中打断点进入
-
进入src/node/cli.ts,此处使用
cac
进行命令参数收集,调用后进入action函数 -
进入src/node/server/index.ts, 调用_createServer 传入{ws:true} 调用resolveConfig
js
export async function resolveConfig(
inlineConfig: InlineConfig,
command: 'build' | 'serve',
defaultMode = 'development',
defaultNodeEnv = 'development',
): Promise<ResolvedConfig> {
let config = inlineConfig // 传入的配置项
let configFileDependencies: string[] = []
let mode = inlineConfig.mode || defaultMode // 方式开发环境
const isNodeEnvSet = !!process.env.NODE_ENV // false node什么环境
// user config may provide an alternative mode. But --mode has a higher priority
mode = inlineConfig.mode || config.mode || mode
configEnv.mode = mode
// load .env files
const envDir = config.envDir
? normalizePath(path.resolve(resolvedRoot, config.envDir))
: resolvedRoot
const userEnv =
inlineConfig.envFile !== false &&
loadEnv(mode, envDir, resolveEnvPrefix(config)) // 前缀标识 resolveEnvPrefix
return resolved
}
- 使用loadEnv进行加载解析,直接进入loadEnv函数
js
export function loadEnv(
mode: string,
envDir: string,
prefixes: string | string[] = 'VITE_',
): Record<string, string> {
if (mode === 'local') {
throw new Error(
`"local" cannot be used as a mode name because it conflicts with ` +
`the .local postfix for .env files.`,
)
}
prefixes = arraify(prefixes) // 转换成数组
const env: Record<string, string> = {}
const envFiles = [
/** default file */ `.env`,
/** local file */ `.env.local`,
/** mode file */ `.env.${mode}`,
/** mode local file */ `.env.${mode}.local`,
]
// [['name','jack'],['age',20]] => {name:'jack',age:20}
const parsed = Object.fromEntries(
envFiles.flatMap((file) => {// [1,2,3] => [2,4,6]
const filePath = path.join(envDir, file) // "C:\\Users\\admin\\Desktop\\ruochuan\\vite\\packages\\vite\\.env"
if (!tryStatSync(filePath)?.isFile()) return [] // 获取文件信息 是否有文件
return Object.entries(parse(fs.readFileSync(filePath)))// {name:'jack'}=>['name','jack']
}),
)
// let environment variables use each other
// 将环境变量中的${xxx} $xxx 替换
expand({ parsed })
// 读取解析文件后的对象
for (const [key, value] of Object.entries(parsed)) {
if (prefixes.some((prefix) => key.startsWith(prefix))) {
env[key] = value
}
}
// 读取process.env中是否有prefix前缀的变量
for (const key in process.env) {
if (prefixes.some((prefix) => key.startsWith(prefix))) {
env[key] = process.env[key] as string
}
}
return env
}
前缀转换,解析对应文件转换成对象,判断环境
expand({parsed}) 主要进行变量替换
resolveConfig调用返回结果
- src/node/plugins/index.ts的resolvePlugins函数解析插件
- 调用了src/node/plugins/define.ts的definePlugin,作用将.env文件中定义的环境变量挂在到了import.meta.env上
5. 总结
总结下Vite中进行环境变量加载过程:
- 使用
cac
读取环境配置,使用resolveConfig读取配置; - 核心是loadEnv加载解析过程,vite中默认定义了加载的环境文件格式
.env.${mode}
,loadEnv使用dotenv
的parse
方法解析文件成对象,使用dotenv-expand
的expand
方法进行变量替换,最后将prefix前缀的变量进行返回。 - resolvePlugins解析插件,definePlugin将变量挂载到import.meta对象上
通过本次的学习了:
- Object中的相关方法如fromEntries、entries等
- 学习了dotenv-expand的解析
- 了解vite中环境变量的配置方式
- 了解了
cac
包的使用
每天进步一小点O^O.