溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

vite是如何解析.env文件的

發(fā)布時(shí)間:2023-01-31 10:14:50 來源:億速云 閱讀:339 作者:iii 欄目:編程語言

這篇文章主要介紹“vite是如何解析.env文件的”的相關(guān)知識(shí),小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“vite是如何解析.env文件的”文章能幫助大家解決問題。

使用vite構(gòu)建的vue3項(xiàng)目中,可以在根目錄下創(chuàng)建.env.[模式]文件定義一種或多種模式,并且在這個(gè)文件中定義的變量就是此模式的環(huán)境變量。定義了環(huán)境變量之后就可以通過import.meta.env.[變量名]的方式讀取環(huán)境變量了。

那么我們要來思考兩個(gè)問題:第一,vite如何讀取.env文件中定義的配置;第二,vite如何將.env文件中配置的環(huán)境變量掛載到import.meta.env環(huán)境變量上的。

1.如何讀取.env文件中定義的配置

1.1 問題的著眼點(diǎn)是什么?

首先我們看一下vite如何讀取.env文件中定義的配置。如下圖所示,在項(xiàng)目根目錄下有.env.development,.env.fat,.env.uat,.env.pro四個(gè)模式文件,其中development模式是對(duì)應(yīng)默認(rèn)的開發(fā)環(huán)境用于本地開發(fā),fat模式對(duì)應(yīng)的也是開發(fā)環(huán)境用于自測,uat模式對(duì)應(yīng)的是預(yù)發(fā)布環(huán)境用于測試團(tuán)隊(duì)測試,pro模式對(duì)應(yīng)的是生產(chǎn)環(huán)境也叫線上環(huán)境用于客戶使用。

vite是如何解析.env文件的

那么如何能夠讓vite知道我們要使用相關(guān)模式的文件呢?運(yùn)行vite 或者vite build命令時(shí)可以通過--mode或者-m設(shè)置環(huán)境模式(詳見文檔),如下圖所示:

vite是如何解析.env文件的

這就提示我們要了解vite如何讀取定義環(huán)境變量的模式文件則需要從vite命令或者vite build命令入手,接下來從vite命令入手研究一下。

1.2 vite的命令定義在何處?

看一下vite的package.json文件(路徑:vite/packages/vite/package.json):

vite是如何解析.env文件的

當(dāng)我們使用vite命令的時(shí)候,會(huì)執(zhí)行bin目錄下的vite.js文件,看一下這個(gè)文件(路徑:vite/packages/vite/bin/vite.js):

vite是如何解析.env文件的

可以看到這段代碼的關(guān)鍵是執(zhí)行start方法,而start方法是導(dǎo)入打包后的cli.js文件,那么這個(gè)打包后的文件對(duì)應(yīng)的原文件是哪個(gè)文件呢?vite打包的時(shí)候是使用rollup的,所以我們看一下rollup的配置文件(路徑:vite/packages/vite/rollup.config.ts):

vite是如何解析.env文件的

如上代碼可以看出vite相關(guān)命令的定義在/src/node/cli.ts 這個(gè)文件當(dāng)中。

1.3 vite的命令是如何定義的?

1.3.1 vite使用cac定義命令

我們來看一下vite的命令是如何定義的(路徑:vite/packages/vite/src/node/cli.ts):

import { cac } from 'cac'

const cli = cac('vite')
cli
  .option('-m, --mode <mode>', `[string] set env mode`)
