Cóż to był za tydzień! Zaczęliśmy od plotek o nowych silnikach przeglądarek na iOS, potem dostaliśmy TypeScript 4.7 i nowe RFC do Next.js, a tydzień zamkneliśmy ogłoszeniem wskrzeszenia Lerny. Dużo się działo, więc bez zbędnego przedłużania zapraszamy do lektury!
1. Koniec monopolu WebKit na iOS
Nie od dziś wiadomo, że Safari to nowy Internet Explorer. Web deweloperzy nieustannie narzekają na to, że przeglądarka od Apple jest mocno w tyle – zarówno jeśli chodzi o nowe funkcjonalności jak i ilość drobnych błędów i glitchy. Skutkuje to oczywiście regularnymi problemami z działaniem niektórych aplikacji na Safari (kto próbował korzystać na niej z Google Meet ten wie).
O ile na komputerach nie jest to krytyczny problem (bo zawsze można odpalić inną przeglądarkę), o tyle na telefonach z jabłuszkiem sytuacja nie wygląda już tak kolorowo. Zgodnie z regulaminem App Store, wszystkie przeglądarki zmuszone są do wykorzystywania silnika WebKit przygotowanego przez Apple. Do tej pory jako deweloperom nie pozostało nam nic innego jak przykładać szczególną uwagę do tej przeglądarki, a jako użytkownicy pozostawaliśmy bezbronni. W zeszłym tygodniu do gry wkroczyła Unia Europejska i być może czeka nas spora zmiana sytuacji na rynku.
W ręce portalu “The Register” trafiła nie upubliczniona wersja Digital Markets Act (DMA). Według portalu najnowsza wersja tego przygotowanego przez Unię Europejską prawodawstwa zawiera zapis zmuszający Apple do zaakceptowania na systemie iOS autorskich silników przeglądarek.
Jakie konsekwencje miałoby wejście w życie takiego prawodawstwa? Przede wszystkim zyskamy możliwość ucieczki z podbramkowych sytuacji, kiedy kolejna aplikacja odmawia nam współpracy. Poza tym na iPhone zawita w końcu wiele od dawna wyczekiwanych funkcjonalności jak chociażby push notyfikacje. Pośrednio, w związku z pojawieniem się konkurencji, może to zmusić Apple do szybszego iterowania nad swoją przeglądarką. Innymi słowy – jako użytkownicy i deweloperzy definitywnie wygrywamy.
Zanim przejdziemy do szczegółów dotyczących szans wejścia przepisów w życie, omówmy sobie czym właściwie jest DMA. Cytując za wikipedią jest to “propozycja legislacyjna Komisji Europejskiej, która ma na celu zwiększenie konkurencji na europejskich rynkach cyfrowych, poprzez uniemożliwienie dużym firmom nadużywania ich siły rynkowej i umożliwienie nowym graczom wejścia na rynek”. Jeśli o wasze uszy obiły się plotki takie jak iPhone z złączem USB-C, czy dodanie do Androida możliwości usuwania pre-instalowanych aplikacji, to wywodziły się one właśnie z zapisów w DMA.
Prace nad DMA trwają już od 2020 roku i od samego początku towarzyszy im niesamowity lobbing ze strony technologicznych gigantów. Ponownie cytując za wikipedią, Google zarezerwował na ten cel 8M$, a Apple 2M$. Warto nadmienić, że nie jest to gra z jasno ustalonymi drużynami: Big Tech vs Unia Europejska. Dla przykładu Facebook z ustawą wiąże duże nadzieje na wyjście z impasu zabraniającego im w celach reklamowych zbierać dane o użytkownikach na iOS, ale równocześnie nie odpowiadają mu inne zapisy w ustawie. Można więc powiedzieć, że mamy tutaj do czynienia z grą każdy na każdego.
Po długich negocjacjach w marcu tego roku wreszcie udało się ustalić ostateczną postać legislacji, a 22 maja upubliczniono przygotowane dokumenty. Przecieki jakie otrzymał portal “The Register” dotyczą poprawek do opublikowanych kilka dni temu dokumentów i do tej pory nie zostały jeszcze potwierdzone. Aby legislacje DMA weszły w życie muszą zostać zatwierdzone zarówno przez Radę Unii Europejskiej jak i Parlament Europejski. Spekuluje się, że najwcześniej nastąpi to początkiem 2023 roku.
Źródła:
https://en.wikipedia.org/wiki/Digital_Markets_Act
https://pl.wikipedia.org/wiki/Digital_Markets_Act
https://www.theregister.com/2022/04/26/apple_ios_browser/
Zainstaluj teraz i czytaj tylko dobre teksty!
2. Lerna wraca do żywych
Lerna to rozwiązanie do zarządzania monorepo, które przez lata było bardzo popularne w świecie JavaScript. Na przestrzeni kilku ostatnich lat projekt napotkał spore turbulencje. W połowie 2020 roku w repozytorium lerny pojawiło się Issue sugerujące, że narzędzie nie jest już aktywnie wspierane. Patrząc na historię commitów ciężko nie zgodzić – większość zmian dotyczy aktualizacji zależności oraz napraw krytycznych podatności. W efekcie długich dyskusji do readme trafił zapis informujący o końcu wsparcia dla projektu.
Przez ostatnie dwa lata na miejscu Lerny wyrastać zaczęły nowe alternatywy. Część użytkowników postanowiła przerzucić się na rozwiązania oferowane przez managery paczek, czyli odpowiednio `npm workspaces` i `yarn workspaces`. Użytkownicy szukający bardziej zaawansowanych funkcjonalności zaczęli natomiast powoli odpływać w stronę Nx, który powoli z narzędzia dedykowanego dla Angulara zmieniał się w narzędzie niezależne od frameworku. W kwietniu tego roku Nx w końcu wyprzedził Lernę w ilości pobrań z npm i stał się najpopularniejszym narzędziem do zarządzania JavaScriptowym monorepo.
Dla formalności nadmienie jeszcze, że ostatnio w grze pojawił się nowy gracz w postaci TurboRepo. Projekt został zakupiony w zeszłym roku przez Vercela, co wiązało się zapewne ze sporym zastrzykiem gotówki. Nie zmienia to jednak faktu, że projekt wciąż znajduje się na dosyć wczesnym etapie rozwoju i nie rozwinął jeszcze w całości swoich możliwości.
Wróćmy jednak do tematu Lerny. Po prawie dwóch latach stagnacji, wydarzyło się niespodziewane – projekt wrócił do życia. Co jeszcze dziwniejsze został on wskrzeszony przez firmę Nrwl, czyli autorów uzyskującego obecnie na popularności Nx’a. Oznacza to, że dwa najpopularniejsze narzędzie do zarządzania monorepo są obecnie w rękach jednej firmy.
Jak wynika z notatki dotyczącej przejęcia lerny, ma być ona aktywnie rozwijana. Krótkoterminowo firma zamierza skupić się na naprawieniu krytycznych błędów i podatności, natomiast długoterminowo wprowadzane będą funkcjonalności, które umożliwią bezproblemową integrację między Lerną i Nx’em. Jeszcze nie wiadomo co to dokładnie oznacza, ale na mój “chłopski rozum” możemy spodziewać się, że migracja do Nx stanie się jeszcze prostsza, a Lerna będzie raczej utrzymywana niż rozwijana.
Źródła:
https://blog.nrwl.io/lerna-is-dead-long-live-lerna-61259f97dbd9
https://www.npmtrends.com/lerna-vs-nx
https://github.com/lerna/lerna/issues/2703
3. Nowy system renderowania zmierza do Next.js
W minionym tygodniu zaprezentowane zostało RFC nowej funkcjonalności w Next.js – Layouts. Ma ona umożliwić łatwiejsze budowanie aplikacji z zagnieżdżonymi ścieżkami.
Do tej pory routing w Next.js oparty był o strukturę katalogów. Aby wyrenderować stronę `/home/about` należało w katalogu `/home/about` umieścić plik index.js, który eksportował reactowy komponent. Architektura taka była bardzo wygodna, ale wymagała sporej dozy powtarzalności (każdy komponent musiał importować predefiniowany layout).
Nowe RFC proponuje rozwiązanie powyższego problemu. Zamiast plików index.js, nowa architektura zakłada istnienie plików layout.js i page.js. Pierwszy z nich odpowiedzialny będzie za renderowanie “skorupy” strony, natomiast drugi za renderowanie “właściwej treści”. Jest to więc rozwiązanie do złudzenia przypominające Angularowy `<router-outlet>` i Reactowy `<Outlet />`.
// Root layout (app/layout.js)
// - Applies to all routes
export default function RootLayout({ children }) {
return (
<html>
<body>
<Header />
{children}
<Footer />
</body>
</html>
)
}
// Regular layout (app/dashboard/layout.js)
// - Applies to route segments in app/dashboard/*
export default function DashboardLayout({ children }) {
return (
<>
<DashboardSidebar />
{children}
</>
)
}
// Page Component (app/dashboard/analytics/page.js)
// - The UI for the `app/dashboard/analytics` segment
// - Matches the `acme.com/dashboard/analytics` URL path
export default function AnalyticsPage() {
return (
<main>...</main>
)
}
Co ważne, każdy z plików layout.js będzie mógł asynchronicznie ładować swoje dane, tak aby możliwa była na przykład dynamiczna zmiana zawartości menu na podstawie preferencji użytkownika. Next.js planuje wykorzystać funkcjonalności ze świeżego React 18, aby umożliwić zarówno współbieżne renderowanie jak i pobieranie danych.
To jeszcze nie koniec zmian, bo Vercel zapowiedział wkrótce opublikowanie kolejnej części RFC. Ja czekam z niecierpliwością, bo zawierać ma ona między innymi możliwość pre-renderowania stron, na które może wejść użytkownik.
Źródła:
https://nextjs.org/blog/layouts-rfc
Zainstaluj teraz i czytaj tylko dobre teksty!
4. Typescript 4.7
Pamiętacie te stare dobre czasy, kiedy duże wydania popularnych języków wychodziły raz na kilka lat? O Javie 8 pisało się latami i w mojej głowie to nadal “ta nowa” wersja Javy. Od tego czasu jako branża wiele się nauczyliśmy i do tworzenia języków podchodzimy dużo bardziej inkrementalnie. Wsiadając do pociągu wydań (wolne tłumaczenie “Release Train”) zyskaliśmy prostsze aktualizacje i szybszą pętlę zwrotną, ale gdzieś po drodze całkowicie zatraciliśmy radość z odkrywania nowych wydań naszych ukochanych języków.
Nie popadajmy jednak w marazm i spójrzmy na nowości, jakie przynosi wydany w tym tygodniu TypeScript 4.7. Największą ze wszystkich jest wsparcie dla ECMAScript Module w Node.js. Moduły w JavaScript to prawdziwa góra lodowa, dlatego wszystkim, którzy nie są zaznajomieni z tematem polecam film od Przeprogramowanych poświęcony w całości temu zagadnieniu.
Co składa się na wsparcie dla ECMAScript Modules? Przede wszystkim do tsconfig.json i package.json trafiają nowe pola, które decydować będą o tym w jaki sposób traktowane są importy w danym module. Do tego dochodzi wsparcie dla plików `.mts` i `.cts`, które kompilowane będą odpowiednio do plików `.mjs` i ‘.cjs`. Oczywiście jak zawsze w przypadku modułów, temat jest dużo bardziej złożony, dlatego wszystkich zainteresowanych szczegółami odsyłam do notki Microsoftu.
Nowa edycja TypeScript jak zwykle przynosi również drobne usprawnienia inferencji typów. Wersja 4.7 będzie lepiej radzić sobie z analizą przepływu kodu, dzięki czemu w wielu przypadkach będzie w stanie zawęzić inferowane typy. Usprawniona została również inferencja typów metod w obiektach.
declare function f<T>(arg: {
produce: (n: string) => T,
consume: (x: T) => void }
): void;
// Works
f({
produce: () => "hello",
consume: x => x.toLowerCase()
});
// Works
f({
produce: (n: string) => n,
consume: x => x.toLowerCase(),
});
// Was an error, now works.
f({
produce: n => n,
consume: x => x.toLowerCase(),
});
// Was an error, now works.
f({
produce: function () { return "hello"; },
consume: x => x.toLowerCase(),
});
// Was an error, now works.
f({
produce() { return "hello" },
consume: x => x.toLowerCase(),
});
Kolejną nowością jest wprowadzenie Instantiation Expressions, które w zwięzły sposób pozwalają zawęzić typ danej funkcji, czy klasy. Ciężko to wytłumaczyć, więc po prostu zerknijcie na przykład poniżej:
interface Box<T> { value: T; }
function makeBox<T>(value: T) {
return { value };
}
// In TypeScript 4.6
const makeStringBox: (string: string) => Box<string> = makeBox;
// In TypeScript 4.7
const makeStringBox = makeBox<string>;
makeStringBos(42); // TypeScript correctly rejects this.
Na koniec zostawiłem sobie prawdziwą bombę: wariancja typów. Nie wchodząc w szczegóły, wariancja w przypadku generycznych typów pozwala określić czy dane typy mogą zostać do siebie przypisane na podstawie struktury dziedziczenia ich generycznych parametrów. Dogłębne wytłumaczenie jak działa wariancja zdecydowanie nie mieści się w formule naszych przeglądów, dlatego zainteresowanych odsyłam do dokumentacji Kotlina, który zdecydował się na taką samą nomenklaturę jak TypeScript (czyli słowa kluczowe in i out). Po co nam wariancja w strukturalnie typowanym języku? Na “chłopski rozum”, wszystkie typy można rozwinąć i porównać. Jak się okazuje, w przypadku skomplikowanych zagnieżdżonych typów sprawdzenie wariancji będzie znacznie szybsze niż ich rozwijanie.
interface Animal {
animalStuff: any;
}
interface Dog extends Animal {
dogStuff: any;
}
type Getter<out T> = () => T;
type Setter<in T> = (value: T) => void;
let animalGetter: Getter<Animal>;
let dogGetter: Getter<Dog>;
animalGetter = dogGetter; // This will work
dogGetter = animalGetter; // This will throw an error
let animalSetter: Setter<Animal>;
let dogSetter: Setter<Dog>;
animalSetter = dogSetter; // This will throw an error
dogSetter = animalSetter; // This will work
Źródła:
https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/