From c121365bed3bd5d98c20b161772b5cfb21546928 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 6 Jun 2026 03:19:25 +0200 Subject: [PATCH] docs(bundle): Phase 5a done (base.css prune), 5b deferred with rationale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit base.css pruned to the 9 role-referenced scales (full palette opt-in): 113.5 → 36.2 KB gz per page total (−68% from the original monolith). Non-Gregorian calendar lazy-loading (5b) deliberately deferred — it needs a sync→async change to the vendored date core for a narrow date-pages-only win; documented the trade-off. Co-Authored-By: Claude Opus 4.8 (1M context) --- optimize-bundle.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/optimize-bundle.md b/optimize-bundle.md index ce8c63264..fb08a0f9f 100644 --- a/optimize-bundle.md +++ b/optimize-bundle.md @@ -19,7 +19,7 @@ excediendo** — y es un problema *estructural* (un agregado monolítico), no de | JS por página | ✅ Sano | entry 10.4 KB gz · runtime ~35 KB gz · componente mediana 7.3 KB gz | | CSS por página | ⚠️ Exceso | **113.5 KB gz / 851 KB raw** en *toda* página `/uix` | | Code-splitting JS | ✅ Correcto | color engine, sema y componentes se cargan bajo demanda | -| Code-splitting CSS | ✅ Resuelto | 93 recipes code-split; index.css foundation-only. **113.5 → 54.4 KB gz/página** (−59 KB). Pendiente: Fase 5 (podar base.css) | +| Code-splitting CSS | ✅ Resuelto | 93 recipes code-split + `base.css` podado a 9 scales. **113.5 → 36.2 KB gz/página** (−68%). | --- @@ -66,7 +66,8 @@ monta la página. Ese eje decide la Fase 1 (`@layer` o no). | **2** ✅ | Partials compartidos (`menu-indicator` order-independent por especificidad) + primitivas layout en foundation | medio | hecho | | **3** ✅ | Propagar a los 83 restantes **por lotes** por familia; couplings arreglados (toggle-group, pickers) | medio | hecho | | **4** ✅ | `index.css` = foundation-only (−59 KB gz/página) | bajo | hecho | -| **5** | (track aparte) podar `base.css` a roles tematizados + calendarios no-gregorianos dynamic import | alto | build | +| **5a** ✅ | podar `base.css` a las scales que usan los roles (9 de 31); resto opt-in en `palette.css` | alto | hecho | +| **5b** ⏸️ | calendarios no-gregorianos a dynamic import | alto | **diferido** (ver abajo) | **Cross-coupling a vigilar** (recipes agregados que referencian un componente splitteado): auditado para los 10 — único caso `words.css → [data-textarea]`, y @@ -164,6 +165,46 @@ NumberField/ColorPicker/TextArea) NO necesita el import — el recipe llega solo barrido (el scoping `[data-{component}]` lo confirma en la práctica). - `check` 0 errores. +### Fase 5a — `base.css` podado (COMPLETADO) + +`base.css` enviaba las **31 scales Radix** donantes, pero los roles del tema solo +referencian **9** (gray·green·indigo·orange·plum·purple·red·slate·teal). Las +otras 22 eran peso muerto en toda página: ningún componente referencia +`--scale-*` crudo (usan `--color-{role}-*`), y el theming runtime construye desde +los datos JS de scales y escribe valores **resueltos** (`build-scheme`: +`variables[k]=hex`), nunca lee los tokens CSS de scale. + +- `renderThemeCss` ahora emite solo las scales referenciadas por roles (opción + `scales: 'roleReferenced'`, default). El donante completo de 31 scales va + **opt-in** en `generated/palette.css` (nuevo `renderColorPaletteCss`). +- El **contrato CSS** sigue siendo el vocabulario completo (una app puede + override cualquier scale; carga `palette.css` para usar las 22 extra). El test + de contrato valida cobertura contra la paleta completa (`scales: 'all'`). +- **base.css 53.7 → 35.4 KB gz**. **Monolito (toda página) 54.4 → 36.2 KB gz.** + Acumulado con Fases 0-4: **113.5 → 36.2 KB gz (−68 %)**. +- Verificado en navegador: roles resuelven (primary = purple, `button` bg + correcto), scales podadas vacías en páginas normales, restauradas en + `/temas/color` vía `palette.css`. Suite eidos en baseline (192/195). + +### Fase 5b — calendarios no-gregorianos: DIFERIDO (decisión) + +`createCalendar(name)` (vendored `@internationalized/date`) es **síncrono** y +hace `import` estático de las 9 clases de calendario → se empaquetan juntas. +Cargar las no-gregorianas bajo demanda exige hacerlo **async**, lo que cambia la +API core del motor de fechas (rompe el supuesto de construcción síncrona, +ramifica a sus llamadores, y **diverge del upstream** vendored). + +- **Recompensa**: ~37 KB raw / ~10-12 KB gz, **solo en páginas de fecha**, y solo + para apps que no usan calendarios no-gregorianos. (Ya están code-split fuera + de las páginas sin fecha.) +- **Coste/riesgo**: refactor sync→async de código vendored core, divergencia del + upstream, ripple a motor + componentes. + +El trade no compensa: hacer async la API de fechas para ahorrar ~10 KB gz en +páginas de fecha no vale la divergencia/riesgo. Se difiere conscientemente; si +una app real necesita muchos sistemas de calendario y le pesa, se reabre con un +`createCalendarAsync` paralelo (sin romper la API síncrona). + ## Metodología - `npm run build` → `adapter-static`, salida en `build/`.