From 499f8ac075365218f8498db634c2e5509d3c812e Mon Sep 17 00:00:00 2001 From: Josep Piguillem Date: Mon, 12 Oct 2026 20:21:40 +0200 Subject: [PATCH] Keep Chart.js and the whole of lodash out of apps' initial bundle Each chart registers what it needs when it is built instead of on import, and lodash is imported one function at a time. In TaskStream the initial bundle drops from 2.13 MB to 1.90 MB. Co-Authored-By: Claude Opus 5.5 --- libs/ui/README.md | 10 ++++++++++ .../lib/ui/chart/bar-chart/bar-chart.component.ts | 5 +++-- libs/ui/src/lib/ui/chart/base-chart.component.ts | 7 ++++--- .../doughnut-chart/doughnut-chart.component.ts | 5 +++-- .../ui/chart/gauge-chart/gauge-chart.component.ts | 5 +++-- .../ui/chart/line-chart/line-chart.component.ts | 14 +++----------- .../lib/ui/chart/pie-chart/pie-chart.component.ts | 5 +++-- .../ui/chart/radar-chart/radar-chart.component.ts | 13 +++---------- .../lib/ui/chip/chip-date/chip-date.component.ts | 6 +++--- .../select-carousel/select-carousel.component.ts | 4 ++-- .../select-multiple/select-multiple.component.ts | 4 ++-- .../select-period/select-period.component.ts | 4 ++-- .../src/lib/ui/select/select/select.component.ts | 4 ++-- 13 files changed, 43 insertions(+), 43 deletions(-) diff --git a/libs/ui/README.md b/libs/ui/README.md index e384b20..eff057f 100644 --- a/libs/ui/README.md +++ b/libs/ui/README.md @@ -184,11 +184,21 @@ Detalles a tener en cuenta: - **`Dataset.cubicInterpolationMode` pasa a opcional.** Solo tiene sentido en `line`; forzarlo en `bar`/`pie`/`doughnut` no aportaba nada y esos tipos ni siquiera usan `Dataset` (los dos últimos usan `ChartSlice`). +- **Cada gráfico registra lo suyo en `buildChart()`, no al importar.** El + paquete es un único `fesm`, así que un `Chart.register(...)` a nivel de + módulo se ejecutaba en cualquier app que importase `@pi/ui` y metía Chart.js + entero en su bundle inicial, pintase gráficos o no (en TaskStream, unos + 230 kB). Registrar dos veces lo mismo no hace nada. Si se añade un tipo de + gráfico, su registro va ahí también. - **El `type="bar"` del `pi-chart` original nunca funcionó**: el input existía pero `getLineChartConfigs()` construía siempre un chart `line` ignorando su valor. Se descubrió al dividir los componentes; `pi-bar-chart` es la primera versión que realmente renderiza barras. +**`lodash` se importa por función** (`lodash/isEqual`, `lodash/some`, +`lodash/capitalize`), nunca `import _ from 'lodash'`: lodash es CommonJS y el +import completo mete la librería entera en el bundle de quien use un select. + **El loader de servidor de traducciones ya no está en el barrel.** `util/translate/loaders/translate-server.loader` usa `fs`, `path` y `process`; exportarlo desde `@pi/ui` rompía el bundle de navegador de cualquier app que diff --git a/libs/ui/src/lib/ui/chart/bar-chart/bar-chart.component.ts b/libs/ui/src/lib/ui/chart/bar-chart/bar-chart.component.ts index cf45024..3b69dab 100644 --- a/libs/ui/src/lib/ui/chart/bar-chart/bar-chart.component.ts +++ b/libs/ui/src/lib/ui/chart/bar-chart/bar-chart.component.ts @@ -12,8 +12,6 @@ import { barDatasetConfig } from '../chart.configs'; import { PiBaseCartesianChartComponent } from '../base-cartesian-chart.component'; import { PiChartLegendComponent } from '../chart-legend/chart-legend.component'; -Chart.register(CategoryScale, LinearScale, BarController, BarElement, Tooltip, Legend); - @Component({ selector: 'pi-bar-chart', templateUrl: './bar-chart.component.html', @@ -30,6 +28,9 @@ export class PiBarChartComponent extends PiBaseCartesianChartComponent { } protected buildChart(): Chart { + // Aquí y no al importar: así una app que nunca pinta este gráfico no + // arrastra Chart.js. Registrar dos veces no hace nada. + Chart.register(CategoryScale, LinearScale, BarController, BarElement, Tooltip, Legend); return new Chart(this.chartRef().nativeElement, { type: 'bar', data: { diff --git a/libs/ui/src/lib/ui/chart/base-chart.component.ts b/libs/ui/src/lib/ui/chart/base-chart.component.ts index 8215739..e4945b3 100644 --- a/libs/ui/src/lib/ui/chart/base-chart.component.ts +++ b/libs/ui/src/lib/ui/chart/base-chart.component.ts @@ -15,9 +15,10 @@ import { Device } from '../../util/device-configs'; * `pi-pie-chart`, `pi-doughnut-chart`): el ``, el ciclo de vida que * construye el `Chart` una vez la vista está lista, y el `TranslateService` * que todos usan para las etiquetas. Cada tipo registra solo los - * controladores/elementos de Chart.js que necesita — no hay un registro - * global compartido, así que un consumidor que solo importe `pi-pie-chart` - * no arrastra el código de líneas/barras. + * controladores/elementos de Chart.js que necesita, y lo hace en + * `buildChart`, no al importar el módulo: el paquete es un único `fesm`, así + * que un `Chart.register` a nivel de módulo metería Chart.js entero en el + * bundle inicial de cualquier app que use `@pi/ui`, pinte gráficos o no. */ @Component({ template: '' }) export abstract class PiBaseChartComponent implements AfterViewInit { diff --git a/libs/ui/src/lib/ui/chart/doughnut-chart/doughnut-chart.component.ts b/libs/ui/src/lib/ui/chart/doughnut-chart/doughnut-chart.component.ts index ef2a587..0821463 100644 --- a/libs/ui/src/lib/ui/chart/doughnut-chart/doughnut-chart.component.ts +++ b/libs/ui/src/lib/ui/chart/doughnut-chart/doughnut-chart.component.ts @@ -8,8 +8,6 @@ import { } from 'chart.js'; import { PiBaseArcChartComponent } from '../base-arc-chart.component'; -Chart.register(ArcElement, DoughnutController, Tooltip, Legend); - @Component({ selector: 'pi-doughnut-chart', templateUrl: './doughnut-chart.component.html', @@ -18,6 +16,9 @@ Chart.register(ArcElement, DoughnutController, Tooltip, Legend); }) export class PiDoughnutChartComponent extends PiBaseArcChartComponent { protected buildChart(): Chart { + // Aquí y no al importar: así una app que nunca pinta este gráfico no + // arrastra Chart.js. Registrar dos veces no hace nada. + Chart.register(ArcElement, DoughnutController, Tooltip, Legend); return new Chart(this.chartRef().nativeElement, { type: 'doughnut', data: this.getArcData<'doughnut'>(), diff --git a/libs/ui/src/lib/ui/chart/gauge-chart/gauge-chart.component.ts b/libs/ui/src/lib/ui/chart/gauge-chart/gauge-chart.component.ts index 6ef6660..ca25de4 100644 --- a/libs/ui/src/lib/ui/chart/gauge-chart/gauge-chart.component.ts +++ b/libs/ui/src/lib/ui/chart/gauge-chart/gauge-chart.component.ts @@ -8,8 +8,6 @@ import { ArcElement, Chart, DoughnutController } from 'chart.js'; import { DatasetColor } from '../../../util/models'; import { PiBaseChartComponent } from '../base-chart.component'; -Chart.register(ArcElement, DoughnutController); - /** * Un único valor sobre `max`, no una serie ni porciones — por eso extiende * directamente `PiBaseChartComponent` (la base raíz) en vez de @@ -36,6 +34,9 @@ export class PiGaugeChartComponent extends PiBaseChartComponent { ); protected buildChart(): Chart { + // Aquí y no al importar: así una app que nunca pinta este gráfico no + // arrastra Chart.js. Registrar dos veces no hace nada. + Chart.register(ArcElement, DoughnutController); const clamped = this.clampedValue(); return new Chart(this.chartRef().nativeElement, { type: 'doughnut', diff --git a/libs/ui/src/lib/ui/chart/line-chart/line-chart.component.ts b/libs/ui/src/lib/ui/chart/line-chart/line-chart.component.ts index 392aaa4..5fd5a3c 100644 --- a/libs/ui/src/lib/ui/chart/line-chart/line-chart.component.ts +++ b/libs/ui/src/lib/ui/chart/line-chart/line-chart.component.ts @@ -14,17 +14,6 @@ import { hexToRgba, lineDatasetConfig } from '../chart.configs'; import { PiBaseCartesianChartComponent } from '../base-cartesian-chart.component'; import { PiChartLegendComponent } from '../chart-legend/chart-legend.component'; -Chart.register( - CategoryScale, - LinearScale, - LineController, - LineElement, - PointElement, - Filler, - Tooltip, - Legend, -); - @Component({ selector: 'pi-line-chart', templateUrl: './line-chart.component.html', @@ -61,6 +50,9 @@ export class PiLineChartComponent extends PiBaseCartesianChartComponent { } protected buildChart(): Chart { + // Aquí y no al importar: así una app que nunca pinta este gráfico no + // arrastra Chart.js. Registrar dos veces no hace nada. + Chart.register(CategoryScale, LinearScale, LineController, LineElement, PointElement, Filler, Tooltip, Legend); return new Chart(this.chartRef().nativeElement, { type: 'line', data: { diff --git a/libs/ui/src/lib/ui/chart/pie-chart/pie-chart.component.ts b/libs/ui/src/lib/ui/chart/pie-chart/pie-chart.component.ts index 74f8fa2..8b7c378 100644 --- a/libs/ui/src/lib/ui/chart/pie-chart/pie-chart.component.ts +++ b/libs/ui/src/lib/ui/chart/pie-chart/pie-chart.component.ts @@ -2,8 +2,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ArcElement, Chart, Legend, PieController, Tooltip } from 'chart.js'; import { PiBaseArcChartComponent } from '../base-arc-chart.component'; -Chart.register(ArcElement, PieController, Tooltip, Legend); - @Component({ selector: 'pi-pie-chart', templateUrl: './pie-chart.component.html', @@ -12,6 +10,9 @@ Chart.register(ArcElement, PieController, Tooltip, Legend); }) export class PiPieChartComponent extends PiBaseArcChartComponent { protected buildChart(): Chart { + // Aquí y no al importar: así una app que nunca pinta este gráfico no + // arrastra Chart.js. Registrar dos veces no hace nada. + Chart.register(ArcElement, PieController, Tooltip, Legend); return new Chart(this.chartRef().nativeElement, { type: 'pie', data: this.getArcData<'pie'>(), diff --git a/libs/ui/src/lib/ui/chart/radar-chart/radar-chart.component.ts b/libs/ui/src/lib/ui/chart/radar-chart/radar-chart.component.ts index 05a22e8..724b2ec 100644 --- a/libs/ui/src/lib/ui/chart/radar-chart/radar-chart.component.ts +++ b/libs/ui/src/lib/ui/chart/radar-chart/radar-chart.component.ts @@ -17,16 +17,6 @@ import { hexToRgba, lineDatasetConfig, tooltipConfig } from '../chart.configs'; import { PiBaseCartesianChartComponent } from '../base-cartesian-chart.component'; import { PiChartLegendComponent } from '../chart-legend/chart-legend.component'; -Chart.register( - RadarController, - RadialLinearScale, - LineElement, - PointElement, - Filler, - Tooltip, - Legend, -); - /** * A diferencia de `pi-line-chart`/`pi-bar-chart`, la escala radial (`r`) no * encaja en `scalesConfig` (pensado para `x`/`y`), así que no reutiliza @@ -94,6 +84,9 @@ export class PiRadarChartComponent extends PiBaseCartesianChartComponent { } protected buildChart(): Chart { + // Aquí y no al importar: así una app que nunca pinta este gráfico no + // arrastra Chart.js. Registrar dos veces no hace nada. + Chart.register(RadarController, RadialLinearScale, LineElement, PointElement, Filler, Tooltip, Legend); return new Chart(this.chartRef().nativeElement, { type: 'radar', data: { diff --git a/libs/ui/src/lib/ui/chip/chip-date/chip-date.component.ts b/libs/ui/src/lib/ui/chip/chip-date/chip-date.component.ts index b66ee8c..d9e2496 100644 --- a/libs/ui/src/lib/ui/chip/chip-date/chip-date.component.ts +++ b/libs/ui/src/lib/ui/chip/chip-date/chip-date.component.ts @@ -10,7 +10,7 @@ import { ComponentFamilyClass, theming } from '../../../styles'; import { Date } from '../../../util/models'; import { PiBaseChipComponent } from '../base-chip.component'; import { DateTime } from 'luxon'; -import _ from 'lodash'; +import capitalize from 'lodash/capitalize'; @Component({ selector: 'pi-chip-date', @@ -73,8 +73,8 @@ export class PiChipDateComponent extends PiBaseChipComponent { day: dateObj.day.toString() || '', month: dateObj.monthShort || '', weekDay: isDateToday - ? _.capitalize(dateObj.toRelativeCalendar() ?? '') - : _.capitalize(dateObj.weekdayShort || ''), + ? capitalize(dateObj.toRelativeCalendar() ?? '') + : capitalize(dateObj.weekdayShort || ''), }; } } diff --git a/libs/ui/src/lib/ui/select/select-carousel/select-carousel.component.ts b/libs/ui/src/lib/ui/select/select-carousel/select-carousel.component.ts index 15a719b..2cf5920 100644 --- a/libs/ui/src/lib/ui/select/select-carousel/select-carousel.component.ts +++ b/libs/ui/src/lib/ui/select/select-carousel/select-carousel.component.ts @@ -9,7 +9,7 @@ import { inject, } from '@angular/core'; import { TranslateService, TranslatePipe } from '@ngx-translate/core'; -import _ from 'lodash'; +import isEqual from 'lodash/isEqual'; import { PiBaseSelectComponent } from '../base-select.component'; import { PiSelectOptionComponent } from '../select-option/select-option.component'; import { Device } from '../../../util/device-configs'; @@ -97,7 +97,7 @@ export class PiSelectCarouselComponent * @returns The selected option. */ get selectedOption(): any { - return this.options().find((option) => _.isEqual(this.value(), option)); + return this.options().find((option) => isEqual(this.value(), option)); } /** diff --git a/libs/ui/src/lib/ui/select/select-multiple/select-multiple.component.ts b/libs/ui/src/lib/ui/select/select-multiple/select-multiple.component.ts index 45b3b73..9a51568 100644 --- a/libs/ui/src/lib/ui/select/select-multiple/select-multiple.component.ts +++ b/libs/ui/src/lib/ui/select/select-multiple/select-multiple.component.ts @@ -11,7 +11,7 @@ import { import { TranslateService, TranslatePipe } from '@ngx-translate/core'; import { PiBaseSelectComponent } from '../base-select.component'; import { PiSelectOptionComponent } from '../select-option/select-option.component'; -import _ from 'lodash'; +import some from 'lodash/some'; import { MatCheckboxChange, MatCheckbox } from '@angular/material/checkbox'; import { NgTemplateOutlet } from '@angular/common'; import { PiIconComponent } from '../../icon/icon/icon.component'; @@ -114,7 +114,7 @@ export class PiSelectMultipleComponent get selectedOptions(): Array { const value = this.value(); if (!value) return []; - return this.options().filter((option) => _.some(value, option)); + return this.options().filter((option) => some(value, option)); } /** diff --git a/libs/ui/src/lib/ui/select/select-period/select-period.component.ts b/libs/ui/src/lib/ui/select/select-period/select-period.component.ts index 413218a..439d2ab 100644 --- a/libs/ui/src/lib/ui/select/select-period/select-period.component.ts +++ b/libs/ui/src/lib/ui/select/select-period/select-period.component.ts @@ -11,7 +11,7 @@ import { import { TranslateService, TranslatePipe } from '@ngx-translate/core'; import { PiBaseSelectComponent } from '../base-select.component'; import { PiSelectOptionComponent } from '../select-option/select-option.component'; -import _ from 'lodash'; +import isEqual from 'lodash/isEqual'; import { NgTemplateOutlet } from '@angular/common'; import { PiIconComponent } from '../../icon/icon/icon.component'; import { PiDisabledDirective } from '../../../util/status/directives/status.directive'; @@ -103,7 +103,7 @@ export class PiSelectPeriodComponent * @returns The selected option. */ get selectedOption(): any { - return this.options().find((option) => _.isEqual(this.value(), option)); + return this.options().find((option) => isEqual(this.value(), option)); } /** diff --git a/libs/ui/src/lib/ui/select/select/select.component.ts b/libs/ui/src/lib/ui/select/select/select.component.ts index 58f6125..616c598 100644 --- a/libs/ui/src/lib/ui/select/select/select.component.ts +++ b/libs/ui/src/lib/ui/select/select/select.component.ts @@ -13,7 +13,7 @@ import { import { TranslateService, TranslatePipe } from '@ngx-translate/core'; import { PiBaseSelectComponent } from '../base-select.component'; import { PiSelectOptionComponent } from '../select-option/select-option.component'; -import _ from 'lodash'; +import isEqual from 'lodash/isEqual'; import { NgTemplateOutlet } from '@angular/common'; import { PiIconComponent } from '../../icon/icon/icon.component'; import { PiDisabledDirective } from '../../../util/status/directives/status.directive'; @@ -116,7 +116,7 @@ export class PiSelectComponent * @returns The selected option. */ get selectedOption(): any { - return this.options().find((option) => _.isEqual(this.value(), option)); + return this.options().find((option) => isEqual(this.value(), option)); } /**