这是一个可直接用于 Vite 的插件原型,集成了:
- ✅ 自动 API 注入(类似 unplugin-auto-import)
- ✅ 注入虚拟模块
virtual:env(模拟 import.meta.env) - ✅ HMR 热更新(自动刷新虚拟模块)
- ✅ 虚拟模块体系(virtual:xxx)
🌟 整体效果(最终可实现)
ts
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()
}插件能力:
自动扫描 API 文件夹(如
src/composables)自动将函数名导出(无需手动 import)
生成虚拟模块:
virtual:auto-api→ 导出自动 APIvirtual:env→ 导出所有 env
import.meta.env自动映射到virtual:env环境变量变化时自动 HMR 推送
📦 1. 插件整体结构
一个完整插件大概如此:
ts
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)
]
}
}
},
}
}🧩 2. 自动 API 导入模块(virtual:auto-api)
类似 unplugin-auto-import,需要扫描文件夹:
ts
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(', ')} }
`
}📌 特性:
- 自动扫描 API 文件夹
- 生成 import 表达式
- 导出所有函数
- 一旦 API 文件变化 → 自动触发 HMR
🧩 3. 生成虚拟环境变量模块(virtual:env)
Vite 的 import.meta.env 是一个对象,我们把它变成模块导出:
ts
function generateEnvModule(env) {
return `
${Object.entries(env)
.map(([k, v]) => `export const ${k} = ${JSON.stringify(v)};`)
.join('\n')}
export default {
${Object.keys(env).join(', ')}
}
`
}效果:
ts
import { VITE_API_URL, MODE } from 'virtual:env'🧩 4. Vite 内置 import.meta.env 映射(可选)
你甚至可以覆盖 transform,劫持代码:
ts
transform(code, id) {
if (id.includes('node_modules')) return
// 将 import.meta.env 替换为虚拟模块
return code.replace(/import\.meta\.env/g, `__VIRTUAL_ENV__`)
}但更推荐:
- 让用户手动
import { MODE } from 'virtual:env' - 或自动注入(更复杂:AST 注入,不在本示例中)
🔥 5. HMR 热更新逻辑
三类文件触发 HMR:
① API 文件变化 → 更新 virtual:auto-api
ts
if (ctx.file.startsWith(apiDir)) {
invalidate(virtualApiId)
}② .env 或 .env.* 变化 → 更新 virtual:env
ts
if (/\.env($|\.)/.test(ctx.file)) {
invalidate(virtualEnvId)
}③ moduleGraph.invalidateModule
ts
ctx.server.moduleGraph.invalidateModule(
moduleNode
)④ 返回要触发热更新的模块
ts
return [ moduleNode ]这样 HMR 客户端能更新。
🧪 6. 最终完整效果
📌 使用自动 API:
ts
import { useUser, useFetch } from 'virtual:auto-api'📌 使用虚拟 env:
ts
import { VITE_APP_NAME, MODE } from 'virtual:env'📌 HMR 自动更新:
ts
if (import.meta.hot) {
import.meta.hot.accept((mod) => {
console.log('Updated env:', mod)
})
}