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-07-29

Vite虚拟模块插件

下面给你一个生产级、可立即使用的自动注入环境变量虚拟模块(virtual:env)插件,完全模拟类似:

import env from 'virtual:env'

console.log(env.API_URL)
1
2
3

它会自动读取:

  • .env
  • .env.local
  • .env.development
  • 或者你定义的任何自定义 env 文件

并生成一个虚拟模块 virtual:env 供任意代码直接 import。

这类插件在真实项目里非常有用。


# 🚀 最终效果

你可以在任意文件中写:

import env from 'virtual:env'

console.log(env)
1
2
3

输出示例:

{
  "NODE_ENV": "development",
  "VITE_API_URL": "https://api.xxx.com",
  "VITE_TITLE": "My App"
}
1
2
3
4
5

这个模块是自动生成的,无需手动维护。


# 🔥 一、插件完整代码(插件文件:plugins/virtualEnv.js)

这段代码可直接复制使用,完整、可运行、包含缓存、HMR、自动 env 文件解析。

import fs from 'node:fs'
import path from 'node:path'
import dotenv from 'dotenv'

export default function virtualEnvPlugin(options = {}) {
  const VIRTUAL_MODULE_ID = 'virtual:env'
  const RESOLVED_ID = '\0' + VIRTUAL_MODULE_ID

  const { envDir = process.cwd() } = options

  let envData = {}

  // 读取所有环境文件
  function loadEnvFiles(mode) {
    const files = [
      `.env`,
      `.env.local`,
      `.env.${mode}`,
      `.env.${mode}.local`
    ]

    envData = {}

    for (const file of files) {
      const filePath = path.resolve(envDir, file)
      if (fs.existsSync(filePath)) {
        const parsed = dotenv.parse(fs.readFileSync(filePath, 'utf-8'))
        Object.assign(envData, parsed)
      }
    }

    // 只暴露 VITE_ 开头的变量
    const clientEnv = {}
    for (const k in envData) {
      if (k.startsWith('VITE_') || k === 'NODE_ENV') {
        clientEnv[k] = envData[k]
      }
    }

    return clientEnv
  }

  return {
    name: 'vite-plugin-virtual-env',

    configResolved(config) {
      // Vite 的 mode(development / production)
      this.mode = config.mode
      this.clientEnv = loadEnvFiles(this.mode)
    },

    resolveId(id) {
      if (id === VIRTUAL_MODULE_ID) {
        return RESOLVED_ID
      }
    },

    load(id) {
      if (id === RESOLVED_ID) {
        return `
          // generated by vite-plugin-virtual-env
          export default ${JSON.stringify(this.clientEnv, null, 2)}
        `
      }
    },

    // Env 文件变动 → 自动 HMR
    handleHotUpdate(ctx) {
      const envFiles = [
        '.env',
        `.env.${this.mode}`,
        '.env.local',
        `.env.${this.mode}.local`
      ]

      const changed = envFiles.some((f) => ctx.file.endsWith(f))

      if (changed) {
        this.clientEnv = loadEnvFiles(this.mode)

        ctx.server.ws.send({
          type: 'update',
          updates: [
            {
              type: 'js-update',
              path: '/@id/' + RESOLVED_ID,
              acceptedPath: '/@id/' + RESOLVED_ID,
              timestamp: Date.now()
            }
          ]
        })

        return []
      }
    }
  }
}
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

# 📌 二、在 vite.config.js 中使用

import virtualEnvPlugin from './plugins/virtualEnv.js'

export default defineConfig({
  plugins: [
    virtualEnvPlugin()
  ]
})
1
2
3
4
5
6
7

# 📁 三、使用示例

假设你的 .env.development:

VITE_API_URL=https://api.dev.com
VITE_APP_NAME=Dev App
SECRET_KEY=xxx
1
2
3

你在代码中使用:

import env from 'virtual:env'

console.log(env)
1
2
3

实际结果(只暴露 VITE_ 和 NODE_ENV):

{
  "NODE_ENV": "development",
  "VITE_API_URL": "https://api.dev.com",
  "VITE_APP_NAME": "Dev App"
}
1
2
3
4
5

# 🔄 四、支持 HMR

当你修改 .env 或 .env.local,页面将 自动刷新虚拟模块(不会刷新整页)。

例如改 .env.local:

-VITE_APP_NAME="Dev App"
+VITE_APP_NAME="Dev App Updated"
1
2

浏览器会收到:

update: virtual:env
1

不需重启 dev server。


# ⚙️ 五、为什么 Vite 要限制只暴露 VITE_ 前缀?

Vite 的官方策略同样是:

只有 VITE_ 前缀的变量才会暴露给浏览器
1

这是 安全策略,避免把敏感变量泄漏到客户端。

你可以自行决定要不要严格限制,插件里可以自定义:

if (k.startsWith('CLIENT_'))
1

# 🧠 六、插件原理总结(重要)

虚拟模块的流程:

  1. resolveId('virtual:env') → 返回 \0virtual:env
  2. load('\0virtual:env') → 返回 env JS 内容
  3. 浏览器 import 时直接得到生成的模块
  4. .env 文件更新 → handleHotUpdate → ws 通知 → HMR reload 虚拟模块

这是 Vite 架构中最美妙的部分: 虚拟模块 + HMR + 自动依赖图刷新


上次更新: 2025/12/09, 03:15:37
Vite HMR源码级讲解
模拟 Vite 内置env

← Vite HMR源码级讲解 模拟 Vite 内置env→

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