Skip to content

환경별 설정

@platform/vue-debugger개발/검증 환경 전용 도구입니다. 그래서 "어디서 켜고, 어디서 끄는가"가 이 도구의 가장 중요한 운영 지식입니다. 로컬에서는 아무 설정 없이 자동으로 켜지고, "배포됐지만 디버깅이 필요한" 빌드(WebView·검증계)에서만 몇 가지를 명시적으로 켜며, 진짜 프로덕션에는 코드가 단 한 줄도 들어가지 않습니다.

이 페이지는 그 세 환경의 차이를 매트릭스로 정리하고, 분기 로직이 어떻게 dead-code 제거를 만들어내는지, 그리고 배포 검증 빌드에서 __VUE_PROD_DEVTOOLS__sourcemap을 켜야 하는 이유를 코드 근거와 함께 설명합니다. 통합 절차는 통합, 켠 뒤의 검증은 설치 검증을 보세요.

환경 매트릭스 (한눈에)

먼저 전체 그림입니다. 세 환경이 각각 무엇을 켜고 끄는지 한눈에 보면 나머지 섹션이 빠르게 이해됩니다.

환경디버거__VUE_PROD_DEVTOOLS__sourcemap어떻게
local (vite dev)✅ 자동불필요 (dev가 이미 노출)불필요 (dev가 원본 제공)import.meta.env.DEV
개발/검증 배포 (vite build --mode staging)✅ 명시적필요 'true'권장 'hidden'MODE === 'staging' AND VITE_DEBUGGER=on + define
production (vite build)❌ 제거됨끔 (또는 hidden)분기 false → dead-code

핵심은 이렇습니다. 로컬은 아무것도 하지 않아도 켜지고, "배포됐지만 디버깅이 필요한" 빌드(WebView·검증계)에서만 두 가지(__VUE_PROD_DEVTOOLS__·sourcemap)를 추가로 켭니다. 진짜 프로덕션에는 셋 다 절대 켜지 않습니다.

분기 로직 — 왜 이 두 조건인가

통합에서 추가한 단 한 줄의 if가 모든 환경 분기를 처리합니다. 이 조건이 production에서 디버거를 번들에서 통째로 사라지게 만드는 원리이므로, 정확히 이해하고 그대로 쓰는 것이 중요합니다.

ts
if (import.meta.env.DEV || (import.meta.env.MODE === 'staging' && import.meta.env.VITE_DEBUGGER === 'on')) {
  const { attachDebugger } = await import('@platform/vue-debugger')
  attachDebugger(app, { /* 옵션 */ })
}
  • import.meta.env.DEVvite dev(로컬)에서만 true입니다. 로컬은 이 조건만으로 자동으로 켜집니다.
  • import.meta.env.MODE === 'staging' — 빌드 --mode에 묶입니다. 검증 빌드는 vite build --mode staging으로 만들고, VITE_DEBUGGER=on.env.staging에 둡니다.
  • **둘 다 정적으로 false인 production 빌드(mode !== 'staging')**에서는 Vite/Rollup이 이 블록과 그 안의 동적 import()까지 dead-code로 통째 제거합니다. 디버거 코드가 번들에 아예 들어가지 않습니다.

VITE_DEBUGGER만으로 분기하지 마세요 — mode를 함께 박아야 합니다 (보안)

if (import.meta.env.VITE_DEBUGGER === 'on')만 쓰면, Vite .env는 빌드 --mode와 무관해서 셸/CI 환경변수나 공통 .envVITE_DEBUGGER=on이 남으면 production 빌드에도 디버거가 그대로 실립니다(2026-06-17 금융권 보안 감사가 직접 재현한 누출 경로). MODE === 'staging'을 AND로 묶으면, production 빌드는 MODE가 리터럴 'production'이라 조건이 정적 false가 되어 환경변수가 새도 누출되지 않습니다.

동적 키 접근 금지 — 정적 치환만 dead-code가 됩니다

반드시 import.meta.env.VITE_DEBUGGER처럼 정적 치환 대상을 쓰세요. 빌드 도구는 이 표현을 빌드 시점에 리터럴 값으로 치환하고, 그 결과가 정적 false면 블록 전체를 제거합니다.

다음처럼 동적으로 접근하면 정적 치환이 일어나지 않아 dead-code 제거가 깨지고, 디버거가 프로덕션 번들에 그대로 남습니다.

ts
// ❌ 동적 키 접근 — 제거되지 않습니다 (프로덕션에 디버거가 남음)
const flag = 'VITE_DEBUGGER'
if (import.meta.env[flag] === 'on') { /* ... */ }

배포 전 누출 게이트 (CI required) — 금융권 등 '운영 배포 절대 차단'

코드 패턴(위)이 예방이라면, 누출 게이트는 증명입니다. 분기를 정확히 써도 사람은 실수하므로, 실제 배포될 dist를 만든 직후 디버거 흔적을 기계적으로 스캔해 잡히면 빌드를 실패시켜 배포를 차단하세요. 패키지에 동봉된 명령을 자사 CI/CD의 blocking(required) 스텝으로 둡니다.

