【vite 插件】env 解析

210 阅读1分钟

简介

要给大家介绍的是这款 vite-plugin-env-parse

该插件核心功能

  • 环境变量值具有确切类型,非 DotEnv 解析出的字符串类型
  • 自动生成声明文件,开发中有代码提示

虽然功能不多,但确实解决了开发中环境变量使用的痛点

前置知识

【vite 原理】环境变量

核心逻辑

以下代码为简化后的源码

开发阶段

configResolved 钩子中可以通过 config.env 获取到解析后的环境变量,对变量值进行转换

export default function ViteEnvParse(): PluginOption {
  return {
    name: 'vite-env-parse',
    enforce: 'pre',
    configResolved(config) {
      const prefixes = arraify(config.envPrefix || ['VITE_']);
      // 环境变量类型转换
      typeConversion(config.env, prefixes)

      // 生成声明文件
      if(config.command !== 'build') {
        const declareStr = generateDeclarationStr(config.env)
        if(declareStr) {
          writeDeclarationStr(path.resolve(config.root, 'src/vite-env.d.ts'), declareStr)
        }
      }
    }
  }
}

将解析后的环境变量进行类型转换,开发中就可以获得具有类型的环境变量值

function typeConversion(obj, prefixes) {
  for (const key in obj) {
    if (!obj.hasOwnProperty(key)) continue
    if (!prefixes.some((prefix) => key.startsWith(prefix))) continue

    const value = obj[key]

    // 解析数字和布尔值
    try {
      obj[key] = JSON.parse(value);
      continue
    } catch (e) {
      obj[key] = value;
    }

    // 解析数组和对象
    try {
      obj[key] = eval(`(${value})`)
      continue
    } catch (e) {
      obj[key] = value;
    }
  }

  return obj;
}

根据类型转换后的结果生成或修改声明文件

function writeDeclarationStr(path: string, str: string) {
  const importMetaEnvRegexp = /interface ImportMetaEnv\s*\{[\s\S]*?\}/g
  
  if (fs.existsSync(path)) {
    // 声明文件存在
    const fileContent = fs.readFileSync(path, { encoding: 'utf-8' })
    if (importMetaEnvRegexp.test(fileContent)) {
      // 替换
      str = fileContent.replace(importMetaEnvRegexp, str)
    } else {
      // 追加
      str = `${fileContent}
${str}`
    }
  } else {
    // 声明文件不存在
    str = `/// <reference types="vite/client" />
${str}`
  }
  fs.writeFileSync(path, str)
}
生产阶段

该阶段原本是通过 vite 内置的 vite:define 插件对代码中的环境变量进行替换,但该插件会对环境变量值进行 JSON.stringify,无奈只能自己实现该插件的逻辑来实现环境变量具有类型的效果

const importMetaEnvReg = /(?<![\'\"])import\.meta\.env\.([\w-]+)/gi
const importObjReg = /(import\.meta\.env)(?:[^.])/gi

{
  name: 'vite-env-parse',
  enforce: 'pre',
  transform(code, id) {
    if (
      !isBuild ||
      isNonJsRequest(id) ||
    ) {
      return
    }
    
    if (code.includes('import.meta.env')) {
      code = code
        .replace(importMetaEnvReg, (matched, envKey) => {
          let val = parsedEnv[envKey]
          return JSON.stringify(val)
        })        

      return {
        code,
        map: null
      }
    }
  }
}