Skip to content

这是一个可直接用于 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 → 导出自动 API
    • virtual: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)
  })
}