bash
# 앱을 production 빌드한 뒤(실제 배포 산출물), 그 dist를 검사
npm run build            # 또는 vite build (production)
npx vue-debugger-leak-check dist   # 누출 발견 시 exit 1 → 파이프라인 실패 → 배포 차단

디버거 컴파일 코드에만 존재하는 문자열 마커(__VUE_DEBUGGER__·vue-debugger-host·__vdbgWrapped, minify로도 보존)를 스캔합니다. "라이브러리가 자동으로 막아준다"가 아니라, 이 게이트를 자사 CI에 배선하는 것이 누출 0의 실질 보장입니다. 추가로 production 빌드는 소스맵을 만들지 않도록(sourcemap: false) 하고, 디버거는 vite build --mode staging(검증계)에서만 포함하세요.

번들러별 분기 표현

Vite는 import.meta.env를 정적 치환하지만, webpack/Vue CLI에는 import.meta.env가 없습니다. 그쪽에서는 DefinePlugin이 정적 치환하는 process.env로 대체하세요.

ts
if (import.meta.env.DEV || (import.meta.env.MODE === 'staging' && import.meta.env.VITE_DEBUGGER === 'on')) {
  const { attachDebugger } = await import('@platform/vue-debugger')
  attachDebugger(app)
}
ts
// vue.config.js — 빌드 스크립트에 묶인 상수를 정의(셸/CI env로는 켤 수 없게)
// package.json: "build:staging": "vue-cli-service build --mode staging"
// const isStagingBuild = process.env.npm_lifecycle_event === 'build:staging'
// DefinePlugin에 추가: __VDBG_STAGING__: JSON.stringify(isStagingBuild)

declare const __VDBG_STAGING__: boolean

if (process.env.NODE_ENV !== 'production' || (__VDBG_STAGING__ && process.env.VUE_APP_DEBUGGER === 'on')) {
  const { attachDebugger } = await import('@platform/vue-debugger')
  attachDebugger(app)
}

플래그를 켜는 .env 파일과 빌드 명령은 다음과 같습니다.

bash
# 검증계 빌드용 — 이 mode 빌드에만 디버거 포함
VITE_DEBUGGER=on
bash
vite build --mode staging   # 이 빌드에만 디버거 포함
vite build                  # production — 디버거 없음

__VUE_PROD_DEVTOOLS__ — 배포 빌드에서만, 그러나 꼭

배포 검증 빌드(vite build 기반)에서 디버거를 켰는데 컴포넌트 트리는 보이지만 State 값이 전부 비어 있다면 거의 항상 이 플래그가 빠진 것입니다. 그래서 검증계 빌드에는 반드시 켜야 합니다.

ts
// vite.config.ts
export default defineConfig(({ mode }) => ({
  define: mode === 'staging' ? { __VUE_PROD_DEVTOOLS__: 'true' } : {},
}))
  • 언제 필요한가: vite build 기반 배포 빌드(검증계)에서만 필요합니다. vite dev(로컬)는 이 플래그 없이도 devtools 바인딩이 노출되므로 불필요합니다.
  • 왜 필요한가: 이 플래그를 켜면 @vitejs/plugin-vue<script setup>인라인 템플릿 OFF로 컴파일해, setup이 바인딩을 __returned__로 노출합니다. 끄면(=기본 프로덕션 빌드) setup이 렌더 함수만 반환하고 변수들이 그 클로저 안에 갇혀, 디버거가 관찰할 수 없습니다.
  • 끄면 무엇이 비는가: 컴포넌트 탭의 **State 값·출처 칩·식 평가가 전부 비거나 undefined**로 나옵니다. 트리는 보이는데 값만 다 비면 이게 원인입니다.

한 스위치, 두 가지 효과

__VUE_PROD_DEVTOOLS__는 런타임 devtools 훅과 컴파일 타임 바인딩 노출을 동시에 켜는 스위치입니다. 그래서 검증계 빌드에서는 이 하나로 컴포넌트 패널 전체가 살아납니다.

sourcemap — 'hidden'을 쓰는 이유

디버거의 스택 추적(에러·네트워크·pinia·라우터 호출 위치)은 번들 위치를 원본 위치로 복원해 보여줍니다. 그 복원은 소스맵에 의존하므로, "내 코드의 줄"을 보려면 소스맵이 필요합니다.

ts
export default defineConfig(({ mode }) => ({
  build: { sourcemap: mode === 'staging' ? 'hidden' : false },
}))
  • 무엇에 쓰는가: 디버거는 번들 위치(chunk-abc.js:1:2345)를 소스맵으로 원본(LessonShell.vue:33)으로 복원합니다. 소스맵이 있어야 난독화된 스택이 "내 코드 줄"로 보입니다.
  • 'hidden'인가: 'hidden'.map 파일을 생성하되 번들에 //# sourceMappingURL 주석을 남기지 않습니다. 디버거는 .map명시적으로 fetch해서 쓰므로 정상 동작하고, 일반 사용자의 브라우저 devtools에는 자동으로 노출되지 않습니다(소스 유출 표면 축소).

