perf: lazy load selected UI provider (AEG-X-002)

This commit is contained in:
2026-08-08 16:29:36 +09:00
parent 4e0a0bc02b
commit 9665f7791f
9 changed files with 101 additions and 15 deletions
@@ -14,6 +14,7 @@
## Change
- Replace route component static imports with lazy `import()` factories in both active co-located router entries.
- Load only the selected, validated UI provider at bootstrap instead of statically bundling both provider implementations and their vendor dependencies.
## Acceptance Evidence
@@ -21,5 +22,7 @@
- `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.
- The selected-provider bootstrap refactoring is covered by `frontend/src/shared/ui/provider/tests/resolveUiProvider.spec.ts`: the native provider resolves and an unsupported provider rejects before mount. The final full regression result is 27 files / 60 tests, preserved in `evidence/AEG-X-002/frontend-regression-provider-lazy_20260808.log`.
- Final provider-lazy build evidence is `evidence/AEG-X-002/frontend-build-provider-lazy_20260808.log`. The app bootstrap chunk is 20.51 kB (7.35 kB gzip); the selected PrimeVue/AG Grid provider is a separate 1,515.61 kB (393.82 kB gzip) lazy chunk.
No API, provider, policy, database, capability, or route authorization behavior changes.
@@ -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; 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-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; evidence/AEG-X-002/frontend-regression-provider-lazy_20260808.log; evidence/AEG-X-002/frontend-build-provider-lazy_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. Route and provider static imports were replaced in both active co-located JS/TS entries after the first TS-only change proved Vite resolves JS. Full regression: 27 files / 60 tests passed. Route splitting reduced initial gzip JS 501.14 kB→423.76 kB; provider splitting leaves a 20.51 kB (7.35 kB gzip) bootstrap chunk and lazy-loads PrimeVue/AG Grid at 393.82 kB gzip. 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'"
1 WBS_ID Sprint Slice_ID Task Status Completion_Date Evidence_Link Owner Notes
2 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
3 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 .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; evidence/AEG-X-002/frontend-regression-provider-lazy_20260808.log; evidence/AEG-X-002/frontend-build-provider-lazy_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. 2026-08-08: behavior-preserving frontend toolchain corrections completed. `pnpm build` uses `vue-tsc --noEmit && vite build`; actual no-emit build passed. Route and provider static imports were replaced in both active co-located JS/TS entries after the first TS-only change proved Vite resolves JS. Full regression: 27 files / 60 tests passed. Route splitting reduced initial gzip JS 501.14 kB→423.76 kB; provider splitting leaves a 20.51 kB (7.35 kB gzip) bootstrap chunk and lazy-loads PrimeVue/AG Grid at 393.82 kB gzip. Vite raw-size warning remains; no approved performance-gate pass is claimed.
4 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.
5 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.
6 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'
@@ -0,0 +1,54 @@
> 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 1.17 kB │ gzip: 0.44 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/ReviewWorkbenchLayout-Dy9jBK5M.css 1.35 kB │ gzip: 0.49 kB
dist/assets/WbsWorkspacePage-Cm2tRI08.css 2.00 kB │ gzip: 0.62 kB
dist/assets/installNativeAdapter-DdlM1au5.css 2.12 kB │ gzip: 0.72 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/installPrimeVueAdapter-DHzNsA0_.css 3.10 kB │ gzip: 0.88 kB
dist/assets/MarketDataIngestion-DHfK7rqp.css 3.40 kB │ gzip: 0.89 kB
dist/assets/index-CXg8AE9B.css 4.35 kB │ gzip: 1.48 kB
dist/assets/RiskDashboard-BYX4FW-a.css 5.55 kB │ gzip: 1.35 kB
dist/assets/_plugin-vue_export-helper-BDNMzG2s.js 0.08 kB │ gzip: 0.09 kB
dist/assets/KsInlineMessage-Cl9GruP-.js 0.82 kB │ gzip: 0.45 kB
dist/assets/useUiAdapter-Dhcoyf0S.js 1.02 kB │ gzip: 0.56 kB
dist/assets/DataQualityPage-BNLXDsvH.js 1.41 kB │ gzip: 0.86 kB
dist/assets/ReviewWorkbenchLayout-CU1nbEwk.js 1.53 kB │ gzip: 0.69 kB
dist/assets/QueryStateBoundary-BJDsqvSq.js 2.57 kB │ gzip: 1.26 kB
dist/assets/WbsWorkspacePage-vvLumTSh.js 3.00 kB │ gzip: 1.59 kB
dist/assets/components-BwWzTICE.js 3.25 kB │ gzip: 1.19 kB
dist/assets/IngestionStatus-DoIuroly.js 3.32 kB │ gzip: 1.36 kB
dist/assets/mutation-DnUWhblf.js 3.37 kB │ gzip: 1.12 kB
dist/assets/RebalanceForm-CVsrIkQm.js 3.57 kB │ gzip: 1.49 kB
dist/assets/MarketDataIngestion-DqcgXQSp.js 6.17 kB │ gzip: 2.48 kB
dist/assets/RiskDashboard-mAl_aLxW.js 6.76 kB │ gzip: 2.46 kB
dist/assets/query-Chqfmdn2.js 7.60 kB │ gzip: 2.62 kB
dist/assets/utils-CZoax6su.js 8.26 kB │ gzip: 3.18 kB
dist/assets/UiStandardPage-DtwvCdVi.js 8.74 kB │ gzip: 3.87 kB
dist/assets/SellDecisionPage-BfNfgw8c.js 8.99 kB │ gzip: 3.72 kB
dist/assets/installNativeAdapter-CHx6h46v.js 10.41 kB │ gzip: 3.37 kB
dist/assets/ModelOperationsPage-BJPx3olr.js 11.49 kB │ gzip: 4.55 kB
dist/assets/runtime-dom.esm-bundler-oBbrWYFJ.js 13.12 kB │ gzip: 5.68 kB
dist/assets/index-CUlBkX9u.js 20.51 kB │ gzip: 7.35 kB
dist/assets/vue-router-CHRd7fwn.js 24.73 kB │ gzip: 9.56 kB
dist/assets/runtime-core.esm-bundler-DJThmTxA.js 65.27 kB │ gzip: 25.63 kB
dist/assets/schemas-CaGf3VsS.js 114.28 kB │ gzip: 35.35 kB
dist/assets/installPrimeVueAdapter-BTITH96X.js 1,515.61 kB │ gzip: 393.82 kB
✓ built in 1.33s
[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.
@@ -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 27 passed (27)
Tests 60 passed (60)
Start at 16:28:00
Duration 34.12s (transform 5.55s, setup 0ms, import 32.47s, tests 2.01s, environment 354.65s)
+1 -1
View File
@@ -10,5 +10,5 @@ const app = createApp(App);
app.use(createPinia());
app.use(router);
app.use(VueQueryPlugin, { queryClient });
resolveUiProvider(import.meta.env.VITE_UI_ADAPTER).install(app);
(await resolveUiProvider(import.meta.env.VITE_UI_ADAPTER)).install(app);
app.mount('#app');
+1 -1
View File
@@ -11,5 +11,5 @@ const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(VueQueryPlugin, { queryClient })
resolveUiProvider(import.meta.env.VITE_UI_ADAPTER).install(app)
;(await resolveUiProvider(import.meta.env.VITE_UI_ADAPTER)).install(app)
app.mount('#app')
@@ -1,10 +1,10 @@
import { nativeUiProvider } from '../adapter/native/installNativeAdapter';
import { primeVueUiProvider } from '../adapter/primevue/installPrimeVueAdapter';
export function resolveUiProvider(name) {
export async function resolveUiProvider(name) {
const normalized = (name ?? 'primevue').trim().toLowerCase();
if (normalized === 'primevue')
return primeVueUiProvider;
if (normalized === 'native')
return nativeUiProvider;
if (normalized === 'primevue') {
return (await import('../adapter/primevue/installPrimeVueAdapter')).primeVueUiProvider;
}
if (normalized === 'native') {
return (await import('../adapter/native/installNativeAdapter')).nativeUiProvider;
}
throw new Error(`Unsupported VITE_UI_ADAPTER '${name}'. Allowed values: primevue, native.`);
}
@@ -1,12 +1,14 @@
import type { UiProvider } from './UiProvider'
import { nativeUiProvider } from '../adapter/native/installNativeAdapter'
import { primeVueUiProvider } from '../adapter/primevue/installPrimeVueAdapter'
export type UiProviderName = 'primevue' | 'native'
export function resolveUiProvider(name: string | undefined): UiProvider {
export async function resolveUiProvider(name: string | undefined): Promise<UiProvider> {
const normalized = (name ?? 'primevue').trim().toLowerCase()
if (normalized === 'primevue') return primeVueUiProvider
if (normalized === 'native') return nativeUiProvider
if (normalized === 'primevue') {
return (await import('../adapter/primevue/installPrimeVueAdapter')).primeVueUiProvider
}
if (normalized === 'native') {
return (await import('../adapter/native/installNativeAdapter')).nativeUiProvider
}
throw new Error(`Unsupported VITE_UI_ADAPTER '${name}'. Allowed values: primevue, native.`)
}
@@ -0,0 +1,14 @@
import { describe, expect, it } from 'vitest'
import { resolveUiProvider } from '../resolveUiProvider'
describe('resolveUiProvider', () => {
it('loads the selected native provider', async () => {
await expect(resolveUiProvider('native')).resolves.toMatchObject({ id: 'native-accessible' })
})
it('fails closed for an unsupported provider before application mount', async () => {
await expect(resolveUiProvider('unsupported')).rejects.toThrow(
"Unsupported VITE_UI_ADAPTER 'unsupported'. Allowed values: primevue, native."
)
})
})