다크 모드
앱에 연동
이 페이지는 @platform/vue-debugger를 여러분의 Vue 3 앱 부트스트랩에 붙이는 방법을 다룹니다. 핵심은 main.ts에 몇 줄입니다 — 조건부 동적 import()로 디버거를 지연 로드하고, attachDebugger(app)을 호출하면 끝입니다. 별도 플러그인 등록이나 CSS import는 필요 없습니다.
이 페이지를 마치면 앱을 dev로 띄웠을 때 우측 하단에 톱니(⚙) FAB가 뜨고, Alt+Shift+D로 디버거를 토글할 수 있습니다. 설치가 아직이라면 설치를 먼저 보세요.
어디서 호출하나
디버거는 호스트 앱의 pinia·router를 import하지 않고 런타임에 구조적으로 발견합니다. 따라서 발견 대상이 이미 앱에 등록된 뒤, 그리고 앱이 마운트되기 전에 붙여야 합니다.
attachDebugger는 app.use(pinia/router) 뒤, app.mount() 전에 호출하세요.
app.use(...)뒤 — 이 순서를 지켜야 Pinia·Router 패널이 인스턴스를 발견합니다. 더 일찍 호출하면 해당 패널이 빈 상태로 뜹니다.app.mount()전 — 마운트 시점에 디버거가 자리 잡고 있어야 초기 렌더·라우팅·네트워크를 처음부터 기록합니다.
main.ts 전체 예시
아래가 연동의 전부입니다. 부트스트랩을 async function으로 감싸고, 그 안에서 개발/검증 빌드일 때만 디버거를 동적 import()로 불러와 붙입니다.
ts
import { createApp } from 'vue'
import App from './App.vue'
async function bootstrap() {
const app = createApp(App)
// app.use(createPinia()) / app.use(router) ... (있다면 먼저)
// 개발/검증 빌드에서만 디버거를 붙인다 (프로덕션엔 코드가 들어가지 않음)
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)
}
app.mount('#app')
}
bootstrap()활성 분기 import.meta.env.DEV || (import.meta.env.MODE === 'staging' && import.meta.env.VITE_DEBUGGER === 'on')이 모든 환경을 한 줄로 처리합니다. 로컬(vite dev)에선 DEV가 true라 자동으로 켜지고, 진짜 프로덕션 빌드에선 두 조건이 정적으로 false가 되어 이 블록과 그 안의 동적 import()가 dead-code로 통째 제거됩니다. 환경별 빌드 설정의 전체 가이드는 환경별 설정을 보세요.
정적 치환 대상만 쓰세요 — 동적 키 접근 금지
분기는 반드시 import.meta.env.VITE_DEBUGGER처럼 정적으로 접근하세요. const f = 'VITE_DEBUGGER'; import.meta.env[f]처럼 동적으로 접근하면 Vite/Rollup이 값을 정적 치환하지 못해 dead-code 제거가 일어나지 않고, 디버거가 프로덕션 번들에 그대로 남습니다. 디버거는 쿠키·스토리지·API 응답을 노출하므로 프로덕션 누출은 보안 사고입니다.
왜 async function으로 감싸나
await import()로 디버거를 지연 로드하려면 await를 쓸 수 있는 컨텍스트가 필요합니다. 이때 main.ts 최상단에 await를 직접 쓰면 안 됩니다(top-level await 금지).
top-level await 함정 — 검증계 빌드가 깨집니다
await import()를 main.ts 최상단에 직접 쓰면 vite dev(esbuild)에선 동작하지만, vite build의 기본 타깃(es2020 / chrome87…)이 top-level await를 거부해 검증계 빌드가 깨집니다.
반드시 async function bootstrap()으로 감싸고 그 안에서 await 하세요. async 함수 내부의 await는 일반 await라 모든 빌드 타깃에서 안전하고, 동적 import의 프로덕션 dead-code 제거도 그대로 유지됩니다.
(프로덕션은 분기가 정적 false → import() 블록째 제거되어 어차피 await가 남지 않지만, 검증계 빌드를 깨뜨리지 않으려면 감싸는 것이 정답입니다.)
webpack / Vue CLI는 어떻게 다른가
import.meta.env는 Vite 전용입니다. webpack/Vue CLI에는 없으므로 process.env.NODE_ENV로 분기하세요(DefinePlugin이 정적 치환).
ts
async function bootstrap() {
const app = createApp(App)
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)
}
app.mount('#app')
}
bootstrap()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
async function bootstrap() {
const app = createApp(App)
if (process.env.NODE_ENV !== 'production' || (__VDBG_STAGING__ && process.env.VUE_APP_DEBUGGER === 'on')) {
const { attachDebugger } = await import('@platform/vue-debugger')
attachDebugger(app)
}
app.mount('#app')
}
bootstrap()webpack에서 VUE_APP_DEBUGGER 단독 분기는 금지
DefinePlugin은 셸/CI의 VUE_APP_* 환경변수도 정적 치환하므로, NODE_ENV 체크에 VUE_APP_DEBUGGER === 'on'를 OR로만 붙인 분기는 CI에 잔존한 env 하나로 production 번들에 디버거가 실릴 수 있습니다(Vite 쪽 MODE === 'staging' AND 결합과 같은 이유). 위처럼 빌드 스크립트에 묶인 상수(__VDBG_STAGING__)와 AND로 묶고, npx vue-debugger-leak-check <dist>를 CI blocking 게이트로 배선하세요.
실행 — FAB와 단축키
연동을 마치고 앱을 dev로 띄우면 화면 우측 하단에 ⚙ FAB가 나타납니다. 이걸 누르거나 Alt+Shift+D를 누르면 11개 패널을 가진 디버거가 열립니다.
| 동작 | 방법 |
|---|---|
| 디버거 열기 | 우측 하단 ⚙ FAB 클릭 |
| 토글(열기/닫기) | Alt+Shift+D |
CSS import가 필요 없습니다
디버거는 Shadow DOM 안에서 동작하며 스타일을 자동으로 주입합니다. 그래서 별도의 CSS import가 필요 없고, 호스트 앱의 CSS와도 충돌하지 않습니다(:host{all:initial}로 완전 격리). 구조와 격리 원리는 아키텍처를 보세요.
콘솔에 ⚠ @platform/vue-debugger ACTIVE 경고가 보입니다 — 의도된 것입니다
attachDebugger가 활성화되면 콘솔에 끌 수 없는 경고를 남깁니다. dev/검증계에서는 "디버거가 켜졌다"는 정상 신호이니 무시하세요. 이는 심층방어 트립와이어로, 만약 production 콘솔에서 이 문구가 보이면 누출이니 즉시 보안·누출 차단의 게이트를 확인하세요.
FAB·패널이 비어 보일 때
- FAB가 안 보임 →
attachDebugger를app.mount()전에 불렀는지, 분기가 dev에서true인지 확인하세요. - Pinia/Router 패널이 비어 있음 →
attachDebugger를app.use(pinia/router)뒤에 호출하세요. - 배포 빌드에서 컴포넌트 트리는 보이는데 값이 다 비어 있음 →
__VUE_PROD_DEVTOOLS__설정이 빠진 것입니다(환경별 설정 참고).
더 많은 증상과 해결은 문제 해결에 있습니다.
반환 핸들로 제어하기
attachDebugger(app)은 디버거를 프로그래밍 방식으로 제어할 수 있는 핸들을 반환합니다. 직접 호출할 일은 드물지만, 자동화 테스트나 커스텀 트리거를 붙일 때 유용합니다.
ts
const handle = attachDebugger(app)
handle.version // 디버거 버전 문자열 (예: '0.17.4')
handle.open() // 패널 열기
handle.close() // 패널 닫기
handle.toggle() // 열기/닫기 토글
handle.destroy() // UI 제거 + 정리, window.__VUE_DEBUGGER__ 해제반환 타입은 DebuggerHandle | null입니다. **비브라우저 환경(SSR 서버 측 등 window/document가 없는 경우)에선 null**을 반환하며 아무 작업도 하지 않으므로, 별도 분기 없이 안전하게 호출할 수 있습니다.
핸들은 전역에서도 접근할 수 있습니다. 같은 핸들이 window.__VUE_DEBUGGER__에 등록되므로, 콘솔이나 다른 모듈에서 바로 제어하거나 버전을 확인할 수 있습니다.
ts
window.__VUE_DEBUGGER__?.toggle()
window.__VUE_DEBUGGER__?.version // 정보 탭에서도 동일하게 확인 가능HMR·중복 부착은 알아서 막습니다
attachDebugger는 이미 window.__VUE_DEBUGGER__가 있으면 새로 만들지 않고 기존 핸들을 그대로 반환합니다. 그래서 HMR로 main.ts가 다시 평가되어도 디버거가 중복 부착되지 않습니다.