手写一个用于Vite打包动态生成运行时可以修改的配置文件的插件

65 阅读1分钟

vite-plugin-env-generate-config

简介

该插件用于在vite构建时将.env,.env.[mode]中(仅VITE_开头的)环境变量提取到打包后的config.js中, 以支持在运行时修改配置变量。

安装

npm install vite-plugin-env-generate-config --save-dev

使用

在vite.config.ts中引入插件并配置:

import { defineConfig } from 'vite'
import envGenerateConfig from 'vite-plugin-env-generate-config'
export default defineConfig({
  plugins: [
    envGenerateConfig()
  ]
})

示例效果

  • 源文件.env
VITE_SYS_TITLE="XXX系统"
VITE_SERVER_BASEURL="http://192.168.1.1:8200/"
  • 源文件.env.production
VITE_SERVER_BASEURL="http://192.0.0.0:8200/"
  • 打包文件中config.js
VITE_SYS_TITLE="XXX系统"
VITE_SERVER_BASEURL="http://192.0.0.0:8200/"

这样在打包后也可以修改服务器地址等信息,方便在不同服务器部署的需求。也支持在不同打包环境下合并相应环境的配置,如build --mode test时则会合并.env和.env.test的配置。

源码思路

  • 根据build环境读取配置文件
export function loadViteConfig(mode: string): Record<string, string> {
    const baseEnvPath = path.resolve(process.cwd(), '.env')
    const modeEnvPath = path.resolve(process.cwd(), `.env.${mode}`)

    const baseEnvConfig = dotenv.parse(fs.readFileSync(baseEnvPath))
    const modeEnvConfig = dotenv.parse(fs.readFileSync(modeEnvPath))
    return Object.fromEntries(
       Object.entries({ ...baseEnvConfig, ...modeEnvConfig }).filter(([key]) => key.startsWith('VITE_')),
    )
}
  • 插件主函数
export default function GenerateConfigPlugin(options?: VitePluginEnvGenerateConfigOptions): Plugin {
    let mode: string
    const { outputName } = assignOptions(options)
    return {
       name,
       apply: 'build',
       
       configResolved(config) {
       
       // 记录本次构建的mode
          mode = config.mode
       },
       
       // 将配置合并写入config.js
       writeBundle(options) {
          const viteEnvConfig = loadViteConfig(mode)

          const configJsContent = `window.vite_env_config = ${JSON.stringify(viteEnvConfig, null, 2)};`
          const outputPath = path.resolve(options.dir as string, `${outputName}.js`)

          fs.mkdirSync(path.dirname(outputPath), { recursive: true })
          fs.writeFileSync(outputPath, configJsContent)
       },
       
       // 在最终的index.html中写入加载config.js语句
       transformIndexHtml(html) {
          if (!html.includes(`<script src="/${outputName}.js"></script>`)) {
             return html.replace('</head>', `  <script src="/${outputName}.js"></script>\n</head>`)
          }
          return html
       },
       
       // 将代码中的import.meta.env语句替换为config.js中的变量
       transform(code, id) {
          if (/.(js|ts|jsx|tsx|vue|svelte)$/.test(id)) {
             const viteEnvConfig = loadViteConfig(mode)

             for (const [key, value] of Object.entries(viteEnvConfig)) {
                code = code.replace(new RegExp(`import\.meta\.env\.${key}`, 'g'), `window.vite_env_config.${key}`)
             }
          }
          return code
       },
    }
}