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

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

Glitz Ma

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

    • 《TypeScript教程》
    • 《Git》
    • 《Vite》
    • 《Vue3》
    • 《React18》
    • 《CSS》
    • 《Tailwind CSS》
    • 《JavaScript教程》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)
  • vite从入门到精通
  • vite插件
  • vite自动导入api插件
  • Vite 开发服务器核心原理讲解
  • Vite HMR源码级讲解
  • Vite虚拟模块插件
  • 模拟 Vite 内置env
  • vite一体化虚拟模块插件
    • 🌟 整体效果(最终可实现)
    • 📦 1. 插件整体结构
    • 🧩 2. 自动 API 导入模块(virtual:auto-api)
    • 🧩 3. 生成虚拟环境变量模块(virtual:env)
    • 🧩 4. Vite 内置 import.meta.env 映射(可选)
    • 🔥 5. HMR 热更新逻辑
      • ① API 文件变化 → 更新 virtual:auto-api
      • ② .env 或 .env.* 变化 → 更新 virtual:env
      • ③ moduleGraph.invalidateModule
      • ④ 返回要触发热更新的模块
    • 🧪 6. 最终完整效果
  • vite可发布npm的虚拟插件
  • 《Vite》学习笔记
mamingjuan
2024-08-15
目录

vite一体化虚拟模块插件

这是一个可直接用于 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

上次更新: 2026/01/21, 04:36:31
模拟 Vite 内置env
vite可发布npm的虚拟插件

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

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