Vite의 import.meta.env를 알아보자

쭌로그·2025년 5월 31일
2
post-thumbnail

회사에서 Vue 프로젝트를 진행하며 가장 잘 바꿨다고 생각하는 부분은 번들러입니다. Webpack을 사용할 때 느린 번들링 및 빌드 속도로 인해 너무 답답하게 느껴졌고, 이는 프로젝트의 규모가 더욱 커지고 소스코드가 많아질수록 크게 와닿았습니다.

이를 해결하기 위해 Vue 진영에서 강력하게 밀고 있는 Vite로 마이그레이션을 진행하게 되었습니다. Vite의 공식 문서에서 소개하는 가장 큰 특징은 devServer 속도입니다. Vite는 앱을 시작할 때 node_modules에 있는 라이브러리를 ESM으로 변환하여 캐시상태로 저장하고 devServer를 cold-start할 때 한번만 실행됩니다.

Webpack은 모든 파일을 빌드와 번들링을 진행해야 구동이 되었지만 Vite는 ESM으로 제공하기 때문에 원하는 라이브러리만 동적으로 가져오는 것이 가능해졌습니다.

환경변수

Node.js에서는 process라는 예약어를 통해 환경 변수에 접근할 수 있도록 했습니다. Node.js를 통해 구동시키는 라이브러리, 프레임워크는 이 환경 변수에 값을 추가하거나 수정하여 프로젝트에 원하는 값(secret Key, ID) 등을 주입할 수 있습니다.

process.env.SECRET_KEY = "test123"
console.log(process.env.SECRET_KEY) //test123

위와 같이 값을 주입하는 방식보다는 .env 파일을 만들고 그 내부에 있는 값들을 process.env에 주입하여 사용하는데 dotenv 라이브러리가 이와 같은 과정을 처리해 줍니다.

dotenv

dotenv 라이브러리는 .env에 있는 값들을 process.env에 주입시켜주는데 이 과정을 통해 런타임 javascript에서 사용할 수 있습니다. 이 과정에 어떻게 이루어진 건지 궁금하여 소스 코드를 확인해 보았습니다.

npm init -y
npm i -D dotenv

위 명령어를 통해 프로젝트를 생성해 줍니다. 저는 ESM 환경을 선호하기 때문에

{
  "name": "dotenv",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module",
//...
}

type: module을 선언하여 ESM 환경으로 설정하였습니다.

TEST=123
import dotEnv from 'dotenv';

console.log(dotEnv.config())

env 실행 결과

프로젝트 루트 디렉터리에 .env파일을 생성하고 출력해 보면 위와 같이 결괏값이 출력됩니다.

