【vite 原理】环境变量vite 中使用环境变量的核心原理,分别介绍了 vite 环境变量的基础 import.met
基础
Vite 使用 dotenv 从环境目录中的下列文件加载额外的环境变量
.env # 所有情况下都会加载
.env.local # 所有情况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略
加载的环境变量会通过 import.meta.env
以字符串形式暴露给客户端源码
import.meta 定义
- 是 ECMAScript 模块规范的一部分,在 ES6 模块系统中使用
- 是 JavaScript 模块中提供的元数据对象,包含有关当前模块的信息
- 是由宿主环境创建的可扩展的 null 原型对象,其所有属性均可写、可配置、可枚举
- ECMAScript 模块规范没有在 import.meta 对象上明确定义任何属性,可由宿主环境进行定义
- import.meta 提供的是当前模块的信息,因此不能跨模块使用
- 每个模块都有自己的 import.meta 对象
开发阶段
后续所有代码皆为简化后的核心逻辑
在 vite 读取配置阶段,通过 loadEnv 函数将环境变量写入 vite 配置
// packages\vite\src\node\env.ts
import { parse } from 'dotenv'
import { expand } from 'dotenv-expand'
function loadEnv(prefixes: string | string[] = 'VITE_') {
const env: Record<string, string> = {}
const filePath = '.env'
const parsed = parse(fs.readFileSync(filePath))
expand({ parsed })
for (const [key, value] of Object.entries(parsed)) {
if (prefixes.some((prefix) => key.startsWith(prefix))) {
env[key] = value
}
}
return env
}
浏览器请求模块时,在 importAnalyzsis 插件的 transform 钩子中通过 es-module-lexer 对模块代码进行解析
- 分析代码中的模块导入,判断模块代码中是否注入 import.meta.env 语句
- 若注入了,则在模块代码头部插入 import.meta.env 的定义
// packages\vite\src\node\plugins\importAnalysis.ts
import { init, parse as parseImports } from 'es-module-lexer'
async transform(source, importer, options) {
await init
;[imports, exports] = parseImports(source)
const str = () => s || (s = new MagicString(source))
await Promise.all(
imports.map(async (importSpecifier, index) => {
const {
s: start,
e: end,
...
} = importSpecifier
const rawUrl = source.slice(start, end)
// check import.meta usage
if (rawUrl === 'import.meta') {
const prop = source.slice(end, end + 4)
if (prop === '.env') {
hasEnv = true
}
return
}
})
)
if (hasEnv) {
// inject import.meta.env
str().prepend(getEnv(ssr))
}
}
// 从 vite 配置中获取环境变量并生成 import.meta.env 对象定义的字符串
let _env: string | undefined
function getEnv(ssr: boolean) {
if (!_env) {
_env = `import.meta.env = ${JSON.stringify({
...config.env,
SSR: '__vite__ssr__',
})};`
...
}
return _env.replace('"__vite__ssr__"', ssr + '')
}
生产阶段
通过 vite 内置的 vite:define 插件对代码中的环境变量进行替换
// packages\vite\src\node\plugins\define.ts
export function definePlugin(config: ResolvedConfig): Plugin {
const isBuild = config.command === 'build'
const importMetaKeys: Record<string, string> = {}
if (isBuild) {
// set here to allow override with config.define
importMetaKeys['import.meta.hot'] = `undefined`
for (const key in config.env) {
importMetaKeys[`import.meta.env.${key}`] = JSON.stringify(config.env[key])
}
}
function generatePattern() {
const replaceProcessEnv = !ssr || config.ssr?.target === 'webworker'
const replacements: Record<string, string> = {
...getImportMetaKeys(ssr),
}
const replacementsKeys = Object.keys(replacements)
const pattern = replacementsKeys.length
? new RegExp(
// Mustn't be preceded by a char that can be part of an identifier
// or a '.' that isn't part of a spread operator
'(?<![\\p{L}\\p{N}_$]|(?<!\\.\\.)\\.)(' +
replacementsKeys.map(escapeRegex).join('|') +
// Mustn't be followed by a char that can be part of an identifier
// or an assignment (but allow equality operators)
')(?:(?<=\\.)|(?![\\p{L}\\p{N}_$]|\\s*?=[^=]))',
'gu',
)
: null
return [replacements, pattern]
}
const defaultPattern = generatePattern()
return {
name: 'vite:define',
transform(code, id, options) {
const [replacements, pattern] = defaultPattern
const s = new MagicString(code)
let match: RegExpExecArray | null
while ((match = pattern.exec(code))) {
const start = match.index
const end = start + match[0].length
const replacement = '' + replacements[match[1]]
s.update(start, end, replacement)
}
...
}
}
}
转载自:https://juejin.cn/post/7396930610537267251