다크 모드
환경별 설정
@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.DEV—vite 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 환경변수나 공통 .env에 VITE_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=onbash
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 기준으로
.map을fetch합니다. 웹뷰가 번들을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=onbash
vite build --mode staging # 디버거 + introspection + .map (검증계·WebView)
vite build # production — 셋 다 없음세 조각이 각각 어떤 일을 하나요?
.env.staging의VITE_DEBUGGER=on— main.ts의 분기를true로 만들어 디버거 코드를 번들에 포함시킵니다.__VUE_PROD_DEVTOOLS__: 'true'—<script setup>바인딩을 노출해 컴포넌트 State·출처 칩·식 평가가 차도록 합니다.sourcemap: 'hidden'—.map을 생성해 스택 추적의 원본 위치 복원을 가능하게 합니다(주석 없이).
--mode staging을 빼면 debug가 false가 되어 셋 다 빠집니다. 즉 일반 vite build는 자동으로 안전한 production 빌드입니다.
진짜 프로덕션에는 절대 켜지 마세요
production 누출 금지
진짜 프로덕션 빌드에는 VITE_DEBUGGER=on·__VUE_PROD_DEVTOOLS__·소스맵 공개를 절대 주지 마세요.
디버거는 쿠키·로컬/세션 스토리지·API 응답·네이티브 브리지 호출을 그대로 노출하고, 소스맵은 원본 코드를 복원합니다. 이 도구는 dev/검증(내부망) 전용입니다.
평소 vite build는 분기가 정적 false이므로 디버거가 자동으로 dead-code 제거됩니다. 의도적으로 켜지 않는 한 누출되지 않습니다.
빌드 산출물에 디버거가 정말 빠졌는지 확인하는 누출 검사와 전체 설치 체크리스트는 설치 검증에서 다룹니다. 환경 분기 한 줄을 어디에 넣는지는 통합, 옵션 전체는 옵션 레퍼런스를 참고하세요.