Glittering's blog Glittering's blog
Home
  • 学习手册

    • 《JavaScript教程》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • 《Vite》
    • 《Vue3》
    • TypeScript
    • CSS
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)

Glitz Ma

前端开发工程师
Home
  • 学习手册

    • 《JavaScript教程》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • 《Vite》
    • 《Vue3》
    • TypeScript
    • CSS
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)
  • vite从入门到精通
  • vite插件
  • vite自动导入api插件
  • Vite 开发服务器核心原理讲解
  • Vite HMR源码级讲解
  • Vite虚拟模块插件
  • 模拟 Vite 内置env
  • vite一体化虚拟模块插件
    • vite可发布npm的虚拟插件
    • 《Vite》学习笔记
    mamingjuan
    2024-08-15
    目录

    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

    插件能力:

    • 自动扫描 API 文件夹(如 src/composables)

    • 自动将函数名导出(无需手动 import)

    • 生成虚拟模块:

      • virtual:auto-api → 导出自动 API
      • virtual: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. 自动 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

    📌 特性:

    • 自动扫描 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

    效果:

    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

    但更推荐:

    • 让用户手动 import { MODE } from 'virtual:env'
    • 或自动注入(更复杂:AST 注入,不在本示例中)

    # 🔥 5. HMR 热更新逻辑

    三类文件触发 HMR:

    # ① API 文件变化 → 更新 virtual:auto-api

    if (ctx.file.startsWith(apiDir)) {
      invalidate(virtualApiId)
    }
    
    1
    2
    3

    # ② .env 或 .env.* 变化 → 更新 virtual:env

    if (/\.env($|\.)/.test(ctx.file)) {
      invalidate(virtualEnvId)
    }
    
    1
    2
    3

    # ③ moduleGraph.invalidateModule

    ctx.server.moduleGraph.invalidateModule(
      moduleNode
    )
    
    1
    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

    上次更新: 2025/12/09, 03:15:37
    模拟 Vite 内置env
    vite可发布npm的虚拟插件

    ← 模拟 Vite 内置env vite可发布npm的虚拟插件→

    Copyright © 2015-2025 Glitz Ma
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式