소스맵 설정에 따른 동작 차이는 다음과 같습니다.

build.sourcemap.map 생성디버거 복원devtools 자동 노출
'hidden' (권장)✅ (직접 fetch)❌ (주석 없음)
true✅ (주석 있음)
false (기본)chunk:line

복원이 안 될 때 / minify의 한계

  • sourcemap: false(기본)면 .map이 없어 복원이 불가능하고, 스택이 chunk:line 형태로만 보입니다(함수 위치 불명).
  • .map을 빌드 산출물에서 빼고 배포하면(흔한 보안 정책) 디버거가 fetch하지 못해 복원이 불가능합니다. 검증계에는 .map함께 두되, 외부 공개망에는 배포하지 마세요.
  • minify는 함수명을 종종 소실시킵니다(소스맵이 파일:줄은 주더라도 함수명 매핑은 불완전). 디버거는 파일:줄 위주로 보여주고, 이 한계를 정보 탭의 '알려진 한계'에 정직하게 고지합니다.
  • 번들 로딩 방식 전제 — 복원은 번들 URL 기준으로 .mapfetch합니다. 웹뷰가 번들을 https://(또는 dev의 http://)로 내려받는 경우에만 동작하며, 앱에 동봉된 번들(file://, capacitor://·ionic:// 등 커스텀 스킴, WebViewAssetLoader의 비HTTP 경로)은 현재 복원이 되지 않습니다(fetch가 해당 스킴을 지원하지 않거나 프레임 파싱 대상이 아님). 자사 웹뷰가 번들을 어떤 방식으로 싣는지 먼저 확인하세요 — 동봉형이라면 스택은 raw로만 보입니다.
  • CSP를 쓰는 검증계라면 connect-src.map을 받는 호스트가 포함되어야 합니다(막히면 조용히 복원 실패).

.map을 외부에 공개하지 마세요

.map 파일은 원본 소스를 그대로 복원합니다. 폐쇄망·사내 검증계 한정으로만 배포하고, 외부 공개망에는 절대 올리지 마세요.

합친 예시 — 검증계 빌드 한 벌

위 두 가지를 한 vite.config.ts에 합치면, staging mode 빌드에서만 디버거·introspection·소스맵이 함께 켜지고 일반 vite build에서는 셋 다 사라집니다.

ts
// vite.config.ts
export default defineConfig(({ mode }) => {
  const debug = mode === 'staging'
  return {
    define: debug ? { __VUE_PROD_DEVTOOLS__: 'true' } : {},
    build: { sourcemap: debug ? 'hidden' : false },
  }
})
bash
# .env.staging
VITE_DEBUGGER=on
bash
vite build --mode staging   # 디버거 + introspection + .map  (검증계·WebView)
vite build                  # production — 셋 다 없음
세 조각이 각각 어떤 일을 하나요?
  • .env.stagingVITE_DEBUGGER=on — main.ts의 분기를 true로 만들어 디버거 코드를 번들에 포함시킵니다.
  • __VUE_PROD_DEVTOOLS__: 'true'<script setup> 바인딩을 노출해 컴포넌트 State·출처 칩·식 평가가 차도록 합니다.
  • sourcemap: 'hidden'.map을 생성해 스택 추적의 원본 위치 복원을 가능하게 합니다(주석 없이).

--mode staging을 빼면 debugfalse가 되어 셋 다 빠집니다. 즉 일반 vite build는 자동으로 안전한 production 빌드입니다.

진짜 프로덕션에는 절대 켜지 마세요

production 누출 금지

진짜 프로덕션 빌드에는 VITE_DEBUGGER=on·__VUE_PROD_DEVTOOLS__·소스맵 공개를 절대 주지 마세요.

디버거는 쿠키·로컬/세션 스토리지·API 응답·네이티브 브리지 호출을 그대로 노출하고, 소스맵은 원본 코드를 복원합니다. 이 도구는 dev/검증(내부망) 전용입니다.

평소 vite build는 분기가 정적 false이므로 디버거가 자동으로 dead-code 제거됩니다. 의도적으로 켜지 않는 한 누출되지 않습니다.

빌드 산출물에 디버거가 정말 빠졌는지 확인하는 누출 검사와 전체 설치 체크리스트는 설치 검증에서 다룹니다. 환경 분기 한 줄을 어디에 넣는지는 통합, 옵션 전체는 옵션 레퍼런스를 참고하세요.

개발/검증 환경 전용 인페이지 Vue 디버거 · 진짜 프로덕션엔 코드가 들어가지 않습니다