기능별 코드 로딩과 상태 보존 — U05
앱 #1536, 대상 release/v0.18.0. 앱 PR #1565의 Merge Check 성공 후 release/v0.18.0에 병합했다(2cfeac5327d5f3eb8eeb2ce7cb4e5e709127f6d5). staging·Production 반영은 별도다.
측정 경계
scripts/performance/measure/feature-graph.mjs는 실제 Vite 설정으로 빌드하면서 chunk별 정적/동적 import, 포함 모듈, 원본/gzip 바이트를 남긴다. 청크 이름은 빌드 도구가 공유 모듈 중에서 고른 이름이다. HostFrames라는 이름이 그 청크 전체가 호스트 DOM 코드라는 뜻은 아니다.
scripts/performance/measure/feature-loading.mjs는 G04 history-1y 생성기와 공식 종목 ID 해석, 기존 인증·온보딩·통계 소진 도구를 사용한다. 격리 로컬 DB의 일회용 계정에 260세션을 넣고 종료 시 계정을 삭제한다. 실제 앱 production-format build를 Vite preview로 열며 모바일390×844와 데스크톱1440×1000, Chromium CPU4배, 각 cold/warm3회다. 원격 Production이나 실제 휴대폰 결과가 아니다.
- cold는 브라우저 HTTP 캐시를 비운 새 context다. warm은 같은 context에서 새 문서를 연다. boot에는 홈 준비 뒤4초 유휴 요청도 포함하므로 기존 전체 화면 선로딩을 숨기지 않는다.
- 실제 전송 바이트와 요청한 파일의 gzip 합계를 별도 기록한다. Vite preview의 인코딩과 CDN 배포 인코딩이 같다고 가정하지 않는다. warm 캐시 파일의 전송0과 파일 자체 크기를 구분한다.
- CDP ScriptDuration은 파싱만 분리한 값이 아니라 JavaScript 실행 구간 지표다. TaskDuration 및 Navigation/Resource Timing 원본을 함께 제공한다.
- 기능 최초 방문·재방문은 실제 탭바/드로어/사이드바를 누른다. 전환 페인트와 추가 요청은 초기 요청과 별도로 기록한다. raw RPC URL/query·토큰·사용자 원문은 보고서에 넣지 않는다.
- G04의 홈 준비1448ms·load456ms·script601434bytes·DOM294 상한은 그대로 적용하며 조건 차이와 기존 초과를 숨기지 않는다. G04에는 기능 전환의 별도 수치 상한이 없으므로 홈 예산을 임의의 전환 합격선으로 바꾸지 않는다.
시작 구조와 보존 조건
폰트 보충 계측: 2026-09-11 같은 앱/폰트 자산의 CDP Network.responseReceived/loadingFinished에서 WantedSansVariable.woff2 HTTP200, diskCache=false, encodedDataLength1290206bytes를 확인했다. document.fonts도 loaded이며 실패 요청0이다. Resource Timing 목록만으로 폰트 비용을 집계하면 누락된다. 인증 전 화면에서 동일 글꼴 로드를 분리 관측한 보충 자료이며, 기존12회 표본 각각의 폰트 전송값을 소급 합성하지 않는다.
첫 기준선: 브라우저 원본, 빌드 그래프. 전체 JS89청크의 gzip합계는793154bytes이며 실제 홈 요청과 구분한다. 관리자 모듈은0개다. 2026-09-11 Chromium151/Node24.16.0/Windows, G04 workload digest 36db8842f1c8b1c7b41904319b8497d63c2cbc208d50ed4b04756921e64a6fce.
| 기준선 | 모바일 cold / warm | 데스크톱 cold / warm |
|---|---|---|
| 홈 준비 중앙값 | 979 / 1253ms | 1117 / 1258ms |
| load 중앙값 | 269.7 / 130.5ms | 238.1 / 116.4ms |
| 실제 boot script 요청 수 | 55 / 55 | 23 / 23 |
| 요청한 script gzip합계 | 651299 / 651299bytes | 499996 / 499996bytes |
| 실제 script 전송량(첫 표본) | 669839 / 16500bytes | 507544 / 6900bytes |
| ScriptDuration 중앙값 | 879.239 / 1653.073ms | 840.155 / 1493.714ms |
| 홈 DOM(첫 표본) | 342 / 342 | 1757 / 1757 |
모바일 cold script는 G04의601434bytes 상한을 넘고, 양 플랫폼 DOM도294보다 크다. G04 DOM의 원래 조건은 모바일이므로 데스크톱에는 비교값으로만 기록한다. 수치를 새 기준으로 상향하지 않는다. ScriptDuration은 부팅4초간 백그라운드 작업도 포함하며 warm이 언제나 작다는 가정은 하지 않는다. 글꼴 CSS 요청649bytes가 관측됐으나 woff 자원 요청은 기록되지 않았다. 폰트 바이트0을 정상 글꼴 로딩 완료나 개선으로 해석하지 않는다.
기준 앱 517c538e는 A15·U04·A16을 포함한다. mobile root는 마운트 시 전체 화면 선로딩을 예약한다. desktop root는 모든 feature binding을 정적 import하고 마운트하며, binding 내부 화면만 일부 lazy다. 공용 chunk의 owner store/runtime·원자 outbox·인증/전송 계약은 코드량만 줄이기 위해 다시 생성하거나 분할하지 않는다.
관리자 진입점은 Barbelic-docs의 독립 빌드다. 앱 그래프에서 관리자 UI·adminComposition·adminRepository가 도달하지 않는지 빌드 모듈 목록으로 확인한다. #1478의 관리자·와드업 CASE/단위/컴포넌트/DB 제외를 복귀시키지 않는다.
기존 A16 경계, U04 목록 identity/스크롤, 탐색 스택, 네이티브 bridge, 소유자 생애주기를 유지한다. route와 별개로 owner가 보존하는 초안·미전송 저장의 확정 의미는 바꾸지 않는다.
첫 방문과 재방문
- 모바일의 기존 화면별 dynamic import와
MobileTabFramevisitedTabs를 유지하고 전체 화면을 유휴 시간에 가져오던warmMobileScreenChunks를 삭제한다. Home은 정적이며 owner provider·알림·초안 복원·연결 상태는 원래 소유자에 남는다. RPC 데이터 선로딩은 바꾸지 않는다. - 작은 feature binding까지 별도 dynamic import한 중간 후보는 전체 JS89→135청크, 실제 mobile boot55→64요청, desktop23→50요청을 만들었다. 모바일은 바이트가 줄어도 요청 수가 늘었고 desktop 바이트도 늘었다. 최종 구현은 연결 코드는 함께 두고 무거운 화면 모듈만 기존 경계에서 분리한다. Vite 설정·vendor 묶음·package/lock을 변경하지 않는다.
- 데스크톱은 달력·로그표·PR·리포트·검색·프로필 binding을 첫 방문 때 마운트한다. 방문한 binding은 유지하고 기존 active-tab 조건이 표시를 소유한다. 즐겨찾기 그룹 provider는 리포트도 소비하므로 공용으로 유지한다.
- mobile report/search 제어 상태와 Home 상세/뒤로가기 조립은 기존 소유를 유지한다. import를 옮기려 별도 저장소나 등록기를 만들지 않는다.
featureLoadingGraph.test.mjs는 실제 build에서 모바일 화면4개·데스크톱 화면5개가 정적 홈 경로에 없고 동적 도달 가능한지 검사한다.feature-loading.mjs의U05_GRAPH옵션은 실제 idle boot 요청을 같은 graph의 화면 모듈과 대조하므로 eager prefetch도 검출한다. 기존 기준선은 모바일6/6표본에서 이 요청 검사를 위반했다. 기존 Home eager·보조화면 lazy·데이터 중복 방지 검사는 유지한다.
청크 실패와 입력 보존
탐색 경로의 FeatureLoadBoundary와 운동 화면의 lazyFeature는 기존 React.lazy와 오류 경계만 조합한다. 기존 owner provider 밖에서 새 상태나 로더 레지스트리를 만들지 않는다. 실패한 기능에 안내를 표시하고 나머지 탐색은 유지한다. 방문한 binding을 유지하므로 데스크톱의 비활성 기능 오류는 숨긴다.
앱이 입력 가능한 상태가 된 뒤에는 기존 Vite preload 복구의 자동 reload를 중단하고 오류를 기능 경계로 전달한다. 최초 문서 부팅의 1회 복구와 영속 guard는 유지한다. 브라우저가 실패한 ES module 주소도 기억하므로 같은 URL을 반복 import하는 버튼이나 쿼리 우회 로더는 채택하지 않는다.
사용자의 앱 다시 열기 동작만 새 문서를 연다. 작성 중 운동은 현재 owner store의 flushLiveWorkoutDraftNow를 기다린다. 저장 실패·계정 변경·추가 입력·진행 중 저장·계획 편집이 있으면 현재 문서를 유지한다. 새 문서는 기존 운동 초안 복원 경로를 사용한다. 다른 화면의 입력은 먼저 저장하도록 안내한다. 모든 임시 폼의 자동 복원이나 서버 전송 완료를 약속하지 않는다. 저장 대기 outbox의 원문·확정 의미는 바꾸지 않는다.
tests/browser/u05의 production-format Vite/Chromium 8개 시나리오는 네트워크 단절·구형 asset 404 뒤 수동 재열기, 실패 반복 상한, 인증 펜스에 의한 보관 거절, 로드 대기 중 뒤로 이동, 실제 IndexedDB 쓰기 오류를 다룬다. 운동 snapshot과 실제 IDB outbox를 실패 전후/재열기 뒤 대조한다. 정상화 뒤 기능 재방문은 추가 요청 없이 동일 입력 상태를 유지한다. 기존 N01 SW/네이티브 lifecycle을 복제하지 않으며 HTTPS SW 교체와 실기기는 미실측이다.
최종 비교와 반영 상태
U05 작업 기록의 전후 표·G04 판정·플랫폼 미측정 범위가 최종 측정 결과다. 모바일 요청55→38, cold 전송669,839→566,048bytes. DOM317은 상한294를 넘는다. 앱 PR #1565의 Merge Check 성공 후 release/v0.18.0에 병합했다(2cfeac5327d5f3eb8eeb2ce7cb4e5e709127f6d5). staging·Production 반영은 별도다.
외부 글꼴 CSS의 실제 응답 지연2.747초가 cold home을 막은 것을 확인해 동일 font-face 선언만 index.html에 둔다. v1.0.3/woff2 URL/weight400~1000/swap은 유지하고, font 파일은 기존 CDN에서 받는다. 최종12회 CSS 요청0·font200을 확인했다.
R01 선로딩 보조 경로 퇴역 (2026-09-11)
U05 이후 제품 호출이 없는 warmChunksSuppressingRecovery·warmup depth·오류 은폐 분기를 제거했다. 기존 단일 boot 복구와 interactive feature 오류 전달은 유지하며, 옛 warmup 전용3건을 현재 interactive preload/resource 오류·상태 전환3건으로 대체했다. R01 검증·보존 조건을 따른다.