다크 모드
설정 옵션
이 페이지는 @platform/vue-debugger가 외부로 노출하는 모든 API를 다루는 레퍼런스입니다. 진입점 attachDebugger, 모든 옵션을 담는 DebuggerOptions, 반환되는 DebuggerHandle, 그리고 부가 export(traceGuards·untraceGuards·traceAxios·untraceAxios·VERSION)를 한곳에 정리했습니다.
대부분의 프로젝트는 옵션 없이 attachDebugger(app) 한 줄로 충분합니다. 디버거가 pinia·router를 런타임에 스스로 발견하기 때문입니다. 아래 옵션은 자동 발견이 닿지 않는 경계(WebView 브리지, 폐쇄망 APM 비콘, 사내 리포트 수집)를 메우기 위한 것입니다. 연동 방법 자체는 설치와 통합을 먼저 보세요.
attachDebugger(app, options?)
개발/검증 빌드의 앱에 인페이지 디버거를 붙이는 유일한 진입점입니다. app.use(pinia)·app.use(router) 이후, app.mount() 이전에 호출하세요 — 그래야 디버거가 등록된 스토어와 라우터를 발견합니다.
ts
function attachDebugger(
app: App,
options?: DebuggerOptions,
): DebuggerHandle | null| 매개변수 | 타입 | 설명 |
|---|---|---|
app | App (vue) | createApp()이 반환한 애플리케이션 인스턴스 |
options | DebuggerOptions | 선택. 미지정 시 모든 값이 기본값 |
반환값 — 브라우저 환경에서는 DebuggerHandle을, window/document가 없는 비브라우저 환경(SSR 서버 렌더 등)에서는 null을 반환합니다. 따라서 반환값을 쓸 때는 항상 null 가능성을 염두에 두세요.
중복 부착은 자동 방지
이미 window.__VUE_DEBUGGER__가 있으면 attachDebugger는 새로 만들지 않고 기존 핸들을 그대로 반환합니다. HMR로 부트스트랩이 다시 실행돼도 디버거가 두 번 붙지 않습니다.
DebuggerOptions
모든 옵션은 선택이며, 합리적인 기본값을 가집니다. 권위 있는 정의는 패키지의 types.ts이며, 아래 표는 그것과 일치합니다.
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
pinia | PiniaLike | { install } | 자동 발견 | 미지정 시 app.config.globalProperties.$pinia에서 발견 |
router | RouterLike | 자동 발견 | 미지정 시 app.config.globalProperties.$router에서 발견 |
maxEntries | number | 500 | 종류별 기록 버퍼 상한 |
hotkey | boolean | true | Alt+Shift+D 패널 토글 단축키 |
ignoreStorageKeys | RegExp | /^__vdbg/ | 스토리지 변경 로그에서 무시할 키 |
trackReads | boolean | true | [0.17] 스토리지 읽기 출처 추적(읽기 의존성 맵) — 끄려면 false |
ignoreUrls | Array<string | RegExp> | [] | 네트워크 추적에서 제외할 URL(문자열=부분일치, RegExp=정규식) |
bridges | string[] | [] | 감시할 WebView↔네이티브 브리지 객체 이름들 |
report | { endpoint?, allowHosts?, redact? } | 미설정 | 리포트 옵트인 전송 + 허용 호스트·민감정보 마스킹 |
pinia
평소에는 필요 없습니다. 디버거가 app.config.globalProperties.$pinia에서 인스턴스를 자동으로 찾기 때문입니다. 비표준 셋업으로 자동 발견이 실패할 때만 실제 Pinia 인스턴스를 그대로 넘기세요.
ts
type pinia = PiniaLike | { install: (...args: any[]) => any }왜 타입이 PiniaLike | { install } 인가
공개 Pinia 타입에는 디버거가 의존하는 내부 필드 _s(등록된 스토어 맵)가 없어, 실제 인스턴스를 PiniaLike로는 직접 받을 수 없습니다. 그래서 Vue 플러그인 형태({ install })로 느슨하게 받아들이고, 런타임에 _s로 접근합니다. 이 패키지는 pinia를 import하지 않으므로, 앱의 pinia 버전이 무엇이든 안전합니다.
router
마찬가지로 보통 불필요합니다. app.config.globalProperties.$router에서 자동 발견되며, 실패할 때만 실제 Router를 넘깁니다. 전역 라우터 가드 추적은 별도 함수 traceGuards를 사용하세요.
maxEntries
장시간 세션에서도 메모리가 무한히 자라지 않도록, 이벤트 종류별(네트워크·콘솔·pinia 등)로 기록을 이 개수만큼만 유지합니다. 기본 500이면 대부분의 디버깅 세션에 충분합니다.
hotkey
true(기본)이면 Alt+Shift+D로 패널을 토글합니다. 호스트 앱이 같은 조합을 이미 쓴다면 false로 끄고, 우측 하단 ⚙ FAB만 사용하세요.
ignoreStorageKeys
스토리지 변경 로그에서 무시할 키 패턴입니다. 기본값 /^__vdbg/는 디버거 자신이 쓰는 키를 가립니다(자기 자신의 노이즈 제거). 앱이 자주 갱신하는 캐시 키 등을 추가로 가리려면 직접 RegExp를 지정하세요.
trackReads 0.17
Storage.prototype.getItem을 후킹해 "어느 코드가 어느 키를 읽는가"를 스토리지 패널의 읽기 의존성 맵으로 보여줍니다(기본 true). 값은 원본 그대로 반환되고 기록은 try/catch로 격리되어 앱 동작에 영향이 없습니다. 기준정보(마스터데이터)를 세션스토리지에 두는 앱에서 "이 키를 지워도 되나 / 누가 쓰나"를 런타임 증거로 답하는 용도입니다 — 동적 키(code.${type})와 서드파티 읽기는 grep이 못 잡고 런타임 역인덱스만 잡습니다.
- 비용 — 읽기당 ~0.3µs(데스크톱)/~1–3µs(실기기) + 키당 첫 출처들 캡처 시 스택 캡처 수십 µs. 캡처는 키당 고유 출처 16곳 + 중복 예산 48회, 관측 키 500개 상한으로 유계입니다(상한 도달 시 패널에 고지).
- 관측 한계(정직) —
storage['k']·점 접근은 HTML 스펙상getItem을 경유하지 않아 잡히지 않습니다. "출처 0곳 ≠ 아무도 안 읽음". 실행 안 된 경로도 안 보입니다(관측 기반). 자세한 배경은 격리·호환·한계를 보세요.
ts
attachDebugger(app, { trackReads: false }) // 읽기 후킹 자체를 끄기팀 lint로 커버리지 확보 (권장) — 괄호/점 접근 금지 규칙
맵이 못 보는 storage['k']·storage.k 접근을 코드베이스에서 없애면 "출처 0곳"의 신뢰도가 올라갑니다. no-restricted-syntax로 강제하세요:
js
// eslint.config.js
rules: {
'no-restricted-syntax': [
'error',
{ selector: "MemberExpression[computed=true][object.name=/^(localStorage|sessionStorage)$/]",
message: "storage['key'] 괄호 접근 금지 — getItem/setItem을 쓰세요 (읽기 의존성 맵 관측 대상이 되도록)." },
{ selector: "MemberExpression[computed=true][object.property.name=/^(localStorage|sessionStorage)$/]",
message: "window.storage['key'] 괄호 접근 금지 — getItem/setItem을 쓰세요." },
{ selector: "MemberExpression[computed=false][object.name=/^(localStorage|sessionStorage)$/]:not([property.name=/^(getItem|setItem|removeItem|clear|key|length)$/])",
message: "storage.key 점 접근 금지 — getItem/setItem을 쓰세요." },
{ selector: "MemberExpression[computed=false][object.property.name=/^(localStorage|sessionStorage)$/]:not([property.name=/^(getItem|setItem|removeItem|clear|key|length)$/])",
message: "window.storage.key 점 접근 금지 — getItem/setItem을 쓰세요." },
],
},별칭 변수(const s = localStorage) 경유까지는 못 잡습니다(정적 분석 한계). 장기적으로는 스토리지 접근을 단일 유틸 모듈로 모으는 컨벤션이 정답이고, 디버거 관측은 레거시·서드파티용 안전망입니다. 같은 규칙이 패키지 동봉 SETUP.md §3-2에도 있습니다.
ignoreUrls
네트워크 추적에서 제외할 URL 패턴 배열입니다. 문자열은 부분일치, RegExp는 정규식으로 매칭합니다.
ts
ignoreUrls: ['/apm', /datadog|newrelic|sentry/, '/telemetry', '/heartbeat']폐쇄망 환경에는 APM·모니터링 비콘(Datadog/New Relic/Sentry/자체 텔레메트리)이 3~5초마다 폴링하며 네트워크 탭을 가득 채우는 경우가 흔합니다. 비즈니스 로직과 무관한 이런 텔레메트리를 걸러 '진짜 요청'만 보이게 하려고 이 옵션을 씁니다.
래핑 충돌 고지
다른 도구가 이미 console·fetch를 감쌌다면 디버거는 호출 위치·타이밍이 어긋날 수 있다는 점을 정보 탭에 정직하게 고지합니다. APM 환경에서 자주 마주치니 문제 해결도 함께 보세요.
bridges
감시할 WebView↔네이티브 브리지 객체의 이름들입니다. iOS WKWebView(webkit.messageHandlers)·React Native·Flutter는 이름 없이 자동 감지되므로 지정할 필요가 없습니다. 주로 Android @JavascriptInterface로 주입한 커스텀 객체 이름을 줍니다.
ts
bridges: ['AndroidBridge', 'NativeApp']핵심 한 줄
bridges엔 '객체 이름'만 줍니다. 그 객체의 메서드는 디버거가 자동 열거·기록합니다(메서드별 설정 불필요). 단, 네이티브에서 @JavascriptInterface가 붙은 메서드만 JS에 노출됩니다.
Android @JavascriptInterface 객체는 어떻게 생겼고, 메서드는 자동인가요?
그 "커스텀 객체"는 네이티브(앱) 코드가 만들어 JS 전역에 꽂아 넣은 것입니다. Android WebView에서 네이티브 코드가 webView.addJavascriptInterface(객체, "이름")을 호출하면, 그 객체가 JS의 window.이름으로 노출됩니다. bridges에 주는 문자열이 바로 이 **두 번째 인자(노출 이름)**입니다.
네이티브 메서드는 각각 @JavascriptInterface 애너테이션이 붙은 것만 JS에서 호출할 수 있습니다(Android 4.2+ 보안 규칙). 애너테이션이 없는 메서드는 JS에 아예 보이지 않으므로 디버거도 볼 수 없습니다. 이건 앱(네이티브) 쪽 설정이지, 웹·디버거 쪽 설정이 아닙니다.
kotlin
class WebAppBridge(private val context: Context) {
@JavascriptInterface
fun showToast(message: String) { /* ... */ }
@JavascriptInterface
fun getDeviceId(): String { /* ... */ }
}
// WebView 설정 시:
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(WebAppBridge(context), "AndroidBridge")위처럼 주입하면 JS에서 다음과 같이 호출됩니다.
js
window.AndroidBridge.showToast('안녕')
window.AndroidBridge.getDeviceId()메서드는 자동 수집됩니다 — 객체 이름만 주면 됩니다. 디버거는 bridges로 받은 이름으로 window['AndroidBridge'] 객체를 찾은 뒤, 그 객체의 키 중 함수인 것을 전부 열거해 하나씩 자동으로 감쌉니다. 즉 위 예시라면 showToast·getDeviceId 두 메서드가 둘 다 자동으로 기록 대상이 됩니다. 메서드를 하나씩 등록하는 작업은 필요 없습니다.
ts
// 웹 개발자가 할 일은 이 한 줄뿐 — 노출 이름만 넣으면 됩니다.
attachDebugger(app, { bridges: ['AndroidBridge'] })참고로 iOS WKWebView(webkit.messageHandlers)·React Native(ReactNativeWebView)·Flutter(flutter_inappwebview)는 이름 없이 자동 감지되므로 bridges에 적을 필요가 없습니다. 네이티브 주입이 JS보다 늦게 들어오는 경우에도 디버거가 잠깐 폴링하며 등장을 기다렸다가 잡습니다. 객체가 읽기전용이라 메서드를 감쌀 수 없을 때는 자동 기록은 못 하지만, 패널에서의 수동 호출은 여전히 가능합니다.
모바일 퍼스트
WebView에는 devtools가 없어 디버거가 유일한 디버깅 수단입니다. 브리지 호출 기록·수동 호출·환경 지문은 브리지 패널에서 확인하세요. 패널 전체는 패널 레퍼런스에 있습니다.
report
리포트 패널이 캡처한 세션 JSON을 사내(폐쇄망) 수집 URL로 POST하는 옵트인 전송 설정입니다. 이 URL은 네트워크 추적에서 자동으로 제외됩니다.
ts
report: {
endpoint: '/internal/debug-reports',
allowHosts: ['reports.corp.example'], // 전송 허용 호스트(필수) — 비우면 전송 비활성
redact: true, // 캡처 시 민감정보 마스킹(기본 false, 옵트인)
}| 필드 | 동작 |
|---|---|
endpoint | 리포트를 POST할 사내 수집 URL |
allowHosts | 전송을 허용할 호스트 화이트리스트. 이 목록에 endpoint 호스트가 있을 때만 전송됩니다. 비어 있으면 '전송' 버튼이 생기지 않고 sendReport도 차단합니다(외부 SaaS로의 우발 유출 방지). 상대경로 endpoint면 현재 호스트를 명시하세요. |
redact | true면 캡처 시 **쿠키 값 전체 · token/secret/password/auth류 키 · JWT 형태 값 · 네트워크 요청/응답 민감 헤더(Authorization·Cookie·Set-Cookie)**를 ***로 마스킹합니다. 같은 기준이 데이터의 평행 복사본에도 적용됩니다(0.17.1) — Pinia 뮤테이션 before/after 스냅샷·액션 인자·타임라인 detail·URL의 민감 쿼리(?token=…)·선택 컴포넌트 값 미리보기. 기본 false(디버깅 가시성 보존) — 공유·전송 전 켜는 옵트인 안전장치. |
외부 SaaS로 전송 금지
리포트에는 쿠키·스토리지·API 응답이 담길 수 있습니다. allowHosts는 반드시 사내 폐쇄망 호스트만 담으세요. 복사·파일 저장 경로에도 리포트 패널이 민감정보 포함 경고를 표시합니다. 금융권 등 전체 데이터 거버넌스·노출 표면은 보안·누출 차단 을 보세요.
마스킹의 한계 — 본문 PII는 못 막습니다
report.redact는 키 이름 패턴 기반이라, 응답 본문 속 일반 키의 PII/PCI(계좌·카드·잔액·주민번호)나 라우터 가드 함수 소스는 마스킹하지 못합니다. 또 라이브 네트워크 화면의 헤더(Authorization·쿠키)는 평문 표시되니 화면 공유에 주의하세요(리포트 반출 시에만 마스킹). 검증계는 가명/테스트 데이터를 우선하세요.
전체 옵션 예시
모든 옵션을 함께 쓴 예시입니다. 실제로는 이 중 필요한 항목만 지정하면 됩니다.
ts
attachDebugger(app, {
// 자동 발견 실패 시에만 (보통 불필요)
pinia,
router,
maxEntries: 500, // 종류별 기록 버퍼 상한 (기본 500)
hotkey: true, // Alt+Shift+D 토글 (기본 true)
ignoreStorageKeys: /^__vdbg/, // 스토리지 로그에서 무시할 키
// Android @JavascriptInterface 커스텀 브리지 이름들
// (iOS·RN·Flutter는 자동 감지)
bridges: ['AndroidBridge', 'NativeApp'],
// 네트워크 추적 제외 — 문자열은 부분일치, RegExp는 정규식
ignoreUrls: ['/apm', /datadog|newrelic|sentry/, '/telemetry', '/heartbeat'],
// 리포트 옵트인 전송 — 사내(폐쇄망) URL로만
report: {
endpoint: '/internal/debug-reports',
allowHosts: ['reports.corp.example'], // 전송 허용 호스트(필수)
redact: true, // 민감정보 마스킹(옵트인)
},
})DebuggerHandle (반환 핸들)
attachDebugger가 브라우저 환경에서 반환하는 제어 핸들입니다. 디버거를 프로그래밍적으로 열고 닫거나 완전히 제거할 수 있습니다.
ts
interface DebuggerHandle {
/** 디버거 버전 (예: '0.17.0') */
version: string
open(): void
close(): void
toggle(): void
destroy(): void
}| 멤버 | 설명 |
|---|---|
version | 부착된 디버거의 버전 문자열 |
open() | 패널을 엽니다 |
close() | 패널을 닫습니다 |
toggle() | 패널 열림/닫힘을 전환합니다 |
destroy() | UI를 내리고 모든 감시를 해제한 뒤 window.__VUE_DEBUGGER__를 삭제합니다 |
window.__VUE_DEBUGGER__
부착 후에는 동일한 핸들을 전역에서도 쓸 수 있습니다. 콘솔에서 빠르게 조작하거나 다른 코드에서 참조할 때 유용합니다.
ts
declare global {
interface Window {
__VUE_DEBUGGER__?: DebuggerHandle
}
}ts
window.__VUE_DEBUGGER__?.open()
window.__VUE_DEBUGGER__?.version // 부착된 디버거 버전null 분기 처리
비브라우저 환경에서 attachDebugger는 null을 반환하고 window.__VUE_DEBUGGER__도 설정되지 않습니다. 반환값과 전역 모두 옵셔널로 다루세요.
부가 export
진입점 외에 다음을 함께 export합니다.
traceGuards(router)
ts
function traceGuards(router: RouterLike): void앱 부트스트랩에서 전역 가드를 등록하기 전에 한 번 호출하면, 그 뒤 등록되는 모든 전역 beforeEach/beforeResolve가 감싸져 '어느 가드가 막았나'가 Router 패널에 귀속됩니다.
attachDebugger는 보통 앱 셋업 이후에 호출되므로, 그 이전에 등록된 전역 가드는 잡지 못합니다(vue-router가 내부 가드 리스트를 비공개로 둠). 이 한 줄이 그 한계를 메웁니다. 라우트별 beforeEnter는 디버거가 attach 시 자동으로 잡으므로 이 호출이 필요 없습니다.
traceGuards는 가드 등록 직전에 동기적으로 호출돼야 하므로, 동적 import()가 아니라 정적 import를 씁니다. 프로덕션 분기가 false가 되면 호출이 제거되고, sideEffects:false 덕에 import도 통째로 트리셰이킹됩니다. 자세한 레시피와 이유는 라우터 가드 추적을 보세요.
traceGuards는 멱등입니다 — HMR로 router.ts가 다시 평가돼 두 번 불려도 이미 감싼 위에 또 감싸지 않습니다.
untraceGuards(router)
ts
function untraceGuards(router: RouterLike): voidtraceGuards가 감싼 전역 beforeEach/beforeResolve를 원본으로 되돌립니다. traceGuards는 attachDebugger 생명주기 밖(부트스트랩)에서 호출되어 destroy()로 자동 원복되지 않으므로, 테스트 정리나 명시적 종료 시 이 한 줄로 호스트 라우터를 깨끗이 되돌릴 수 있습니다. 이미 원복 상태면 무해한 no-op입니다.
traceAxios(instance) · untraceAxios(instance)
ts
function traceAxios(instance: AxiosLike): () => void // 반환값 = 추적 해제 함수
function untraceAxios(instance: AxiosLike): voidaxios 인스턴스를 만든 직후 traceAxios(instance)를 한 번 부르면, 그 인스턴스의 요청에 호출 위치(onMounted부터의 호출 체인) 가 네트워크 패널에 복원됩니다. axios는 내부 비동기 경계(.then(dispatchRequest))로 전송 시점엔 호출자 체인을 잃는데, traceAxios는 사용자가 메서드를 부르는 순간 스택을 잡아 그 한계를 메웁니다.
동작 무영향(검증됨) — 순수 pass-through라 인자·반환·전송 바이트를 안 바꾸고, 호출 위치와 네트워크 항목의 연결은 헤더 무주입(method+URL+시간창 매칭)이라 요청 서명(HMAC) API도 안전합니다. 안 켜면 0 비용, 멱등(HMR 안전), untraceAxios나 반환된 함수로 원복합니다. 자세한 레시피·검증은 axios 호출 위치 추적을 보세요.
VERSION
ts
const VERSION: string디버거 패키지 버전 문자열입니다. package.json에서 빌드 시 인라인되며, DebuggerHandle.version과 동일한 값입니다.
ts
import { VERSION } from '@platform/vue-debugger'타입 export
다음 타입을 함께 export합니다 — DebuggerOptions, PiniaLike, RouterLike, AxiosLike, DebuggerController. PiniaLike·RouterLike·AxiosLike는 이 패키지가 pinia·vue-router·axios를 import하지 않고도 실제 인스턴스를 받기 위한 구조적 타입입니다(AxiosLike는 traceAxios 시그니처에 등장). 내부 동작 원리는 아키텍처에서 다룹니다.