cli
  .command('[root]', 'start dev server') // default command
  .alias('serve') // the command is called 'serve' in Vite's API
  .option('--port <port>', `[number] specify port`)
  .action(async (root: string, options: ServerOptions & GlobalCLIOptions) => {
    const { createServer } = await import('./server')
    try {
      const server = await createServer({
        root,
        base: options.base,
        mode: options.mode,
        configFile: options.config,
        logLevel: options.logLevel,
        clearScreen: options.clearScreen,
        optimizeDeps: { force: options.force },
        server: cleanOptions(options),
      })
  })

如上代碼所示,vite主要是使用cac這個(gè)命令行工具庫定義命令的,解釋一下這里使用到的cac的相關(guān)API:

  • cac(name?):用于創(chuàng)建一個(gè)cac實(shí)例,name參數(shù)是可選的。

  • option(name, description, config): 用于設(shè)置配置項(xiàng)。

  • command(name, description, config?): 聲明cli命令,可以給命令設(shè)置獨(dú)立的配置項(xiàng)喝其配置后的執(zhí)行動(dòng)作。

  • command.alias(name):給cli命令起別名。

  • action(callback): 指定命令所執(zhí)行的操作。

可以看出,當(dāng)運(yùn)行vite命令的時(shí)候會(huì)執(zhí)行createServer方法,我們這里要注意參數(shù)mode就是我們運(yùn)行命令時(shí)通過--mode 或者 -m指定的參數(shù),下面來研究createServer方法。

1.3.2 createServer

看一下createServer方法(路徑:createServervite/packages/vite/src/node/server/index.ts):

import { resolveConfig } from '../config'
export async function createServer(
  inlineConfig: InlineConfig = {},
): Promise<ViteDevServer> {
  const config = await resolveConfig(inlineConfig, 'serve')
}

可以看到createServer方法調(diào)用的是resolveConfig方法,下面看一下resolveConfig方法。

1.3.3 resolveConfig

resolveConfig方法的代碼如下(路徑;vite/packages/vite/src/node/config.ts):

import { loadEnv, resolveEnvPrefix } from './env'
export async function resolveConfig(
  inlineConfig: InlineConfig,
  command: 'build' | 'serve',
  defaultMode = 'development',
  defaultNodeEnv = 'development',
): Promise<ResolvedConfig> {
  const envDir = config.envDir
    ? normalizePath(path.resolve(resolvedRoot, config.envDir))
    : resolvedRoot
  const userEnv =
    inlineConfig.envFile !== false &&
    loadEnv(mode, envDir, resolveEnvPrefix(config))
  const resolvedConfig: ResolvedConfig = {
    command,
    mode,
    env: {
      ...userEnv,
      BASE_URL,
      MODE: mode,
      DEV: !isProduction,
      PROD: isProduction,
    },
  }
  const resolved: ResolvedConfig = {
    ...config,
    ...resolvedConfig,
  }
  return resolved
}

可以看到resolveConfig的主要工作:

  • 首先確定.env文件的路徑

  • 然后調(diào)用loadEnv方法加載解析.env文件,將結(jié)果賦值給userEnv

  • 最后返回整個(gè)解析后的配置

我們看到這里的關(guān)鍵代碼是loadEnv(mode, envDir, resolveEnvPrefix(config))下面我就重點(diǎn)看一下loadEnv方法。

1.3.4 loadEnv

loadEnv方法是vite中一個(gè)比較核心的方法,也作為vite對(duì)外提供的一個(gè)JavaScript API,用于加載 envDir 中的 .env 文件。

vite是如何解析.env文件的

我們看一下loadEnv方法(路徑:vite/packages/vite/src/node/env.ts):

import { parse } from 'dotenv'
import { arraify, lookupFile } from './utils'

export function loadEnv(
  mode: string,
  envDir: string,
  prefixes: string | string[] = 'VITE_',
): Record<string, string> {
  prefixes = arraify(prefixes)
  const env: Record<string, string> = {}
  const envFiles = [
    /** default file */ `.env`,
    /** local file */ `.env.local`,
    /** mode file */ `.env.${mode}`,
    /** mode local file */ `.env.${mode}.local`,
  ]

  const parsed = Object.fromEntries(
    envFiles.flatMap((file) => {
      const path = lookupFile(envDir, [file], {
        pathOnly: true,
        rootDir: envDir,
      })
      if (!path) return []
      return Object.entries(parse(fs.readFileSync(path)))
    }),
  )
  // only keys that start with prefix are exposed to client
  for (const [key, value] of Object.entries(parsed)) {
    if (prefixes.some((prefix) => key.startsWith(prefix))) {
      env[key] = value
    } else if (
      key === 'NODE_ENV' &&
      process.env.VITE_USER_NODE_ENV === undefined
    ) {
      // NODE_ENV override in .env file
      process.env.VITE_USER_NODE_ENV = value
    }
  }

  return env
}

如上代碼所示理解loadEnv方法注意以下幾個(gè)方面:

  • 該方法接收三個(gè)參數(shù),分別是模式、.env文件的路徑還有環(huán)境變量的前綴。

  • 使用遞歸方法lookupFile找到.env文件的路徑,使用fs.readFileSync讀取文件。

  • 使用dotenv提供的方法解析.env文件內(nèi)容。

關(guān)于dotenv可以學(xué)習(xí)川哥的文章,也可以看看筆者的源碼共讀語雀筆記。至此,我們了解了vite是如何讀取.env文件中定義的環(huán)境變量了。下面我們研究第二個(gè)問題vite如何將.env中配置的環(huán)境變量掛載到import.meta.env環(huán)境變量上。

2.如何將變量掛載到import.meta.env環(huán)境變量上

2.1 vite的環(huán)境變量和import.meta

Vite 在一個(gè)特殊的 import.meta.env 對(duì)象上暴露環(huán)境變量,有一些在所有情況下都可以使用的內(nèi)建變量:

import.meta.env.MODE: {string} 應(yīng)用運(yùn)行的模式。

import.meta.env.BASE_URL: {string} 部署應(yīng)用時(shí)的基本 URL。他由base 配置項(xiàng)決定。

import.meta.env.PROD: {boolean} 應(yīng)用是否運(yùn)行在生產(chǎn)環(huán)境。

import.meta.env.DEV: {boolean} 應(yīng)用是否運(yùn)行在開發(fā)環(huán)境 (永遠(yuǎn)與 import.meta.env.PROD相反)。

import.meta.env.SSR: {boolean} 應(yīng)用是否運(yùn)行在 server 上。

詳見環(huán)境變量。這里我們要解釋一下import.meta。它是一個(gè)給JavaScript模塊暴露特定上下文的元數(shù)據(jù)屬性的對(duì)象。它包含了這個(gè)模塊的信息,比如說這個(gè)模塊的URL。詳見import.meta 的MDN文檔。需要注意不可以在模塊的外部使用import.meta,如下圖所示:

vite是如何解析.env文件的

2.2 resolveConfig

在上文中我們已經(jīng)研究了resolveConfig的代碼,我們?cè)賮砜匆韵麓朔椒ㄖ械牧硪欢未a(路徑:vite/packages/vite/src/node/config.ts):

