Vite虚拟模块插件
下面给你一个生产级、可立即使用的自动注入环境变量虚拟模块(virtual:env)插件,完全模拟类似:
import env from 'virtual:env'
console.log(env.API_URL)
1
2
3
2
3
它会自动读取:
.env.env.local.env.development- 或者你定义的任何自定义 env 文件
并生成一个虚拟模块 virtual:env 供任意代码直接 import。
这类插件在真实项目里非常有用。
# 🚀 最终效果
你可以在任意文件中写:
import env from 'virtual:env'
console.log(env)
1
2
3
2
3
输出示例:
{
"NODE_ENV": "development",
"VITE_API_URL": "https://api.xxx.com",
"VITE_TITLE": "My App"
}
1
2
3
4
5
2
3
4
5
这个模块是自动生成的,无需手动维护。
# 🔥 一、插件完整代码(插件文件:plugins/virtualEnv.js)
这段代码可直接复制使用,完整、可运行、包含缓存、HMR、自动 env 文件解析。
import fs from 'node:fs'
import path from 'node:path'
import dotenv from 'dotenv'
export default function virtualEnvPlugin(options = {}) {
const VIRTUAL_MODULE_ID = 'virtual:env'
const RESOLVED_ID = '\0' + VIRTUAL_MODULE_ID
const { envDir = process.cwd() } = options
let envData = {}
// 读取所有环境文件
function loadEnvFiles(mode) {
const files = [
`.env`,
`.env.local`,
`.env.${mode}`,
`.env.${mode}.local`
]
envData = {}
for (const file of files) {
const filePath = path.resolve(envDir, file)
if (fs.existsSync(filePath)) {
const parsed = dotenv.parse(fs.readFileSync(filePath, 'utf-8'))
Object.assign(envData, parsed)
}
}
// 只暴露 VITE_ 开头的变量
const clientEnv = {}
for (const k in envData) {
if (k.startsWith('VITE_') || k === 'NODE_ENV') {
clientEnv[k] = envData[k]
}
}
return clientEnv
}
return {
name: 'vite-plugin-virtual-env',
configResolved(config) {
// Vite 的 mode(development / production)
this.mode = config.mode
this.clientEnv = loadEnvFiles(this.mode)
},
resolveId(id) {
if (id === VIRTUAL_MODULE_ID) {
return RESOLVED_ID
}
},
load(id) {
if (id === RESOLVED_ID) {
return `
// generated by vite-plugin-virtual-env
export default ${JSON.stringify(this.clientEnv, null, 2)}
`
}
},
// Env 文件变动 → 自动 HMR
handleHotUpdate(ctx) {
const envFiles = [
'.env',
`.env.${this.mode}`,
'.env.local',
`.env.${this.mode}.local`
]
const changed = envFiles.some((f) => ctx.file.endsWith(f))
if (changed) {
this.clientEnv = loadEnvFiles(this.mode)
ctx.server.ws.send({
type: 'update',
updates: [
{
type: 'js-update',
path: '/@id/' + RESOLVED_ID,
acceptedPath: '/@id/' + RESOLVED_ID,
timestamp: Date.now()
}
]
})
return []
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# 📌 二、在 vite.config.js 中使用
import virtualEnvPlugin from './plugins/virtualEnv.js'
export default defineConfig({
plugins: [
virtualEnvPlugin()
]
})
1
2
3
4
5
6
7
2
3
4
5
6
7
# 📁 三、使用示例
假设你的 .env.development:
VITE_API_URL=https://api.dev.com
VITE_APP_NAME=Dev App
SECRET_KEY=xxx
1
2
3
2
3
你在代码中使用:
import env from 'virtual:env'
console.log(env)
1
2
3
2
3
实际结果(只暴露 VITE_ 和 NODE_ENV):
{
"NODE_ENV": "development",
"VITE_API_URL": "https://api.dev.com",
"VITE_APP_NAME": "Dev App"
}
1
2
3
4
5
2
3
4
5
# 🔄 四、支持 HMR
当你修改 .env 或 .env.local,页面将 自动刷新虚拟模块(不会刷新整页)。
例如改 .env.local:
-VITE_APP_NAME="Dev App"
+VITE_APP_NAME="Dev App Updated"
1
2
2
浏览器会收到:
update: virtual:env
1
不需重启 dev server。
# ⚙️ 五、为什么 Vite 要限制只暴露 VITE_ 前缀?
Vite 的官方策略同样是:
只有 VITE_ 前缀的变量才会暴露给浏览器
1
这是 安全策略,避免把敏感变量泄漏到客户端。
你可以自行决定要不要严格限制,插件里可以自定义:
if (k.startsWith('CLIENT_'))
1
# 🧠 六、插件原理总结(重要)
虚拟模块的流程:
resolveId('virtual:env')→ 返回\0virtual:envload('\0virtual:env')→ 返回 env JS 内容- 浏览器 import 时直接得到生成的模块
.env文件更新 → handleHotUpdate → ws 通知 → HMR reload 虚拟模块
这是 Vite 架构中最美妙的部分: 虚拟模块 + HMR + 自动依赖图刷新
上次更新: 2025/12/09, 03:15:37