W tym tygodniu w świecie Angulara sporo się działo, bo opublikowany został od dawna wyczekiwany RFC Angular Standalone Components. Oprócz tego światło dzienne ujrzał Storybook 6.4 (wprowadzający trzykrotną optymalizację czasu ładowania!) i Parcel v2 (zawierający ogrom od dawna wyczekiwanych nowości). Przygotujcie gorącą herbatę ☕️ i zapraszamy do lektury! 📚
1. Samodzielne komponenty i dyrektywy w Angularze
Jeśli nigdy nie mieliście do czynienia z angularową koncepcja modułów, to na pierwszy rzut oka może ona być mocno niezrozumiała. Szczególnie biorąc pod uwagę, że mają one niewiele wspólnego z modułami znanym z JavaScriptu. Natywne moduły pozwalają podzielić aplikację na wiele plików i zarządzać API, jakie udostępniamy. Angularowe moduły mają na celu zapewniać konfigurację zależności dla Dependency Injection. W Angularze każdy komponent czy dyrektywa musi być częścią jakiegoś modułu, co czyni je najbardziej atomicznym elementem frameworku. Co ciekawe, moduły trafiły do Angulara dopiero w wersji 2.0.0-rc.5 i były odpowiedzią na problemy z publikowanie angularowych bibliotek w npm. Z racji, że framework był już na etapie Release Candidate, to całe rozwiązanie powstało w przyśpieszonym tempie, i jak to bywa z takimi rozwiązaniami zostało z nami na dłużej.
// JS Modules imports
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
// Angular Module declaration
@NgModule({
declarations: [AppComponent],
// Angular Module import
imports: [BrowserModule],
providers: [],
bootstrap: [AppComponent]
})
// JS Module export
export class AppModule {}
Angular jest chyba jedynym poważnym rozwiązaniem, w którym najmniejszym atomem jest coś innego niż pojedynczy komponent. Aby podążać za dobrymi praktykami w ostatnich latach wytworzył się trend promujący tworzenie modułów zawierających pojedyncze komponenty. Taka architektura jest bardzo elastyczna i promuje DRY, ale nawet nie wiecie, ile czasu zmarnowałem w ostatnim roku na generowanie modułowego boilerplatu. Dobrze, że mamy `ng generate`, bo bez tego już dawno postradałbym zmysły…
// hello-world.component.ts
@Component({
selector: 'hello-world',
template: '<h2>Hello World</h2>'
styles: ['h2 { color: red; }']
})
export class HelloWorldComponent { }
// hello-world.module.ts
@NgModule({
declarations: [HelloWorldComponent],
exports: [HelloWorldComponent]
})
export class HelloWorldComponentModule {}
Zaprezentowany powyżej schemat nazywany jest SCAM (od Single Component Angular Module). Jeśli chcecie dowiedzieć się więcej, to zapraszam tutaj.
Zespół Angulara już od jakiegoś czasu sugerował, że zajmie się tym tematem. W zeszłym tygodniu w ich repozytorium pojawiło się obszerne RFC zawierające propozycję rozwiązania problemu. Prezentowana architektura nie pozbywa się całkowicie NgModules, ale sprawia, że stają się one opcjonalne. Jak zapewniają autorzy RFC, zmiany nie będą mieć negatywnego wpływu na performance, czy lazy loading i znacząco poprawią Developer Experience.
Jeśli chcemy oznaczyć komponent jako samodzielny, jego konfigurację należy rozszerzyć o parametr `standalone: true`. RFC sugeruje, że `ng generate` będzie można skonfigurować tak, aby domyślnie generował adnotacje w taki właśnie sposób. Z czasem może pojawić się również flaga konfiguracji, która sprawi, że parametr ten domyślnie będzie ustawiony na true. Warto wspomnieć, że zadeklarowanie standalone komponentu wewnątrz modułu będzie powodowało błąd kompilacji. Adnotacja @Component zyska również dwa nowe parametry `imports` i `schemas`, które pozwolą odpowiednio skonfigurować Dependency Injection. Warto wspomnieć, że oba parametry będą dostępne tylko jeśli oznaczymy komponent jako samodzielny.
import { AnotherStandaloneComponent } from './another-standalone-component.component.ts';
import { CommonModule } from '@angular/common';
import { Component, CUSTOM_ELEMENTS_SCHEMA, Input } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-standalone-component',
standalone: true,
imports: [AnotherStandaloneComponent, CommonModule,, FormsModule],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
template: `
<app-another-standalone-component *ngIf="flag"></app-another-standalone-component>
<custom-element></custom-element>
Forms work: <input [(ngModel)]="name" /> (name = {{ name }})
`
})
export class MyComponent {
@Input() name: string = 'Vived';
@Input() flag: boolean = true;
}
W ramach RFC rozważana była również jedna ciekawa zmiana: podzielenie CommonModule na wiele samodzielnych komponentów. Jeśli nie używacie Angulara, to wspomniany moduł zawiera zestaw podstawowych dyrektyw i transformatorów, takich jak *ngIf, czy *ngFor. Oznacza to, że w praktyce większość nietrywialnych komponentów musi importować ten moduł. Podzielenie tego podstawowego modułu na pojedyncze komponenty sprawiłoby, że deklaracje importów byłyby czytelniejsze, ale też dużo trudniejsze w utrzymaniu. Co ciekawe rozważano również automatyczne importowanie CommonModule, ale porzucono ten pomysł motywując decyzję wprowadzeniem zbyt dużo magii dla programistów.
@Component({
selector: 'with-control-statments-component',
standalone: true,
//Angular team could replace this:
//imports: [CommonModule],
//with this:
imports: [ NgIf, NgFor ],
template: `
<ul *ngIf="show">
<li *ngFor="let item of items">
{{item}}
</li>
</ul>
`
})
export class WithControlStatmentsComponent {
items = [1,2,3];
@Input() show = true;
}
Jeszcze nie wiadomo kiedy faza RFC się zakończy i kiedy mamy szansę zobaczyć nową architekturę w naszych aplikacjach. W tym momencie możemy już natomiast zobaczyć Proof Of Concept rozwiązania na StackBlitz udostępnionym tutaj. Trzymam kciuki, bo jest to jedna z rzeczy, na którą czekam najbardziej na angularowej roadmapie.
PS. Dzisiaj opublikowany został pierwszy Release Candidate Angulara 13 🥳
Źródła:
https://twitter.com/IgorMinar/status/1447593511981772802
https://github.com/angular/angular/discussions/43784
https://docs.google.com/document/d/1SBeMuko8_T15t511iJgCktyO9tnggFvBda-XLMeE9r8/edit?resourcekey=0-fs2SACbxWCThdLyILtNNSA#heading=h.bb41yd99dqib
https://stackblitz.com/edit/ng-standalone
https://twitter.com/IgorMinar/status/1447593478834188291
https://marmicode.io/blog/your-angular-module-is-a-scam
Zainstaluj teraz i czytaj tylko dobre teksty!
2. Storybook trzykrotnie przyspiesza
Korzystacie w swoich projektach ze Storybooka? Jeśli tak, to pewnie zainteresuje Was fakt, że do najnowszej wersji 6.4 trafiła flaga `storyStoreV7`, która sprawia, że paczka jaką zwraca Webpack jest 50% mniejsza, a sam Storybook ładuje się trzykrotnie szybciej. Jeśli zastanawiacie się nad jej tajemniczą nazwą, to potraktujcie ją jako preview technologii, która od Storybook 7.0 będzie tą domyślną.
Jeśli Storybook, który utrzymujecie nie jest duży, być może nie dostrzegliście do tej pory problemów z wydajnością. Tak jak to zwykle bywa, takie problemy objawiają się przy osiągnięciu odpowiedniej skali, a taką osiągnął między innymi Shopify. Ich Storybook liczy obecnie ponad tysiąc Stories, ważył 16MB i ładował się ponad 1,5s. To właśnie Shopify wraz z Webpackiem i chromatic (firmą stojąca za Storybookiem) przygotował tą optymalizację. Jak osiągnięto tak imponujący wynik? Do tej pory Storybook serwowany był jako jedna ogromna paczka. Nowy flaga umożliwia podzielenie paczki na mniejsze kawałki zawierające pojedyncze story i ich lazy loading. Proste optymalizacje zawsze są najlepsze.
Dobrze słyszeć, że narzędzia z których na co dzień korzysta się w projekcie stają się szybsze. Osobiście czekam jednak na optymalizacje, którą pod koniec notatki wspominają jej autorzy, czyli lazy compilation. Ta eksperymentalna funkcja Webpacka ma umożliwić kompilowanie tylko tych zasobów, o które poprosi klient, co powinno znacząco przyśpieszyć kompilację w trybie deweloperskim (jeśli obecnie pracujemy nad jednym story, wszystkie pozostałe nie będą kompilowane).
Źródła:
https://storybook.js.org/blog/storybook-on-demand-architecture/
3. Parcel v2
Wy też myśleliście, że Parcel umarł śmiercią naturalną podczas ostatniego napływu modnych narzędzi do budowania aplikacji? Jak się okazuje nic bardziej mylnego i w zeszłym tygodniu światło dzienne ujrzała wersja druga tego narzędzia
Jeśli nie kojarzycie Parcela, to w telegraficznym skrócie jest to narzędzie do budowania aplikacji, które za zadanie postawiło sobie ograniczenie konfiguracji wymaganej do rozpoczęcia pracy nad projektem do minimum. Z Parcela korzystają takie tuzy jak Atlassian, Adobe czy Microsoft.
Z dokumentacji wynika, że jeśli nie polegacie zbytnio na zewnętrznych wtyczkach to migracja powinna przebiec raczej bezboleśnie. A migrować warto, bo lista zmian wygląda naprawdę imponująco. Poniżej znajdziecie jej skróconą wersję, a po więcej szczegółów jak zwykle odsyłamy do źródeł.
Nowości w Parcel v2:
- Nowy system wtyczek (niestety nie jest on wstecznie kompatybilny, więc wtyczki do Parcel 1 muszą zostać przepisane od podstaw)
- Domyślnie włączony Tree Shaking
- Domyślnie włączony Differential Building (ES modules)
- Automatyczne dzielenie kodu obsługujące współdzielenie paczek i ich współbieżne ładowanie
- Optymalizacja wydajności (Parcel dołącza do grona frontendowych narzędzi napisanych w Rust!)
- Konwersja i optymalizacja obrazów (włączając takie formaty jak AVIF i WebP)
- Usprawniony Hot Module Reloading (z wsparciem dla React Fast Refresh)
- Lazy Development Module (ta funkcjonalność w Webpacku 5 jest jeszcze w preview)
Zainstaluj teraz i czytaj tylko dobre teksty!
Źródła:
https://parceljs.org/blog/v2/
https://parceljs.org/getting-started/migration/