import {resolvePlugins,} from './plugins'

export async function resolveConfig(
  inlineConfig: InlineConfig,
  command: 'build' | 'serve',
  defaultMode = 'development',
  defaultNodeEnv = 'development',
): Promise<ResolvedConfig> {
  (resolved.plugins as Plugin[]) = await resolvePlugins(
    resolved,
    prePlugins,
    normalPlugins,
    postPlugins,
  )
}

這里調(diào)用了resolvePlugins,接收resolved對(duì)象,此對(duì)象中含有開發(fā)者所指定的模式以及.env文件中的環(huán)境變量。我們接著看一下resolvePlugins方法。

2.3 resolvePlugins

節(jié)選resolvePlugins方法如下(路徑:vite/packages/vite/src/node/plugins/index.ts):

import { definePlugin } from './define'

export async function resolvePlugins(
  config: ResolvedConfig,
  prePlugins: Plugin[],
  normalPlugins: Plugin[],
  postPlugins: Plugin[],
): Promise<Plugin[]> {
  return [
    //...
    definePlugin(config),
    //...
  ].filter(Boolean) as Plugin[]
}

resolvePlugins負(fù)責(zé)解析插件,這里面調(diào)用了definePlugin方法,我們看一下。

2.4 definePlugin

definePlugin的代碼如下(路徑:vite/packages/vite/src/node/plugins/define.ts):

const importMetaKeys: Record<string, string> = {}
const importMetaFallbackKeys: Record<string, string> = {}
if (isBuild) {
  const env: Record<string, any> = {
    ...config.env,
    SSR: !!config.build.ssr,
  }
  for (const key in env) {
    importMetaKeys[`import.meta.env.${key}`] = JSON.stringify(env[key])
  }
  Object.assign(importMetaFallbackKeys, {
    'import.meta.env.': `({}).`,
    'import.meta.env': JSON.stringify(config.env),
    'import.meta.hot': `false`,
  })
}

這段代碼的關(guān)鍵部分在于第8-10行的for循環(huán),將.env文件中定義的環(huán)境變量掛在到了import.meta.env上。至此,如何也了解了vite是如何將環(huán)境變量掛在到import.meta.env環(huán)境變量上。

關(guān)于“vite是如何解析.env文件的”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI