Początek roku, to okres pełen motywacji i nowych postanowień. Zanim ruszymy do przodu, warto jednak rzucić okiem za siebie i podsumować najważniejsze wydarzenia w ekosystemie JavaScript w 2022 roku.
1. TypeScript w 2022
Na przestrzeni tegorocznych przeglądów wielokrotnie powtarzałem, że TypeScript bardzo dojrzał, a co za tym idzie stał się nudny jak flaki z olejem. Patrząc na rok 2022 okazuje się jednak, że nawet w takim dojrzałym i stabilnym ekosystemie wydarzyło się kilka ciekawych rzeczy.
Typy zmierzają do JavaScript?
Początkiem marca Microsoft, czyli firma stojąca z TypeScript, przygotowała proposal do standardu JavaScript. Jego treść skupiała się na wzbogaceniu składni o typy podobne do tych znanych z TypeScript. Aby zachować kompatybilność wsteczną oraz nie zmieniać fundamentalnych założeń języka, Microsoft zaproponował, że interpreter będzie traktował typy tak samo jak komentarze (tj. będzie je kompletnie ignorował). Dzięki temu typy nie będą miały wpływu na logikę, ale sprawią, że kod będzie czytelniejszy, a deweloperzy będą znacznie wydajniejsi dzięki podpięciu language servera i type checkera do swojego IDE.
Przedstawione powyżej zachowanie może do złudzenia przypominać obecne zachowanie JSDoc. Nowy proposal ma jednak znaczną przewagę nad tym rozwiązaniem. Po pierwsze proponowany przez Microsoft format jest znacznie zwięźlejszy i czytelniejszy niż JSDoc. Po drugie możliwości JSDoc są mocno ograniczone (za jego pomocą ciężko jest zdefiniować chociażby generyki).
// —-----------------------------------
// JSDoc
// —-----------------------------------
/**
* @param {string} p1 - A string param.
* @param {string=} p2 - An optional param (Closure syntax)
* @param {string} [p3] - Another optional param (JSDoc syntax).
* @param {string} [p4="test"] - An optional param with a default value
* @return {string} This is the result
*/
function stringsStringStrings(p1, p2, p3, p4) {
/* ... */
}
// —-----------------------------------
// JavaScript with type annotations
// —-----------------------------------
function stringsStringStrings(p1: string, p2?: string, p3?: string, p4 = "test"): string {
/* ... */
}
Czy typy w JavaScript oznaczają koniec TypeScript? To zależy. W początkowych etapach typy w JavaScript prawdopodobnie będą dużo bardziej prymitywne niż te znane z JavaScript. TypeScript oferuje też funkcjonalności opierające się na transpilacji kodu, takie jak chociażby enumy. Pod tym względem JavaScript raczej nigdny nie dogoni TypeScript.
Od marca prace nad Proposalem nie posunęły się znacznie do przodu i utknęły w Stage 1 (cały proces składa się z 4 etapów). Proposalem opiekuje się jednak Microsoft, więc wydaje mi się, że tamat wróci jeszcze na tapet w 2023 roku.
Nowe funkcjoalności w TypeScript
Na przestrzeni minionego roku dostaliśmy aż 3 nowe wersje TypeScript: 4.7, 4.8 i 4.9. Nie ma jednak sensu, żebyście zagłębiali się w poszczególne wersje, bo… są nudne jak flaki z olejem. Programiści pracujący nad TypeScriptem poświęcili masę czasu na optymalizację wydajnośći i lepszą obsługę kolejnych przypadków brzegowych. Wśród nowości na pierwszy plan przebija się jednak jedna nowa funkcjonalność – operator satisfies
.
Tłumacząc działanie nowego operatora posłużę się przykładem z dokumentacji od Microsoftu. Załóżmy, że potrzebujemy otypować następujący kod:
// Each property can be a string or an RGB tuple.
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
};
// We want to be able to use array methods on 'red'...
const redComponent = palette.red.at(0);
// or string methods on 'green'...
const greenNormalized = palette.green.toUpperCase();
W pierwszej chwili do głowy przyjść może zdefiniowanie typu Color
oraz wykorzystanie typu Record
. Niestety, w takim przypadku zmuszeni jesteśmy wykonywać niebezpieczną operację rzutowania:
type Color = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
type Palette = Record<Color, string | RGB>
const palette: Palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
};
// We want to be able to use array methods on 'red'...
const redComponent = (palette.red as RGB).at(0);
// or string methods on 'green'...
const greenNormalized = (palette.green as string).toUpperCase();
Obejściem tego problemu ma być nowy operator satisfies, który będzie walidował typ w momencie przypisania, ale nie będzie miał wpływu na typ ewaluowany przez TypeScript. Brzmi skomplikowanie, ale na prostym przykładzie dobrze widać, o co chodzi:
type Color = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
type Palette = Record<Color, string | RGB>
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies Palette;
// Both of these methods are still accessible!
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();
// —-----------------------------------
// Example errors caught by satisfies
// —-----------------------------------
const spelloPalette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255] // Such typos are now caught
} satisfies Palette;
// Missing properties are now caught
const missingColorPalette = {
red: [255, 0, 0],
bleu: [0, 0, 255]
} satisfies Palette;
const wrongColorTypePalette = {
red: [255, 0], // Such typos are now also caught
green: "#00ff00",
bleu: [0, 0, 255]
} satisfies Palette;
Zastosowanie nowego oparetara znalazłem już chociażby w SvelteKit i jestem przekonany, że przyda się on w niejednym projekcie.
Przyszłość TypeScript
Przyszły rok zaczniemy od releasu TypeScript 5.0 początkiem marca. Należy jednak pamiętać, że TypeScript nie stosuje się do Semantic Versioning. Co za tym idzie nowa duża wersja powinna przynieść paczkę dużych i ekscytujących zmian, ale nie koniecznie brakinc changes. Buszują po repozytorium TypeScript możemy doszykać się kilku nadchodzących nowości. Po pierwsze TypeScript przepisany został do modułów (do tej pory bazował na namespace), co zmniejszyło paczkę o 25% i przyśpieszyło kompilację o 10%. Dekoratory z funkcjonalności nie wychodzącej poza kompilator, mają stać się kompatybilne z dekoratorami znajdującymi sie w 3-cim etapie standaryzacji JavaScript (przyp: standaryzacja składa się z 4 etapów, bardzo rzadko zdarza się sytuacja kiedy funkcjonalność na 3-cim etapie zostaje usunięta). TypeScript pracuje też nad natywnym wsparciem importów dla dowolnego typu plików. Jak widzicie jest tego sporo i wszystkie znaki na niebie i ziemi wskazują, że rok 2023 będzię dla TypeScript zdecydowanie ciekawszy niż 2022.
Zainstaluj teraz i czytaj tylko dobre teksty!
2. React w 2022
W tym roku wreszcie się doczekaliśmy! Po dwóch latach od opublikowania React 17, światło dzienne ujrzała kolejna wersja frameworka od Mety. W odróżnieniu od poprzedniej wersji, która z punktu widzenia API nie wprowadzała znaczących nowości, ta wręcz pęka w szwach od nowych funkcjonalności.
React 18 i Concurrent Mode
Jeśli nigdy nie słyszeliście o trybie współbieżnym w React to pewnie drapiecie się teraz w głowę próbując połączyć współbieżność z jednowątkowym JavaScriptem. Jak się okazuje współbieżność polega tutaj na możliwości kolejkowania renderów, nadawaniu im priorytetów oraz dodaniu możliwości przerwania renderu w trakcie.
Sztandarowym przykładem, kiedy skorzystamy na trybie współbieżnym, są interakcje z polem tekstowym służącym do wyszukiwania elementów w długiej liście. Kiedy użytkownik naciska klawisz na klawiaturze, nawet najmniejsze opóźnienie w aktualizacji pola tekstowego daje mu poczucie, że coś jest nie tak. Natomiast jeśli chodzi o wyniki wyszukiwania, to drobne opóźnienie jest wręcz naturalne. W takiej sytuacji jasno widać, że do czynienia mamy tu z priorytetowym i niepriorytetowym renderem. Ponadto jeśli użytkownik jest w stanie pisać szybciej niż React renderuje komponenty, to renderowanie pośrednich stanów wyszukiwania jest niepożądane. W tym przypadku do gry wchodzi anulowanie renderowania, które w momencie pojawienia się nowszej wersji komponentu przerwie poprzedni render.
Część z was prawdopodobnie zwróci uwagę na fakt, że podobny efekt można było do tej pory osiągnąć odpowiednio wykorzystując funkcję debounce. Różnica pomiędzy stosowanymi obecnie technikami i trybem współbieżnym polega na tym, że obecnie renderowanie odbywa się synchronicznie i w jego trakcie użytkownik nie jest w stanie interaktować ze stroną. W trybie współbieżnym render o niższym priorytecie zostanie przerwany w momencie, kiedy w kolejce pojawi się render o wyższym priorytecie. Dzięki przerwaniu mniej priorytetowego renderu strona powinna sprawiać wrażenie dużo bardziej responsywnej.
Oczywiście na przykładzie z inputem możliwości wykorzystania trybu współbieżnego się nie kończą. Dzięki jego zastosowaniu w przyszłości możliwe będzie na przykład renderowanie poza ekranem, dzięki któremu cachować będzie można już odwiedzone strony lub zawczasu renderować strony, które najprawdopodobniej użytkownik odwiedzi w dalszej kolejności. Dzięki zastosowaniu priorytetówm, takie akcje nie będą wpływać na responsywność interfejsu, bo będą po prostu odbywać się z niższym priorytetem.
Wystarczy już teorii – przejdźmy do mięska. Nisko priorytetowe aktualizacje opakowane muszą zostać w metodę startTransition
, do której dostać możemy się poprzez hook useTransition()
.
export default function App() {
const [isPending, startTransition] = useTransition();
const [count, setCount] = useState(0);
function handleClick() {
startTransition(() => {
setCount((c) => c + 1);
});
}
const advancedCounter = useMemo(
() => <AdvancedCounter count={count} />,
[count]
);
return (
<>
{/*Immediately after clicking the button */}
{/*div changes opacity until low priority render finishes*/}
<div style={{ opacity: isPending ? 0.8 : 1 }}>
{/*Component below will update only after all priority renders finish.*/}
{/*If user spams increase button only last render will be visible to user.*/}
{advancedCounter}
<button onClick={handleClick}>Increase counter</button>
</div>
</>
);
}
Do API trafił również hook useDeferredValue
. Jeśli hook ten wywołany zostanie w priorytetowym renderze to zwróci wartość z poprzedniego renderu. W ten sposób referencja nie zmieni się, co pozwoli nam uniknąć nadmiernego renderowania. Kiedy priorytetowe rendery zostaną wykonane, zakolejkowany zostanie niskopriorytetowy render w którym hook zwróci nową wartość i wyrenderowany zostanie poprawny komponent.
export default function App() {
const [count, setCount] = useState(0);
function handleClick() {
setCount((c) => c + 1);
}
const deferredCount = useDeferredValue(count);
const advancedCounter = useMemo(
() => <AdvancedCounter count={deferredCount} />,
[deferredCount]
);
return (
<>
<div>
{/*This component will update only after all priority renders finish.*/}
{/*If user spams increase button only last render will be visible to user.*/}
{advancedCounter}
<button onClick={handleClick}>Increase counter</button>
</div>
</>
);
}
Co czeka React w 2023 roku?
W 2023 roku raczej nie doczekamy się kolejego dużego wydania Reacta. Nie zmienia to faktu, że wiemy nad czym obecnie pracuje zespół rozwijający bibliotekę. Możemy się również spodziewać, że cześć z tych funkcjonalności nie będzie czekać na React 19 i trafi do kolejnych wersji React 18.
Chyba największą i najbardziej wyczekiwaną funkcjonalnością nad którą obecnie trwają prace są React Server Components. Więcej o nich przeczytacie w jendej z kolejnej sekcji tego posta. Nie są to jednak jedyna aktualnie rozwijana funkjonalność. Zespół pracuje również nad komponentem <Offscreen />
, którym umożliwi renderowanie komponentów poza ekranem, bez konieczności montowania ich do struktury HTML. Inną rozwijaną w pocie czoła funkcjonalnością jest kompilator automatycznie dodający useMemo
i useCallback
wszędzie tam gdzie to potrzebne.
3. Angular w 2022
W 2022 roku w ekosystemie Angulara działo się naprawdę sporo. Rok zaczęlismy od pożegnania angular.js. Niedługo później wraz z Angularem 14 zaprezentowana została pierwsza eksperymentalna implementacja Standalone Componetns. Na przestrzeni całego roku zespół z Google pracował w pocie czoła i Angular 15 dostarczył nam stabilną wersję tej od dawna wyczekiwanej funkcjonalności.
Koniec wsparcia dla angular.js
Wsparcie dla angular.js miało zostać wycofane już w lipcu 2021 roku. Niedługo po wybuchu pandemii ze względu na wywołane przez nią perturbacje, Google zdecydował się dać firmom dodatkowe pół roku wsparcia. Dodatkowy czas dobiegł końca z początkiem 2022 roku. Wtedy to oficjalnie pożegnaliśmy angular.js, (znany też jako Angular 1.x). Oczywiście framework dalej dostępny jest w npm, ale Google nie planuje już dalszych łatek.
Angular 14 i 15
Miniony rok w ekosystemie Angulara upłynął właściwie pod jednym hasłem: Standalone Components. Co ważne, nie mówimy tutaj o funkcjonalności samej w sobie, ale także o masie pracy wykonanej naokoło. Aby umożliwić tworzenie aplikacji pozbawionych modułów zespuł przygotował między innymi funkcję inject()
, API do towrzenia funkcyjnych guardów czt nowe API do inicjalizacji aplikacji.
Angularowe moduły mają 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.
Na przestrzeni lat dobre praktyki wokół Angulara ewoluowały i obecnie najczęściej uskuteczniamy jest schemat SCAM ( Single Component Angular Module). Jedgo jedyną wadą takiego podejścia jest spora ilość generowanego boilerplate.
@Component({
selector: 'vived-my-component',
template:`
<div>
<h2>Today is {{today | date}} </h2>'
<CustomComponent />
</div>
`
})
export class MyComponent {
readonly today = new Date();
}
@NgModule({
imports: [ CommonModule, CustomComponentModule ],
declarations: [ MyComponent ],
exports: [ MyComponent ],
})
export class MyComponentModule { }
Począwszy od Angulara 15, większość modułów może być generowana automatycznie dzięki dodaniu do dekoratora parametru standalone
. Za pomocą tego jednego prostego triku Angular poddany został bardzo skutecznej diecie.
@Component({
selector: 'vived-my-component',
standalone: true.,
imports: [ CommonModule, CustomComponent ]
template: `
<div>
<h2>Today is {{today | date}} </h2>'
<CustomComponent />
</div>
`
})
export class MyComponent {
readonly today = new Date();
}
Właściwie wszystkie funkcjonalności dodane do Angulara w 2022 roku, skupione były ukruceniu zbędnego boilerpate’u. Zobaczcie tylko, jak odchudzono chociażby Router API.
// This is how you bootstrap application with modules
@NgModule({
declarations: [AppComponent],
imports: [
RouterModule.forRoot(
[
{ path: '/home', component: HomeComponent },
{ path: '**', redirectTo: '/home' },
],
{
preloadingStrategy: PreloadAllModules,
}
),
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
],
bootstrap: [AppComponent],
})
export class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule)
// This is how you bootstrap application without modules
bootstrapApplication(AppComponent, {
providers: [
provideRouter([
{ path: '/home', component: HomeComponent },
{ path: '**', redirectTo: '/home' },
], withPreloading(PreloadAllModules)),
provideHttpClient(withInterceptors([AuthInterceptor]))
]
});
Angular 16 i 17
Przglądając oficjalną roadmapę i GitHub’a Angulara możemy już zgadywać na czym skupiał się będzie zespuł Angulara w 2023 roku. Po pierwsze minony rok kręcił się wokół renderowania po stronie serwera i nie umknęło to uwadze zespołu. W 2023 roku możemy się więc spodziewać powrotu do tematu i kilku ciekawych RFC w tym obszarze. Po drugie, kolejnym dużym tematej jest umożliwienie wycięcia z zależności Angulara biblioteki zone.js. Dla przypomnienia, jest to trochę nadgryziona zębem czasu biblioteka, która patchuje większość API przeglądarki i umożliwia Angularowi „magiczne” wykrywanie zmian w aplikacji.
4. Vue w 2022
W 2022 roku ukazała się tylko jedna duża wersja Vue. Co zaskakujące, nie było to kolejne Vue 3, ale finalna wersja Vue 2. Poza tym wreszcie doczekaliśmy się renderowania po stonie serwera z prawdziwego zdarzenia w postaci Nuxt 3.
Vue 2.7
Vue 2.7 to ostatnie wydanie Vue 2 i będzie ono wspierane do końca 2023 roku. W największym skrócie, wersja ta dodaje najważniejsze funkcjonalności z Vue 3 do Vue 2. Dzięki takiemu podejściu, inkrementalna migracja powinna stać się znacznie prostsza. Czy tak rzeczywiście będzie? O tym przekonamy sie pod koniec 2023 roku, kiedy to okaże się jak dużo projektów nie wyrobiło się z ową migracją na czas.
Największą funkcjonalnością jaka trafiła do Vue 2.7 jest Composition API
, przez wielu żartobliwie nazywane React Hooks dla Vue. Na API to składają się 3 składowe: Reactivity API (ref
i reactive
), Lifecycle Hooks (onMounted
/onUnmounted
) oraz Dependency Injection (provide
/inject
). Co ważne, zarówno zachowanie jak i typy wszystkich nowych funkcjonalności są w 100% kompatybilne z Vue 3.
// Old Options API
<script>
export default {
data() {
return {
name: 'John',
};
},
mounted() {
console.log(`Hello ${this.name}`);
},
};
</script>
<template>
<p>Hello {{ name }}!</p>
</template>
// New Composition API
<script setup>
const name = ref(0);
onMounted(() => {
console.log(`Hello ${name.value}`);
});
</script>
<template>
<p>Hello {{ name }}!</p>
</template>
Vue 3.3
Vue 3.3 miało ukazać się pod koniec 2022 roku, ale jego premiera została przeniesiona na rok 2023. Możemy już jednak uchylić rąbka tajemnicy i zerknąć na to, co czeka nas w Vue w przyszłym roku.
<Suspense>
umożliwiać ma renderowania tymczasowego komponentu do czasu, aż wszystkie asynchroniczne komponenty w drzewie zostaną załadowane.
<Suspense>
<!-- component with nested async dependencies -->
<Dashboard />
<!-- loading state via #fallback slot -->
<template #fallback>
Loading...
</template>
</Suspense>
Drugą wyczekiwaną funkcjonalnością jest Reactivity Transform
, czyli szereg usprawnień do Composition API
. Wszystkie metody z Reactive API
(m.in ref
i computed
) doczekają się wersji poprzedzonych $
. Będą to makra, które umożliwią łatwiejszy dostęp do danych, kosztem dodatkowego kroku kompilacji.
// Before Vue 3.3
<script setup>
import { ref } from 'vue'
let count = ref(0)
console.log(count.value)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
// Vue 3.3
<script setup>
let count = $ref(0)
console.log(count)
function increment() {
count++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Podczas Vue Amsterdam Evan You podzielił się też dalekosiężnymi planami zespołu. Jak zaznacza, na razie plany te są w powijakach i mogą jeszcze ulec zmianom. Po zakończeniu pracy nad Vue 3.3 zespół rozważa skupienie się nad nową strategią kompilacji na wzór tej znanej z SolidJS. Dla przypomnienia, SolidJS to framework do złudzenia przypominający Reacta, ale chwalący się lepszą wydajnością ze względu na porzucenie Virtual DOM. Opcjonalne wycięcie Virtual DOM mogłoby znacząco odchudzić paczkę pobieraną przez klienta i pozwoliłoby oszczędzić pamięc potrzebną na przechowywanie wirtualnej struktury komponentów.
5. Renderowanie po stronie serwera w 2022
Ach, co to był za rok dla renderowania po stronie serwera! Właściwie każdy duży framework otrzymał albo sporą aktualizację w tym temacie, albo zupełnie nową bibliotekę z masą nowości (Vue – Nuxt 3, Svelte – SvelteKit, React – React Server Components, Remix, Next.js 13). Na rynku pojawiło się też sporo innowacyjnych rozwiązań, nie powiązanych z istniejącymi frameworkami. W tym miejscu namienić muszę chodziażby Qwik, który stara się zrewolucjonizować podejście do hydracji, czy Astro, który spopularyzował innowacyjne podejście Dynami Islands.
React Server Components i Next.js 13
Od kiedy komponenty renderowane po stronie serwera zostały zaprezentowane światu przez Dana Abramova minęły już ponad dwa lata. Jeśli nie kojarzycie koncepcji, to w dużym skrócie React umożliwiał będzie wyrenderowanie pojedynczego komponentu po stronie serwera i przesłanie kodu HTML do klienta. Mi podejście takie mocno przypomina na PHP, bo możliwe jest wykonywanie zapytań do bazy danych bezpośrednio z komponentów.
// Note.js - Server Component
import db from 'db';
async function Note(props) {
const note = await db.posts.get(props.id);
return (
<div>
<h2>{note.title}</h2>
<section>{note.body}</section>
</div>
);
}
Niestety React Server Components mają też swoje ograniczenia. Komponenty tego typu nie mogą przechowywać stanu (useState
jest zakazany), nie mogą być importowane przez komponenty renderowane po stronie klienta i do działania wymagają meta frameworku jak Next.js, Hyrdogen czy Gatsby. Temat ten jest niezwykle zawiły i zasługuje na osobny wpis na naszym blogu, ale jeśli chcielibyście dowiedzieć się więcej, to gorąco polecam ten artykuł.
Pomimo tego, że React Server Components nadal znajdują sie w fazie alphy, na rynku pojawiły się już pierwsze frameworki, które je wykorzystują (Next.js, Gatsby). W szczególności Next.js 13 odbił się szerokim echem społeczności, bo nową architekturę oparł całkowicie na React Server Components. Interesującym efektem ubocznym takiej architektury jest fakt, że dopóki wprost nie zdefiniujemy komponentu jako renderowanego po stronie klienta, to cała aplikacja renderowana będzie tylko i wyłącznie po stronie serwra.
Qwik
Qwik wywraca do góry nogami paradygmaty wszystkich współczesnych narzędzi do renderowania po stronie serwera, poprzez podważenie zasadności procesu hydracji. Hydracja, to proces po stronie klienta, podczas którego wyrenderowana po stronie serwera aplikacja ładuje kod JavaScript, a następnie powtarza całą logikę inicjalizacyjną. Dopiero po zakończeniu procesu hydracji aplikacja staje się responsywna. Dzięki takiej taktyce, klient właściwie od razu widzi docelową treść i dopiero po chwili może z nią wejść w interakcję.
Alternatywa, którą oferuje Qwik, to wznawiane wykonania (ang. Resumable). Paradygmat ten pozwala zapisać stan aplikacji po stronie serwera w taki sposób, aby po stronie klienta możliwe było natychmiastowe wznowienie wykonywania kodu. Brzmi skomplikowanie? Na szczęście skomplikowana logika skrzętnie schowana jest pod przystępnym API, które całą magię chowa przed programistą.
No dobra, a co jeśli naprawdę chcę wiedzieć jak ta magia działa pod spodem? W krótkiej formie jaką są nasze przeglądy nie podejmę się wytłumaczenia jak działają bebechy frameworka i najlepiej będzie jeśli sami sięgnięcie do dokumentacji. W ogromnym skrócie, kod komponentów kompilowany jest do osobnych paczek, a sam stan aplikacji zapisywany jest w DOM w “JSONie na sterydach”. Do klienta wysyłany jest wyrenderowany HTML oraz 1KB kody samego Qwika. Ten od razu pobiera kodu wszystkich niezbędnych komponentów, jednak zamiast łączyć je od razu z DOM’em czeka na pierwsze interakcje użytkownika.
Na zkończenie, jeszcze mała wzmianka o tym, dlaczego warto śledzić dalsze losy Qwika. Za jego rozwój odpowiedzialny jest prawdziwy zespół All-Stars – Miško Hevery (twórca anguar.js), Manu Almeida (twórca Gina i Stencila) oraz Adam Bradley (twórca Ionica i Stencila). Jeśli ktoś ma zrewolucjonizować Frontend, to właśnie zepół takich wteranów.
Astro
Astro to framework, który spopularyzował tak zwaną architekturę wyspową. Zakłada ona, że na tworzonej przez nas stronie/aplikacji wśród morza statycznej treści (np. menu nawigacji, artykuł) znajdują się małe wyspy interaktywnych komponentów (np. formularz zapisu do newslettera). Takie podejście pozwala wyrenderować całą aplikację po stronie serwera, a do klienta wysyłany jest tylko niewielkich rozmiarów kod komponentów-wysp.
Astro wyposażony został w bardzo zaawansowany mechanizm definiowania kiedy ma zostac pobrany kod dynamicznych komponentów oraz kiedy powinny one zostać poddane hydracji. Wśród dostępnych opcj mamy między innymi doładowanie dopiero kiedy komponent będzie widoczny na ekranie czy doładowanie tylko i wyłącznie kiedy spełnione są odpowiednie media-query.
Astro na tle konkurencji wyróżnia się też niezależnością od frameworku. Jest to pierwsza biblioteka od bardzo dawna, która naprawdę nie faworyzuje żadnego rozwiązania, a na dodatek pozwala mieszać je dowoli między soba.
—
import HomeLayout from '../layouts/HomeLayout.astro'
import MyReactComponent from '../components/MyReactComponent.jsx';
import MySvelteComponent from '../components/MySvelteComponent.svelte';
const data = await fetch('API_URL').then(r => r.json());
---
<HomeLayout>
<MyReactComponent client:load name={data.name}>
<MySvelteComponent avatar={data.avatar}/>
</MyReactComponent>
</HomeLayout>
Zainstaluj teraz i czytaj tylko dobre teksty!
6. Node.js, Deno i Bun w 2022
Rok temu wydawało się, że sytuacja jeśli chodzi o środowiska uruchomieniowe JavaScript jest raczej stabilna. Node dominuje silną ręką, a Deno z roku na rok skrupulatnie powiększa swoją niszę. W połowie tego roku nastąpiło jednak prawdziwe trzęsienie ziemi, gdy do gry wkroczył Bun z finansowaniem 7M$.
Bun
Bun to alternatywa typu drop-in dla Node.js i Deno. O ile Deno swój marketing skupia na rozwiązywaniu problemów Node.js, to Bun stawia nacisk na wydajność. Według benchmarków opublikowanych przez autora nowego środowiska uruchomieniowego przewaga jest naprawdę istotna
Wydajność Buna wynika z dwóch czynników. Po pierwsze w odróżnieniu od konkurencji jest on napisany nie w C++, a w języku Zig. Jeśli jeszcze o nim nie słyszeliście, to nie macie się czym martwić, bo nadal jest on dosyć niszową alternatywą dla C++ i Rust.
Drugim czynnikiem wpływającym na wydajność Buna jest wykorzystanie silnika JavaScriptCore od Apple. Jak się okazuje, JavaScriptCore jest szybszy od V8. Różnica jest natomiast stosunkowo niewielka i to nie z niej wynikają główne różnice w wydajności między Node i Bun.
Bun wprowadze też całkiem sporo usprawnień względem Node.js . Podobnie jak w przypadku Deno, TypeScript jest natywnie wspieranym językiem. Bun jest też alternatywą dla NPM, która jest nawet do 100x szybsza. To sprawia, że bez problemu może stawać w szranki chociażby z pnpm. Bun udostępnia API do tworzenia makr, czyli kodu generowanego podczas kompilacji z dostępem do drzewa AST i metadanych typów. Do pakowania aplikacji Bun wykorzystuje niesamowicie szybki bundler. Szczerze mówiąc, wszystkie te funkcjonalności brzmią dużo ciekawiej niż pusty slogan: szybszy Node.js.
Deno
Na odpowiedź Deno na pojawienie się nowego gracza nie musieliśmy długo czekać. Już kilka tygodni po premierze wersji alpha Buna, zespół stojący za Deno zapowiedział dodanie kompatybilności z npm oraz sporą poprawy wydajności serwera http.
Wsparcie dla npma było szczególnie kontrowersyją decyzją, biorąc pod uwagę jak długo Deno odcinało się od niego grubą kreską dopatrując się w nim źródła wielu problemów naszego ekosystemu. Trzeba jednak pamietać, że npm w połączeniu z Deno będzie działał zupełnie inaczej niż w połączeniu z Node.js. Po pierwsze, Deno nie będzie wymagać uruchamiania npm install
. Po drugie, Deno nie będzie tworzyć katalogu node_modules
,a pobrane paczki przechowywane będą globalnie. Po trzecie, importy z npm będą oznaczone specjalnym prefixem npm:
import { chalk } from "npm:chalk@5";
Node.js
Node.js nie rozwija się aż tak dynamicznie jak Bun czy Deno. Wszyscy zgodzimy się jednak, że w przypadku takiego staruszka cenimy raczej stabilność i przewidywalność, niż wywracające świat do góry nogami funkcjonalności. Niemniej, Node.js również dostarczył w tym roku kilka ciekawych nowości.
Zaczniem od funkcjonalności, która ucieszyła wszystkich, którzy kiedykolwiek próbowali pisać kod uruchamiany zarówno na serwerze jak i w przeglądarce. Do Node 18 trafiło bowiem wsparcie dla metody fetch()
oparte o popularną bibliotekę Undici
. Co prawda implementacja fetch()
w przeglądarkach i Node.js nie są w 100% kompatybilne, ale różnice są na tyle niewielkie, że większości projektów nawet ich nie zauważycie.
fetch("http://example.com/api/endpoint")
.then((response) => {
// Do something with response
})
.catch(function (err) {
console.log("Unable to fetch -", err);
});
Kolejną ciekawą nową funkcjonalnością jest wbudowany test runner. Oznacza to, że do testowania Node nie będziemy już więcej potrzebować bibliotek takich jak Jest czy Vitest.
test('synchronous passing test', (t) => {
// This test passes because it does not throw an exception.
assert.strictEqual(1, 1);
});
test('asynchronous passing test', async (t) => {
// This test passes because the Promise returned by the async
// function is not rejected.
assert.strictEqual(1, 1);
});