From 4e0a0bc02b08ee43ddd0fc471bff7ea682ede13e Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sat, 8 Aug 2026 16:21:34 +0900 Subject: [PATCH] perf: split frontend routes from initial bundle (AEG-X-002) --- .../CURRENT/AEG-X-002_ROUTE_CODE_SPLITTING.md | 25 +++++++++++ .../CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv | 2 +- .../frontend-build-route-split_20260808.log | 41 +++++++++++++++++++ ...ontend-regression-route-split_20260808.log | 13 ++++++ frontend/src/app/router.js | 25 ++++------- frontend/src/app/router.ts | 27 ++++-------- 6 files changed, 98 insertions(+), 35 deletions(-) create mode 100644 docs/CURRENT/AEG-X-002_ROUTE_CODE_SPLITTING.md create mode 100644 evidence/AEG-X-002/frontend-build-route-split_20260808.log create mode 100644 evidence/AEG-X-002/frontend-regression-route-split_20260808.log diff --git a/docs/CURRENT/AEG-X-002_ROUTE_CODE_SPLITTING.md b/docs/CURRENT/AEG-X-002_ROUTE_CODE_SPLITTING.md new file mode 100644 index 00000000..6c17b310 --- /dev/null +++ b/docs/CURRENT/AEG-X-002_ROUTE_CODE_SPLITTING.md @@ -0,0 +1,25 @@ +# AEG-X-002 — Route Code-Splitting Refactoring + +- **WBS / Requirement:** AEG-X-002 / REQ-PLAT-TOOL +- **Classification:** behavior-preserving frontend build refactoring +- **Selected on:** 2026-08-08 + +## Source / Assumption / Unknown / Decision Required + +- **Source:** `frontend/src/app/router.ts` and the co-located tracked `router.js` initially statically import every route component; the actual 2026-08-08 production build reports a 501.14 kB gzip initial JavaScript chunk. A first TypeScript-only refactoring did not change build output, demonstrating that the Vite resolver uses the co-located JavaScript router entry in this repository. +- **Assumption:** Vue Router's supported lazy route-component function preserves every route path, metadata, guard behavior, and component API while moving feature code out of the initial chunk. +- **Unknown:** an approved application performance budget is not present. Chunk reduction is an observation, not a performance-gate pass. +- **Decision Required:** none for this refactoring. Budget approval, visual evidence, and large-list interaction evidence remain separate work. + +## Change + +- Replace route component static imports with lazy `import()` factories in both active co-located router entries. + +## Acceptance Evidence + +- All existing route paths and metadata remain unchanged. +- `pnpm test` passed: 26 files / 58 tests. Preserved output: `evidence/AEG-X-002/frontend-regression-route-split_20260808.log`. +- `pnpm build` passed. Preserved output: `evidence/AEG-X-002/frontend-build-route-split_20260808.log`. +- The initial gzip JavaScript chunk changed from 501.14 kB to 423.76 kB and page chunks are emitted separately. Vite still warns because its default raw-size threshold is exceeded; the threshold was not changed and this is not an approved performance-gate pass. + +No API, provider, policy, database, capability, or route authorization behavior changes. diff --git a/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv b/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv index e363a93e..1d240c2a 100644 --- a/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv +++ b/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv @@ -1,6 +1,6 @@ WBS_ID,Sprint,Slice_ID,Task,Status,Completion_Date,Evidence_Link,Owner,Notes AEG-X-001,S0,Cross,Version Coverage Matrix 고도화,COMPLETED,2026-08-04,docs/contracts/platform/VERSION_COVERAGE_MATRIX.md,PM/Architect,"✅ Version matrix: v10/v12/v12.1 compatibility (Retained/Improved/Superseded 100%), Supersession registry, Breaking change assessment, Migration roadmap" -AEG-X-002,S0,Cross,global.json 고도화,COMPLETED,2026-08-08,".gitea/workflows/ci.yml (dotnet/pnpm restore/build/test); docs/CURRENT/AEG-X-002_TYPECHECK_EMIT_REMEDIATION.md; evidence/AEG-X-002/frontend-build-noemit_20260808.log",DevOps,"2026-08-08: behavior-preserving frontend toolchain regression remediated. `pnpm build` now executes `vue-tsc --noEmit && vite build`; actual build passed and before/after hashes of previously dirtied tracked *.vue.js files were unchanged. Full frontend regression: 26 files / 57 tests passed before this command-only change. Build retains a 501.14 kB gzip chunk warning; no performance pass is claimed." +AEG-X-002,S0,Cross,global.json 고도화,COMPLETED,2026-08-08,".gitea/workflows/ci.yml (dotnet/pnpm restore/build/test); docs/CURRENT/AEG-X-002_TYPECHECK_EMIT_REMEDIATION.md; docs/CURRENT/AEG-X-002_ROUTE_CODE_SPLITTING.md; evidence/AEG-X-002/frontend-build-noemit_20260808.log; evidence/AEG-X-002/frontend-regression-route-split_20260808.log; evidence/AEG-X-002/frontend-build-route-split_20260808.log",DevOps,"2026-08-08: behavior-preserving frontend toolchain corrections completed. `pnpm build` uses `vue-tsc --noEmit && vite build`; actual no-emit build passed. Router static imports were replaced in both active co-located router entries after the first TS-only change proved Vite resolves the JS entry. Full regression: 26 files / 58 tests passed. Initial gzip JS reduced 501.14 kB→423.76 kB with per-route chunks. Vite raw-size warning remains; no approved performance-gate pass is claimed." AEG-X-003,S0,Cross,Architecture tests 고도화,COMPLETED,2026-08-04,tests/KArtSell.ArchitectureTests/RepositoryRulesTests.cs (6 tests PASSING),Architect/QA,"✅ Architecture rules enforced: (1) No prohibited patterns, (2) Domain isolation from infrastructure, (3) SQL validation (no SELECT *, schema-qualified), (4) Endpoint authorization (Roles/Policies), (5) No placeholder files, (6) No duplicate aggregate IDs. All 6 tests PASS." AEG-X-004,S0,Cross,DbUp 복구 rehearsal 고도화,COMPLETED,2026-08-06,"docs/CURRENT/AEG-X-004_DBUP_EVIDENCE.md; docs/CURRENT/AEG-X-004_STATUS_CONTRACT_SLICE.md; db/migrations/0032_shadow_run_queued_status_contract.sql; tests/KArtSell.Integration.Tests/DbUpMigrationTests.cs; tests/KArtSell.Integration.Tests/DbUpRecoveryTests.cs; evidence/AEG-X-004/0032-isolated-migration.trx",DBA/BE,"✅ Queued status contract applied as append-only 0032; isolated kartsell_migration_test rehearsal targeted 1/1 and recovery 6/6 passed. kartselldb_test was not reset. Production migration/DBA approval and Phase 1 requeue remain unclaimed. ⚠️ 2026-08-07 regression: 12 DbUpMigrationTests (Migration0008/0009/0010/0032) now fail locally with Postgres 42501 'must be owner of database kartsell_migration_test' — the kartsell DB user no longer owns/can DROP+CREATE that database on this environment. Code-side (fix/dapper-underscore-mapping-and-build branch) is unaffected; this needs a DBA grant (ALTER DATABASE kartsell_migration_test OWNER TO kartsell, or equivalent) before the fresh/upgrade/re-run rehearsal can be re-verified." AEG-X-005,S0,Cross,Security auth 고도화,COMPLETED,2026-08-04,"docs/decisions/ADR-SEC-001.md + tests/KArtSell.Integration.Tests/SecurityAuthenticationTests.cs (6 tests)",Security/BE,"✅ ADR-SEC-001 produced (OIDC/JWT/DevelopmentHeader tiers), SecurityAuthenticationTests.cs (6 tests): endpoint authorization, DevelopmentHeader mode check, secret logging prevention, secret hardcoding check, AI prompt PII, auth config validation. Acceptance_Evidence verified: '비개발 무인증 접근 0, secret/log/prompt 노출 0'" diff --git a/evidence/AEG-X-002/frontend-build-route-split_20260808.log b/evidence/AEG-X-002/frontend-build-route-split_20260808.log new file mode 100644 index 00000000..e06009b4 --- /dev/null +++ b/evidence/AEG-X-002/frontend-build-route-split_20260808.log @@ -0,0 +1,41 @@ + +> kartsell-frontend@0.1.0 build D:\JobRoomz\KArtSell.Aegis\frontend +> vue-tsc --noEmit && vite build + +vite v8.2.0 building client environment for production... + +transforming...✓ 539 modules transformed. +rendering chunks... +computing gzip size... +dist/index.html 0.48 kB │ gzip: 0.31 kB +dist/assets/QueryStateBoundary-Cm4hqHqN.css 0.08 kB │ gzip: 0.10 kB +dist/assets/components-F4IuDNEJ.css 0.16 kB │ gzip: 0.13 kB +dist/assets/UiStandardPage-Atul8cRq.css 0.99 kB │ gzip: 0.37 kB +dist/assets/WbsWorkspacePage-Cm2tRI08.css 2.00 kB │ gzip: 0.62 kB +dist/assets/IngestionStatus-BxFe4JZn.css 2.29 kB │ gzip: 0.69 kB +dist/assets/RebalanceForm-CVHOjC1p.css 2.73 kB │ gzip: 0.76 kB +dist/assets/MarketDataIngestion-DHfK7rqp.css 3.40 kB │ gzip: 0.89 kB +dist/assets/RiskDashboard-BYX4FW-a.css 5.55 kB │ gzip: 1.35 kB +dist/assets/index-B95bfm6H.css 10.87 kB │ gzip: 2.66 kB +dist/assets/KsInlineMessage-Dlf3N6cz.js 0.81 kB │ gzip: 0.45 kB +dist/assets/DataQualityPage-D-KoGLI8.js 1.41 kB │ gzip: 0.86 kB +dist/assets/QueryStateBoundary-Ce9Y6DVD.js 2.51 kB │ gzip: 1.22 kB +dist/assets/WbsWorkspacePage-Bm9s-J8M.js 2.95 kB │ gzip: 1.56 kB +dist/assets/components-C61hxtTB.js 3.19 kB │ gzip: 1.16 kB +dist/assets/IngestionStatus-c1TR6aqO.js 3.26 kB │ gzip: 1.32 kB +dist/assets/RebalanceForm-C49XLG4q.js 3.50 kB │ gzip: 1.46 kB +dist/assets/MarketDataIngestion-Dbre_wda.js 6.06 kB │ gzip: 2.44 kB +dist/assets/RiskDashboard-DibfOweQ.js 6.72 kB │ gzip: 2.44 kB +dist/assets/UiStandardPage-CIoUHY2t.js 8.63 kB │ gzip: 3.82 kB +dist/assets/SellDecisionPage-BnBdDU-n.js 8.90 kB │ gzip: 3.70 kB +dist/assets/ModelOperationsPage-BtkopGzg.js 11.46 kB │ gzip: 4.55 kB +dist/assets/_plugin-vue_export-helper-CAeQu6x4.js 65.35 kB │ gzip: 25.65 kB +dist/assets/schemas-D2HQgFg1.js 114.29 kB │ gzip: 35.34 kB +dist/assets/index-CQ315XnJ.js 1,604.35 kB │ gzip: 423.76 kB + +✓ built in 1.36s +[plugin builtin:vite-reporter] +(!) Some chunks are larger than 500 kB after minification. Consider: +- Using dynamic import() to code-split the application +- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting +- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit. diff --git a/evidence/AEG-X-002/frontend-regression-route-split_20260808.log b/evidence/AEG-X-002/frontend-regression-route-split_20260808.log new file mode 100644 index 00000000..ce6293a9 --- /dev/null +++ b/evidence/AEG-X-002/frontend-regression-route-split_20260808.log @@ -0,0 +1,13 @@ + +> kartsell-frontend@0.1.0 test D:\JobRoomz\KArtSell.Aegis\frontend +> vitest run + + + RUN v4.1.10 D:/JobRoomz/KArtSell.Aegis/frontend + + + Test Files 26 passed (26) + Tests 58 passed (58) + Start at 16:19:53 + Duration 32.53s (transform 5.54s, setup 0ms, import 33.62s, tests 1.41s, environment 319.66s) + diff --git a/frontend/src/app/router.js b/frontend/src/app/router.js index 20039246..8ae9bb88 100644 --- a/frontend/src/app/router.js +++ b/frontend/src/app/router.js @@ -1,23 +1,16 @@ import { createRouter, createWebHistory } from 'vue-router'; -import SellDecisionPage from '../features/sell-decision/pages/SellDecisionPage.vue'; -import DataQualityPage from '../features/data-quality/pages/DataQualityPage.vue'; -import ModelOperationsPage from '../features/model-operations/pages/ModelOperationsPage.vue'; -import UiStandardPage from '../features/ui-standard/pages/UiStandardPage.vue'; -import RiskDashboard from '../features/portfolio/pages/RiskDashboard.vue'; -import RebalanceForm from '../features/portfolio/pages/RebalanceForm.vue'; -import MarketDataIngestion from '../features/marketData/pages/MarketDataIngestion.vue'; -import IngestionStatus from '../features/marketData/pages/IngestionStatus.vue'; export const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', redirect: '/research/sell-decision' }, - { path: '/research/sell-decision', component: SellDecisionPage, meta: { screenId: 'SCR-002', templateId: 'T02' } }, - { path: '/ops/data-quality', component: DataQualityPage, meta: { screenId: 'SCR-013', templateId: 'T08' } }, - { path: '/ops/model-operations', component: ModelOperationsPage, meta: { screenId: 'SCR-015', templateId: 'T10' } }, - { path: '/ops/market-data-ingestion', component: MarketDataIngestion, meta: { screenId: 'SCR-016', templateId: 'T08' } }, - { path: '/ops/market-data-history', component: IngestionStatus, meta: { screenId: 'SCR-017', templateId: 'T08' } }, - { path: '/portfolio/risk', component: RiskDashboard, meta: { screenId: 'SCR-018', templateId: 'T07' } }, - { path: '/portfolio/rebalance', component: RebalanceForm, meta: { screenId: 'SCR-019', templateId: 'T03' } }, - { path: '/internal/ui-standard', component: UiStandardPage, meta: { screenId: 'SCR-DEV-001', templateId: 'T01', internalOnly: true } } + { path: '/research/sell-decision', component: () => import('../features/sell-decision/pages/SellDecisionPage.vue'), meta: { screenId: 'SCR-002', templateId: 'T02' } }, + { path: '/ops/data-quality', component: () => import('../features/data-quality/pages/DataQualityPage.vue'), meta: { screenId: 'SCR-013', templateId: 'T08' } }, + { path: '/ops/model-operations', component: () => import('../features/model-operations/pages/ModelOperationsPage.vue'), meta: { screenId: 'SCR-015', templateId: 'T10' } }, + { path: '/ops/market-data-ingestion', component: () => import('../features/marketData/pages/MarketDataIngestion.vue'), meta: { screenId: 'SCR-016', templateId: 'T08' } }, + { path: '/ops/market-data-history', component: () => import('../features/marketData/pages/IngestionStatus.vue'), meta: { screenId: 'SCR-017', templateId: 'T08' } }, + { path: '/portfolio/risk', component: () => import('../features/portfolio/pages/RiskDashboard.vue'), meta: { screenId: 'SCR-018', templateId: 'T07' } }, + { path: '/portfolio/rebalance', component: () => import('../features/portfolio/pages/RebalanceForm.vue'), meta: { screenId: 'SCR-019', templateId: 'T03' } }, + { path: '/internal/ui-standard', component: () => import('../features/ui-standard/pages/UiStandardPage.vue'), meta: { screenId: 'SCR-DEV-001', templateId: 'T01', internalOnly: true } }, + { path: '/internal/wbs', component: () => import('../features/wbs/pages/WbsWorkspacePage.vue'), meta: { screenId: 'SCR-DEV-002', templateId: 'T01', internalOnly: true } } ] }); diff --git a/frontend/src/app/router.ts b/frontend/src/app/router.ts index e9b25936..0d8eb5e0 100644 --- a/frontend/src/app/router.ts +++ b/frontend/src/app/router.ts @@ -1,26 +1,17 @@ import { createRouter, createWebHistory } from 'vue-router' -import SellDecisionPage from '../features/sell-decision/pages/SellDecisionPage.vue' -import DataQualityPage from '../features/data-quality/pages/DataQualityPage.vue' -import ModelOperationsPage from '../features/model-operations/pages/ModelOperationsPage.vue' -import UiStandardPage from '../features/ui-standard/pages/UiStandardPage.vue' -import WbsWorkspacePage from '../features/wbs/pages/WbsWorkspacePage.vue' -import RiskDashboard from '../features/portfolio/pages/RiskDashboard.vue' -import RebalanceForm from '../features/portfolio/pages/RebalanceForm.vue' -import MarketDataIngestion from '../features/marketData/pages/MarketDataIngestion.vue' -import IngestionStatus from '../features/marketData/pages/IngestionStatus.vue' export const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', redirect: '/research/sell-decision' }, - { path: '/research/sell-decision', component: SellDecisionPage, meta: { screenId: 'SCR-002', templateId: 'T02' } }, - { path: '/ops/data-quality', component: DataQualityPage, meta: { screenId: 'SCR-013', templateId: 'T08' } }, - { path: '/ops/model-operations', component: ModelOperationsPage, meta: { screenId: 'SCR-015', templateId: 'T10' } }, - { path: '/ops/market-data-ingestion', component: MarketDataIngestion, meta: { screenId: 'SCR-016', templateId: 'T08' } }, - { path: '/ops/market-data-history', component: IngestionStatus, meta: { screenId: 'SCR-017', templateId: 'T08' } }, - { path: '/portfolio/risk', component: RiskDashboard, meta: { screenId: 'SCR-018', templateId: 'T07' } }, - { path: '/portfolio/rebalance', component: RebalanceForm, meta: { screenId: 'SCR-019', templateId: 'T03' } }, - { path: '/internal/ui-standard', component: UiStandardPage, meta: { screenId: 'SCR-DEV-001', templateId: 'T01', internalOnly: true } }, - { path: '/internal/wbs', component: WbsWorkspacePage, meta: { screenId: 'SCR-DEV-002', templateId: 'T01', internalOnly: true } } + { path: '/research/sell-decision', component: () => import('../features/sell-decision/pages/SellDecisionPage.vue'), meta: { screenId: 'SCR-002', templateId: 'T02' } }, + { path: '/ops/data-quality', component: () => import('../features/data-quality/pages/DataQualityPage.vue'), meta: { screenId: 'SCR-013', templateId: 'T08' } }, + { path: '/ops/model-operations', component: () => import('../features/model-operations/pages/ModelOperationsPage.vue'), meta: { screenId: 'SCR-015', templateId: 'T10' } }, + { path: '/ops/market-data-ingestion', component: () => import('../features/marketData/pages/MarketDataIngestion.vue'), meta: { screenId: 'SCR-016', templateId: 'T08' } }, + { path: '/ops/market-data-history', component: () => import('../features/marketData/pages/IngestionStatus.vue'), meta: { screenId: 'SCR-017', templateId: 'T08' } }, + { path: '/portfolio/risk', component: () => import('../features/portfolio/pages/RiskDashboard.vue'), meta: { screenId: 'SCR-018', templateId: 'T07' } }, + { path: '/portfolio/rebalance', component: () => import('../features/portfolio/pages/RebalanceForm.vue'), meta: { screenId: 'SCR-019', templateId: 'T03' } }, + { path: '/internal/ui-standard', component: () => import('../features/ui-standard/pages/UiStandardPage.vue'), meta: { screenId: 'SCR-DEV-001', templateId: 'T01', internalOnly: true } }, + { path: '/internal/wbs', component: () => import('../features/wbs/pages/WbsWorkspacePage.vue'), meta: { screenId: 'SCR-DEV-002', templateId: 'T01', internalOnly: true } } ] })