likes
comments
collection
share

【vite 原理】环境变量vite 中使用环境变量的核心原理,分别介绍了 vite 环境变量的基础 import.met

作者站长头像
站长
· 阅读数 24

基础

Vite 使用 dotenv 从环境目录中的下列文件加载额外的环境变量

.env                # 所有情况下都会加载
.env.local          # 所有情况下都会加载,但会被 git 忽略
.env.[mode]         # 只在指定模式下加载
.env.[mode].local   # 只在指定模式下加载,但会被 git 忽略

加载的环境变量会通过 import.meta.env 以字符串形式暴露给客户端源码

import.meta 定义

  • 是 ECMAScript 模块规范的一部分,在 ES6 模块系统中使用
  • 是 JavaScript 模块中提供的元数据对象,包含有关当前模块的信息
  • 是由宿主环境创建的可扩展的 null 原型对象,其所有属性均可写、可配置、可枚举
    • ECMAScript 模块规范没有在 import.meta 对象上明确定义任何属性,可由宿主环境进行定义
  • import.meta 提供的是当前模块的信息,因此不能跨模块使用
    • 每个模块都有自己的 import.meta 对象

开发阶段

后续所有代码皆为简化后的核心逻辑

在 vite 读取配置阶段,通过 loadEnv 函数将环境变量写入 vite 配置

// packages\vite\src\node\env.ts
import { parse } from 'dotenv'
import { expand } from 'dotenv-expand'

function loadEnv(prefixes: string | string[] = 'VITE_') {
  const env: Record<string, string> = {}
  const filePath = '.env'
  const parsed = parse(fs.readFileSync(filePath))
  expand({ parsed })

  for (const [key, value] of Object.entries(parsed)) {
    if (prefixes.some((prefix) => key.startsWith(prefix))) {
      env[key] = value
    }
  }

  return env
}

浏览器请求模块时,在 importAnalyzsis 插件的 transform 钩子中通过 es-module-lexer 对模块代码进行解析

  • 分析代码中的模块导入,判断模块代码中是否注入 import.meta.env 语句
  • 若注入了,则在模块代码头部插入 import.meta.env 的定义
// packages\vite\src\node\plugins\importAnalysis.ts
import { init, parse as parseImports } from 'es-module-lexer'

async transform(source, importer, options) {
  await init
  ;[imports, exports] = parseImports(source)
  const str = () => s || (s = new MagicString(source))

  await Promise.all(
  imports.map(async (importSpecifier, index) => {
    const {
      s: start,
      e: end,
      ...
    } = importSpecifier
    const rawUrl = source.slice(start, end)
    // check import.meta usage
    if (rawUrl === 'import.meta') {
      const prop = source.slice(end, end + 4)
      if (prop === '.env') {
        hasEnv = true
      }
      return
    }
  })
  )

  if (hasEnv) {
    // inject import.meta.env
    str().prepend(getEnv(ssr))
  }
}

// 从 vite 配置中获取环境变量并生成 import.meta.env 对象定义的字符串
let _env: string | undefined
function getEnv(ssr: boolean) {
  if (!_env) {
    _env = `import.meta.env = ${JSON.stringify({
      ...config.env,
      SSR: '__vite__ssr__',
    })};`
    ...
  }
  return _env.replace('"__vite__ssr__"', ssr + '')
}

生产阶段

通过 vite 内置的 vite:define 插件对代码中的环境变量进行替换

// packages\vite\src\node\plugins\define.ts
export function definePlugin(config: ResolvedConfig): Plugin {
  const isBuild = config.command === 'build'

  const importMetaKeys: Record<string, string> = {}
  if (isBuild) {
    // set here to allow override with config.define
    importMetaKeys['import.meta.hot'] = `undefined`
    for (const key in config.env) {
      importMetaKeys[`import.meta.env.${key}`] = JSON.stringify(config.env[key])
    }
  }

  function generatePattern() {
    const replaceProcessEnv = !ssr || config.ssr?.target === 'webworker'

    const replacements: Record<string, string> = {
      ...getImportMetaKeys(ssr),
    }

    const replacementsKeys = Object.keys(replacements)
    const pattern = replacementsKeys.length
      ? new RegExp(
          // Mustn't be preceded by a char that can be part of an identifier
          // or a '.' that isn't part of a spread operator
          '(?<![\\p{L}\\p{N}_$]|(?<!\\.\\.)\\.)(' +
            replacementsKeys.map(escapeRegex).join('|') +
            // Mustn't be followed by a char that can be part of an identifier
            // or an assignment (but allow equality operators)
            ')(?:(?<=\\.)|(?![\\p{L}\\p{N}_$]|\\s*?=[^=]))',
          'gu',
        )
      : null

    return [replacements, pattern]
  }

  const defaultPattern = generatePattern()
  return {
    name: 'vite:define',
    transform(code, id, options) {
      const [replacements, pattern] = defaultPattern
      const s = new MagicString(code)
      let match: RegExpExecArray | null

      while ((match = pattern.exec(code))) {
        const start = match.index
        const end = start + match[0].length
        const replacement = '' + replacements[match[1]]
        s.update(start, end, replacement)
      }

      ...
    }
  }
}
转载自:https://juejin.cn/post/7396930610537267251
评论
请登录