¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.dev
Eider Ogueta 21/04/2022 Cargando comentarios…
Alguna vez te has preguntado si una librería tan simple puede afectar al rendimiento, ¿o no puede? O, por qué me obliga mi librería a instalar todas peerDependencies, sabiendo que no voy a utilizar los módulos que son dependientes de librerías de terceros. O, te has preguntado, ¿qué son y por qué necesitamos puntos de entrada secundarios? Entonces, si te has formulado algunas de las anteriores preguntas, este es tu post :)
Dado que una librería se puede utilizar en muchos lugares, el rendimiento es un aspecto crítico. Y, en este punto, ¡entran en juego los puntos de entrada secundarios!
Al aprovechar los puntos de entrada secundarios, buscamos una mejor arquitectura, compatibilidad con treeshaking, tamaños de paquetes más pequeños y, por lo tanto, aplicaciones más rápidas.
Antes de nada, os dejo un glosario de términos.
Y ¿qué mejor que entender esto con un ejemplo?
Empezamos creando nuestra librería de componentes usando el CLI de Angular:
$ ng new my-lib --create-application=false
$ cd my-lib
$ ng generate library my-lib
Nuestra librería va a constar de dos módulos: simple-text
y custom-time.
$ ng g c simple-text
$ ng g m simple-text
$ ng g c custom-time
$ ng g m custom-time
simple-text.component.ts
va a ser un componente sin dependencias.
import { Component } from '@angular/core';
@Component({
selector: 'simple-text',
template: `
<div>I have no dependencies!</div>`
})
export class SimpleTextComponent {}
custom-time.component.ts
va a depender de la librería moment. Ejecutamos el siguiente comando para instalar la librería:
$ npm i moment
import { Component } from '@angular/core';
import * as moment_ from 'moment';
const moment = moment_;
@Component({
selector: 'custom-time',
template: `
<div>Hey, Custom Time:</div>
<div>{{ time }}</div>
`
})
export class CustomTimeComponent {
time: string;
constructor() {
this.time = moment().format();
}
}
Exportamos en el fichero public-api.ts los componentes y módulos creados anteriormente.
/*
* Public API Surface of my-lib
*/
export * from './simple-text/simple-text.component';
export * from './simple-text/simple-text.module';
export * from './custom-time/custom-time.component';
export * from './custom-time/custom-time.module';
Ya que uno de nuestros componentes depende de moment, debemos especificarla en nuestro my-lib/package.json:
{
"name": "my-lib",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^8.2.14",
"@angular/core": "^8.2.14",
"moment": "^2.26.0"
}
Vamos a crear una aplicación de ejemplo que va a consumir nuestra librería:
$ ng new example-app
Antes de instalar nuestra librería, veremos qué dependencias tiene nuestra aplicación y cuánto pesan de forma gráfica con Webpack Bundle Analyzer.
$ npm i -D webpack-bundle-analyzer
Añadimos el siguiente script al package.json.
"analyze": "ng build --prod --stats-json && webpack-bundle-analyzer ./dist/example-app/stats.json"
Al ejecutar este comando, se realiza una compilación para producción y se genera un stats.json
que luego es recogido y visualizado por webpack-bundle-anlyzer.
Nuestro bundle principal incluye sólo Angular. El tamaño de nuestra aplicación actualmente es de 127.06KB.
Ahora vamos a instalar nuestra librería y ver cómo afecta al tamaño del bundle del proyecto.
Ya que estamos trabajando en este ejemplo con una librería en nuestro entorno local, debemos compilarla y copiar el dist/my-lib
dentro de el node_modules
de nuestra aplicación.
Importamos solo el módulo simple-text
en app.component.module.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { SimpleTextModule } from 'node_modules/my-lib';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
SimpleTextModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Ejecutamos la aplicación y vemos que obtenemos un error en la terminal.
./node_modules/my-lib/fesm2015/my-lib.js:4:0-33 - Error: Module not found: Error: Can't resolve 'moment' in '/home/eogueta/example-app/node_modules/my-lib/fesm2015'
Aunque nuestra aplicación solo importa SimpleTextModule
, el compilador de Angular nos pide instalar todas las peerDependencies definidas en my-lib
, así que deberemos instalar moment.
$ npm i moment
Vamos a volver a lanzar el script de analyze:
Vemos que aunque no se esté usando directamente la librería moment, está incluida en nuestro bundle y el tamaño ha aumentado considerablemente, 506.74KB
.
Esto se debe a que cuando construimos la librería se genera solo un chunk(my-lib.js)
que contiene tanto SimpleTextComponent
como CustomTimeComponent.
Así que todavía obtenemos la librería moment incluso si solo importamos SimpleTextModule.
Hay varias razones por las que queremos usar puntos de entrada secundarios al diseñar nuestras librerías de Angular.
Para este ejemplo vamos a establecer custom-time como punto de entrada secundario siguiendo la estructura definida por ng-packagr, mientras que simple-text seguirá igual que hasta ahora.
De acuerdo con la documentación, esta sería la estructura:
my-lib
├── src
| ├── lib
| | └── simple-text
| ├── public_api.ts (primary entry point)
| └── *.ts
├── ng-package.json
├── package.json
└── custom-time(secondary entry point)
├── custom-time.component.ts
├── …
├── index.ts
├── public_api.ts
└── package.json
Para trabajar con puntos de entrada secundarios, se necesita un mínimo de cuatro archivos por feature.
El index.ts solo está ahí para apuntar a public_api, que es útil durante las importaciones.
export * from './public_api';
Public_api.ts exporta todos los módulos y componentes de nuestro módulo.
/
* Public API Surface of custom-time
*/
export * from './custom-time.component';
export * from './custom-time.module';
El package.json contiene configuraciones específicas de ng-packagr.
{
"ngPackage": {
"lib": {
"entryFile": "public_api.ts"
}
},
"peerDependencies": {
// Lista de las peerDependecies de cada módulo
"moment": "^2.29.1"
}
}
projects/my-lib/package.json
, ya que la acabamos de definir en el nuevo fichero projects/my-lib/custom-time/package.json.
custom-time
de nuestro archivo principal public_apit.ts./
* Public API Surface of my-lib
*/
export * from "./lib/simple-text/simple-text.component";
export * from "./lib/simple-text/simple-text.module";
Terminamos con 2 chunks: my-lib.js, my-lib-custom-time.js.
Cada una de nuestros componentes/features obtiene su propio archivo .js, lo que significa que están incluidas en el paquete de su aplicación individualmente, favoreciendo la técnica de tree-shaking.
my-lib-custom-time.js
ahora solo contiene el código relacionado con CustomTimeModule
, y my-lib.js
el código específico de SimpleTextModule.
Ejecutamos por última vez npm i analyze
y podemos ver que el tamaño del bundle se ha reducido, 127.19KB.
Hay un ligero cambio a la hora de importar las rutas, ya que hemos movido nuestro custom-time
del punto de entrada principal.
// Primary entry points
import { SimpleText } from 'my-lib';
// Secondary entry points
import { CustomTimeModule } from 'my-lib/custom-time';
Si la aplicación cliente solo importa el componente SimpleTextModule,
deberíamos poder ejecutar la aplicación sin instalar moment.
Como hemos dicho antes, los puntos de entrada secundarios nos ofrecen una excelente manera de entregar nuestra librería en múltiples chunks.
Esto es lo que utiliza el equipo de Angular para las bibliotecas @angular/core y @angular/material. La mayoría de nosotros solamente usamos un pequeño subconjunto de los componentes de @angular/material y, al importar únicamente los módulos necesarios, el estilo o la lógica de otros componentes no necesitan incluirse innecesariamente en el paquete final de la aplicación.
Podemos sacar las siguientes conclusiones:
Cosas a tener en cuenta:
Este artículo está basado en:
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.