// Populates process.env from .env file
function config (options) {
  // fallback to original dotenv if DOTENV_KEY is not set
  if (_dotenvKey(options).length === 0) {
    return DotenvModule.configDotenv(options)
  }

  const vaultPath = _vaultPath(options)

  // dotenvKey exists but .env.vault file does not exist
  if (!vaultPath) {
    _warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`)

    return DotenvModule.configDotenv(options)
  }

  return DotenvModule._configVault(options)
}

dotenv 라이브러리에서 제공하는 config 메서드는 위와 같습니다.
저희는 아무런 옵션도 제공하지 않았기 때문에 내부적으로 configDotEnv 메서드를 호출하고 있는 것을 확인할 수 있습니다.

function configDotenv (options) {
  const dotenvPath = path.resolve(process.cwd(), '.env')
  //...

  for (const path of optionPaths) {
    try {
      // Specifying an encoding returns a string instead of a buffer
      const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }))

      DotenvModule.populate(parsedAll, parsed, options)
    } catch (e) {
      if (debug) {
        _debug(`Failed to load ${path} ${e.message}`)
      }
      lastError = e
    }
  }

  let processEnv = process.env
  if (options && options.processEnv != null) {
    processEnv = options.processEnv
  }

  DotenvModule.populate(processEnv, parsedAll, options)

  if (lastError) {
    return { parsed: parsedAll, error: lastError }
  } else {
    return { parsed: parsedAll }
  }

위 메서드으 동작 순서는 아래와 같습니다.

  1. process.cwd() 메서드를 호출하여 node 명령을 실행한 프로젝트의 절대 경로를 반환한다.
  2. path.resolve 메서드를 호출하여 /프로젝트의 절대 경로/.env 경로를 반환한다.
  3. parsed 변수에 .env 파일의 값을 파싱 해서 저장한다.
    3-1. readFileSync로 읽어온 파일(.env)의 값을 DotenvModule.parsed메서드를 통해 Javascript 객체로 변환한다.
    3-2. 이 때의 결과값이 {parsed: {test: "123"}}의 객체값이다.
  4. populate 메서드를 한다.
// Populate process.env with parsed values
function populate (processEnv, parsed, options = {}) {
 
  // Set process.env
  for (const key of Object.keys(parsed)) {
    if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
    processEnv[key] = parsed[key]
  }
}

populate 메서드에서는 파싱 된 key 값을 통해 processEnv 환경 변수에 값을 주입하고 있습니다.

정리

dotEnv 라이브러리는 .env 파일 값을 파싱해 객체로 변환 후 process.env 환경 변수에 값을 저장한다. 이 전역 변수가 저장될 때에는 server가 구동한 초기에 값을 넣어주는데 Vite에서는 이 값이 변경되면 순간적으로 devServer를 다시 구동시킵니다.

Vite에서의 환경 변수 관리

Vite 또한 여느 라이브러리처럼 env에 값을 주입하여 환경변수로 사용할 수 있습니다.
Webpack과는 다르게 Vite에서는 import.meta라는 자바스크립트 문법을 통해 접근하라고 설명하고 있습니다.

import.meta란 무엇인가?

MDN 공식 사이트에서 말하는 import.meta는 아래와 같습니다

import.meta 속성은 모듈의 메타 데이터를 JavaScript 모듈에 노출합니다. 여기에는 URL과 같은 모듈에 대한 정보가 포함됩니다.

아까 작성했던 main.js에서 import.meta를 console.log로 작성하면 아래와 같이 나옵니다.

console.log(import.meta)

1. server

import.meta

2. broswer

주의 : import.meta는 ESM(ECMAScript Modules) 환경에서만 동작하기 때문에 package.json에서 type:module을 작성하거나 main.mjs와 같이 확장자를 설정해야 합니다.

Vite에서 import.meta로 환경변수를 사용하는 이유

Webpack에서의 process.env는 ESM 환경이 아니기 때문에 브라우저에서 console.log(process.env)를 사용하면 ReferenceError: process is not defined와 같은 오류가 발생합니다. process.env는 JS가 아닌 Node.js의 전역 객체이기 때문입니다.
하지만 import.meta는 ECMA에 도입된 새로운 기능이기 때문에 브라우저에서 확인할 수 있습니다. 이를 활용하기 위해 vite에서는 import.meta 객체에 env 속성을 추가하여 환경 변수를 주입하는 것입니다. 이는 모듈 스코프 내에서 안전하게 동작하고 브라우저에서도 접근할 수 있게 해주기 때문에 채택하고 있습니다.

보안에서의 이점

process.env는 서버 환경에서 모든 시스템 환경 변수에 접근할 수 있어 보안을 주의해서 사용해야 합니다. 하지만 Vite의 import.meta.env는 VITE_ 접두사가 붙은 환경 변수만 노출하도록 제한합니다.

TEST=123
VITE_TEST="abcd"
console.log(import.meta.env.TEST) //undefined 출력
console.log(import.meta.env.VITE_TEST) //abcd

위 코드를 실행하면 같이 .env파일에 있지만 VITE_ 접두사가 붙은 값만 브라우저에서 출력됩니다.
이는 개발자가 의도적으로 노출시키고 싶은 변수만 클라이언트 측에서 접근 가능하게 하여 보안 위험을 줄일 수 있습니다.

결론

결론적으로, Vite는 모던 웹 개발의 핵심인 ES Modules 기반의 브라우저 환경에 맞춰 import.meta.env를 채택하여 환경 변수를 보다 안전하고 효율적으로 관리해 주기 때문에 사용하는 것으로 보입니다.

참고

Vite에서 import.meta는 왜 사용하는 걸까? (feat. HMR)

profile
매일 발전하는 프론트엔드 개발자

3개의 댓글

comment-user-thumbnail
2025년 6월 21일

안녕하세요 쭌로그님!🙃
좋은 글 작성해주셔서 이해하기 쉽게 읽을 수 있었던 것 같습니다 ㅎㅎ

[import.meta란 무엇인가? - 2. broswer] 목차에서 "import.meta는 CJS(Common JS) 환경에서만 동작"이라고 설명해 주셨는데, import.meta는 ESM(ECMAScript Modules)환경에서 동작하는 것으로 알고 있고, 그 밑에 확장자 설명에도 오타가 있는 것 같습니다! main.msjmain.mjs

혹시라도 제가 잘못 알고 있다면 편하게 말씀주셔도 좋습니다~~

2개의 답글