vite一体化虚拟模块插件
下面给你 「自动导入 API + env 注入 + HMR」一体化虚拟模块插件 的完整设计与实现步骤。 这是一个可直接用于 Vite 的插件原型,集成了:
- ✅ 自动 API 注入(类似 unplugin-auto-import)
- ✅ 注入虚拟模块
virtual:env(模拟 import.meta.env) - ✅ HMR 热更新(自动刷新虚拟模块)
- ✅ 虚拟模块体系(virtual:xxx)
# 🌟 整体效果(最终可实现)
import { useFoo, compute } from 'virtual:auto-api'
import { VITE_API_URL, MODE } from 'virtual:env'
// 支持 HMR 自动更新 env 变化
if (import.meta.hot) {
import.meta.hot.accept()
}
1
2
3
4
5
6
7
2
3
4
5
6
7
插件能力:
自动扫描 API 文件夹(如
src/composables)自动将函数名导出(无需手动 import)
生成虚拟模块:
virtual:auto-api→ 导出自动 APIvirtual:env→ 导出所有 env
import.meta.env自动映射到virtual:env环境变量变化时自动 HMR 推送
# 📦 1. 插件整体结构
一个完整插件大概如此:
import { promises as fs } from 'node:fs'
import path from 'node:path'
import { createFilter } from 'vite'
export default function AutoAllPlugin(options = {}) {
const apiDir = options.apiDir || 'src/composables'
const virtualApiId = 'virtual:auto-api'
const virtualEnvId = 'virtual:env'
let root = ''
let env = {}
return {
name: 'vite:auto-all-plugin',
enforce: 'pre',
configResolved(config) {
root = config.root
env = config.env // 直接读取 vite 内部的 env
},
// --- 虚拟模块解析
resolveId(id) {
if (id === virtualApiId) return '\0' + virtualApiId
if (id === virtualEnvId) return '\0' + virtualEnvId
},
// --- 虚拟模块加载
async load(id) {
if (id === '\0' + virtualApiId) {
return await generateAutoApiModule(apiDir)
}
if (id === '\0' + virtualEnvId) {
return generateEnvModule(env)
}
},
// --- 监听文件,实现 HMR
async handleHotUpdate(ctx) {
if (ctx.file.startsWith(path.resolve(root, apiDir))) {
ctx.server.moduleGraph.invalidateModule(
ctx.server.moduleGraph.getModuleById('\0' + virtualApiId)
)
return [
ctx.server.moduleGraph.getModuleById('\0' + virtualApiId),
]
}
if (/\.env($|\.)/.test(ctx.file)) {
const newEnv = loadEnvFile(ctx.server.config)
// 只有变化时才触发 HMR
if (JSON.stringify(newEnv) !== JSON.stringify(env)) {
env = newEnv
ctx.server.moduleGraph.invalidateModule(
ctx.server.moduleGraph.getModuleById('\0' + virtualEnvId)
)
return [
ctx.server.moduleGraph.getModuleById('\0' + virtualEnvId)
]
}
}
},
}
}
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
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
# 🧩 2. 自动 API 导入模块(virtual:auto-api)
类似 unplugin-auto-import,需要扫描文件夹:
async function generateAutoApiModule(apiDir) {
const files = await fs.readdir(apiDir)
const imports = []
const exportsList = []
for (const file of files) {
if (!file.endsWith('.ts') && !file.endsWith('.js')) continue
const filepath = path.join(apiDir, file)
const content = await fs.readFile(filepath, 'utf-8')
// 简单解析:找出 export function foo()
const fnNames = [...content.matchAll(/export function (\w+)/g)]
.map(m => m[1])
for (const name of fnNames) {
imports.push(`import { ${name} } from '${path.posix.join('/', apiDir, file)}'`)
exportsList.push(name)
}
}
return `
${imports.join('\n')}
export { ${exportsList.join(', ')} }
`
}
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
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
📌 特性:
- 自动扫描 API 文件夹
- 生成 import 表达式
- 导出所有函数
- 一旦 API 文件变化 → 自动触发 HMR
# 🧩 3. 生成虚拟环境变量模块(virtual:env)
Vite 的 import.meta.env 是一个对象,我们把它变成模块导出:
function generateEnvModule(env) {
return `
${Object.entries(env)
.map(([k, v]) => `export const ${k} = ${JSON.stringify(v)};`)
.join('\n')}
export default {
${Object.keys(env).join(', ')}
}
`
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
效果:
import { VITE_API_URL, MODE } from 'virtual:env'
1
# 🧩 4. Vite 内置 import.meta.env 映射(可选)
你甚至可以覆盖 transform,劫持代码:
transform(code, id) {
if (id.includes('node_modules')) return
// 将 import.meta.env 替换为虚拟模块
return code.replace(/import\.meta\.env/g, `__VIRTUAL_ENV__`)
}
1
2
3
4
5
6
2
3
4
5
6
但更推荐:
- 让用户手动
import { MODE } from 'virtual:env' - 或自动注入(更复杂:AST 注入,不在本示例中)
# 🔥 5. HMR 热更新逻辑
三类文件触发 HMR:
# ① API 文件变化 → 更新 virtual:auto-api
if (ctx.file.startsWith(apiDir)) {
invalidate(virtualApiId)
}
1
2
3
2
3
# ② .env 或 .env.* 变化 → 更新 virtual:env
if (/\.env($|\.)/.test(ctx.file)) {
invalidate(virtualEnvId)
}
1
2
3
2
3
# ③ moduleGraph.invalidateModule
ctx.server.moduleGraph.invalidateModule(
moduleNode
)
1
2
3
2
3
# ④ 返回要触发热更新的模块
return [ moduleNode ]
1
这样 HMR 客户端能更新。
# 🧪 6. 最终完整效果
📌 使用自动 API:
import { useUser, useFetch } from 'virtual:auto-api'
1
📌 使用虚拟 env:
import { VITE_APP_NAME, MODE } from 'virtual:env'
1
📌 HMR 自动更新:
if (import.meta.hot) {
import.meta.hot.accept((mod) => {
console.log('Updated env:', mod)
})
}
1
2
3
4
5
2
3
4
5
上次更新: 2025/12/09, 03:15:37