Optymalizacja aplikacji Next.js za pomocą Nx

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ Nx to framework do budowania, który ułatwia optymalizację, wydajne skalowanie aplikacji i inne funkcje, takie jak współdzielone biblioteki i komponenty. W tym artykule przyjrzymy się, jak możemy efektywnie skalować aplikacje Next.js za pomocą Nx.

W tym artykule omówimy, jak zoptymalizować i zbudować wydajną aplikację Next.js przy użyciu Nx i jego bogatych funkcji. Omówimy, jak skonfigurować serwer Nx, jak dodać wtyczkę do istniejącego serwera oraz koncepcję monorepo z praktyczną wizualizacją.

Jeśli jesteś programistą, który chce skutecznie optymalizować aplikacje i tworzyć składniki wielokrotnego użytku w różnych aplikacjach, w tym artykule dowiesz się, jak szybko skalować aplikacje i jak pracować z Nx. Aby kontynuować, będziesz potrzebować podstawowej wiedzy na temat frameworka Next.js i TypeScript.

Co to jest Nx?

Nx to platforma kompilacji typu open source, która pomaga w projektowaniu, testowaniu i kompilowaniu w dowolnej skali — bezproblemowo integruje się z nowoczesnymi technologiami i bibliotekami, zapewniając jednocześnie niezawodny interfejs wiersza poleceń (CLI), buforowanie i zarządzanie zależnościami. Nx oferuje programistom zaawansowane narzędzia CLI i wtyczki do nowoczesnych frameworków, testów i narzędzi.

W tym artykule skupimy się na tym, jak Nx współpracuje z aplikacjami Next.js. Nx zapewnia standardowe narzędzia do testowania i stylizacji w aplikacjach Next.js, takie jak Cypress, Storybook i styled-components. Nx ułatwia tworzenie monorepo dla Twoich aplikacji, tworząc przestrzeń roboczą, która może pomieścić kod źródłowy i biblioteki wielu aplikacji, umożliwiając współdzielenie zasobów między aplikacjami.

Dlaczego warto korzystać z Nx?

Nx zapewnia programistom rozsądną ilość funkcjonalności po wyjęciu z pudełka, w tym szablony do kompleksowego (E2E) testowania aplikacji, bibliotekę stylów i monorepo.

Korzystanie z Nx ma wiele zalet, a kilka z nich omówimy w tej sekcji.

  • Wykonywanie zadań w oparciu o wykres
    Nx wykorzystuje rozproszone wykonywanie zadań w oparciu o wykresy i buforowanie obliczeń w celu przyspieszenia zadań. System zaplanuje zadania i polecenia za pomocą systemu wykresów, aby określić, który węzeł (tj. aplikacja) powinien wykonać każde zadanie. Obsługuje to wykonywanie aplikacji i efektywnie optymalizuje czas wykonania.
  • Testowanie
    Nx zapewnia wstępnie skonfigurowane narzędzia testowe do testów jednostkowych i testów E2E.
  • Buforowanie
    Nx przechowuje również zbuforowany wykres projektu. Dzięki temu może ponownie przeanalizować tylko zaktualizowane pliki. Nx śledzi pliki zmienione od ostatniego zatwierdzenia i pozwala testować, budować i wykonywać działania tylko na tych plikach; pozwala to na odpowiednią optymalizację podczas pracy z dużą bazą kodu.
  • Wykres zależności
    Wykres zależności wizualnych umożliwia sprawdzenie, w jaki sposób składniki współdziałają ze sobą.
  • Magazyn w chmurze
    Nx zapewnia również przechowywanie w chmurze i integrację z GitHub, dzięki czemu możesz udostępniać łącza członkom zespołu w celu przeglądania dzienników projektów.
  • Udostępnianie kodu
    Tworzenie nowej biblioteki współdzielonej dla każdego projektu może być dość trudne. Nx eliminuje tę komplikację, pozwalając Ci skupić się na podstawowej funkcjonalności Twojej aplikacji. Dzięki Nx możesz udostępniać biblioteki i komponenty między aplikacjami. Możesz nawet udostępniać kod wielokrotnego użytku między aplikacjami front-end i back-end.
  • Wsparcie dla monorepo
    Nx zapewnia jeden obszar roboczy dla wielu aplikacji. Dzięki tej konfiguracji jedno repozytorium GitHub może pomieścić źródło kodu dla różnych aplikacji w obszarze roboczym.
Więcej po skoku! Kontynuuj czytanie poniżej ↓

Nx dla bibliotek do publikacji

Nx umożliwia tworzenie bibliotek do publikacji. Jest to niezbędne, gdy masz biblioteki, których będziesz używać poza monorepo. W każdym przypadku, gdy rozwijasz organizacyjne komponenty UI z integracją Nx Storybook, Nx utworzy komponenty do publikacji wraz z Twoimi historiami. Komponenty do publikowania mogą skompilować te komponenty w celu utworzenia pakietu biblioteki, który można wdrożyć w rejestrze zewnętrznym. Podczas generowania biblioteki należy użyć opcji --publishable , w przeciwieństwie do --buildable , która służy do generowania bibliotek używanych tylko w monorepo. Nx nie wdraża automatycznie publikowalnych bibliotek; możesz wywołać kompilację za pomocą polecenia, takiego jak nx build mylib (gdzie mylib jest nazwą biblioteki), które następnie utworzy zoptymalizowany pakiet w folderze dist / mylib , który można wdrożyć w zewnętrznym rejestrze.

Nx daje możliwość utworzenia nowego obszaru roboczego z Next.js jako ustawienia wstępnego lub dodania Next.js do istniejącego obszaru roboczego.

Aby utworzyć nowy obszar roboczy z Next.js jako ustawieniem wstępnym, możesz użyć następującego polecenia:

 npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo

To polecenie utworzy nowy obszar roboczy Nx z aplikacją Next.js o nazwie „todo” i styled-components jako biblioteką stylów.

Następnie możemy dodać aplikację Next.js do istniejącego obszaru roboczego Nx za pomocą następującego polecenia:

 npx nx g @nrwl/next:app

Budowanie aplikacji Next.js i Nx

Wtyczka Nx dla Next.js zawiera narzędzia i executory do uruchamiania i optymalizacji aplikacji Next.js. Aby rozpocząć, musimy stworzyć nową przestrzeń roboczą Nx z next jako presetem:

 npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo

Powyższy blok kodu wygeneruje nowy obszar roboczy Nx i aplikację Next.js. Otrzymamy monit o skorzystanie z Nx Cloud. W tym samouczku wybierzemy „Nie”, a następnie poczekamy na zainstalowanie naszych zależności. Gdy to zrobimy, powinniśmy mieć drzewo plików podobne do tego:

 happynrwl ┣ apps ┃ ┣ todo ┃ ┣ todo-e2e ┃ ┗ .gitkeep ┣ libs ┣ node_modules ┣ tools ┣ .editorconfig ┣ .eslintrc.json ┣ .gitignore ┣ .prettierignore ┣ .prettierrc ┣ README.md ┣ babel.config.json ┣ jest.config.js ┣ jest.preset.js ┣ nx.json ┣ package-lock.json ┣ package.json ┣ tsconfig.base.json ┗ workspace.json

W folderze apps będziemy mieć naszą aplikację Next.js „todo”, ze wstępnie skonfigurowanym testem E2E dla aplikacji do zrobienia. Wszystko to jest generowane automatycznie za pomocą potężnego narzędzia Nx CLI.

Aby uruchomić naszą aplikację, użyj polecenia npx nx serve todo . Po zakończeniu obsługi aplikacji powinieneś zobaczyć poniższy ekran:

Strona główna wygenerowana przez Nx dla nowej aplikacji
Domyślna strona Nx dla nowej aplikacji. (duży podgląd)

Budowanie API

W tym momencie skonfigurowaliśmy obszar roboczy. Następnym krokiem jest zbudowanie API CRUD, którego będziemy używać w aplikacji Next.js. W tym celu użyjemy Express; aby zademonstrować obsługę monorepo, zbudujemy nasz serwer jako aplikację w obszarze roboczym. Najpierw musimy zainstalować wtyczkę Express dla Nx, uruchamiając to polecenie:

 npm install --save-dev @nrwl/express

Gdy to zrobisz, jesteśmy gotowi do skonfigurowania naszej aplikacji Express w udostępnionym obszarze roboczym. Aby wygenerować aplikację Express, uruchom poniższe polecenie:

 npx nx g @nrwl/express:application --name=todo-api --frontendProject=todo

Polecenie nx g @nrwl/express:application wygeneruje aplikację Express, do której możemy przekazać dodatkowe parametry specyfikacji; aby określić nazwę aplikacji, użyj flagi --name ; aby wskazać aplikację frontonu, która będzie korzystać z aplikacji Express, przekaż nazwę aplikacji w naszym obszarze roboczym do --frontendProject . W przypadku aplikacji Express dostępnych jest kilka innych opcji. Gdy to zrobisz, będziemy mieli zaktualizowaną strukturę plików w folderze apps z dodanym do niej folderem todo-api .

 happynrwl ┣ apps ┃ ┣ todo ┃ ┣ todo-api ┃ ┣ todo-e2e ┃ ┗ .gitkeep …

Folder todo-api to szablon programu Express z plikiem wejściowym main.ts

 /** * This is not a production server yet! * This is only minimal back end to get started. */ import * as express from 'express'; import {v4 as uuidV4} from 'uuid'; const app = express(); app.use(express.json()); // used instead of body-parser app.get('/api', (req, res) => { res.send({ message: 'Welcome to todo-api!' }); }); const port = process.env.port || 3333; const server = app.listen(port, () => { console.log(`Listening at http://localhost:${port}/api`); }); server.on('error', console.error);

Będziemy tworzyć nasze trasy w tej aplikacji. Na początek zainicjujemy tablicę obiektów z dwiema parami klucz-wartość, item i id , tuż pod deklaracją app.

 /** * This is not a production server yet! * This is only minimal back end to get started. */ import * as express from 'express'; import {v4 as uuidV4} from 'uuid'; const app = express(); app.use(express.json()); // used instead of body-parser let todoArray: Array<{ item: string; id: string }> = [ { item: 'default todo', id: uuidV4() }, ]; …

Następnie ustawimy trasę do pobrania wszystkich list rzeczy do zrobienia w app.get() :

 … app.get('/api', (req, res) => { res.status(200).json({ data: todoArray, }); }); …

Powyższy blok kodu zwróci bieżącą wartość todoArray . Następnie będziemy mieli trasy do tworzenia, aktualizowania i usuwania elementów do zrobienia z tablicy.

 … app.post('/api', (req, res) => { const item: string = req.body.item; // Increment ID of item based on the ID of the last item in the array. let id: string = uuidV4(); // Add the new object to the array todoArray.push({ item, id }); res.status(200).json({ message: 'item added successfully', }); }); app.patch('/api', (req, res) => { // Value of the updated item const updatedItem: string = req.body.updatedItem; // ID of the position to update const id: string = req.body.id; // Find index of the ID const arrayIndex = todoArray.findIndex((obj) => obj.id === id); // Update item that matches the index todoArray[arrayIndex].item = updatedItem res.status(200).json({ message: 'item updated successfully', }); }); app.delete('/api', (req, res) => { // ID of the position to remove const id: string = req.body.id; // Update array and remove the object that matches the ID todoArray = todoArray.filter((val) => val.id !== id); res.status(200).json({ message: 'item removed successfully', }); }); …

Aby utworzyć nowy element do zrobienia, potrzebujemy tylko wartości nowego elementu w postaci ciągu. Wygenerujemy identyfikator, zwiększając identyfikator ostatniego elementu w tablicy na serwerze. Aby zaktualizować istniejący element, przekażemy nową wartość elementu i identyfikator obiektu, który ma zostać zaktualizowany; na serwerze przejrzelibyśmy każdy element metodą forEach i zaktualizowali element w miejscu, w którym identyfikator pasuje do identyfikatora wysłanego z żądaniem. Na koniec, aby usunąć element z tablicy, wraz z żądaniem wysyłamy identyfikator elementu do usunięcia; następnie filtrujemy tablicę i zwracamy nową tablicę wszystkich elementów niezgodnych z identyfikatorem wysłanym z żądaniem, przypisując nową tablicę do zmiennej todoArray .

Uwaga: Jeśli zajrzysz do folderu aplikacji Next.js, powinieneś zobaczyć plik proxy.conf.json z poniższą konfiguracją:

 { "/api": { "target": "http://localhost:3333", "secure": false } }

Powoduje to utworzenie serwera proxy, dzięki czemu wszystkie wywołania interfejsu API do tras pasujących do /api są kierowane do serwera todo-api .

Generowanie stron Next.js za pomocą Nx

W naszej aplikacji Next.js wygenerujemy nową stronę, home główną i komponent item. Nx udostępnia nam narzędzie CLI do łatwego tworzenia strony:

 npx nx g @nrwl/next:page home

Po uruchomieniu tego polecenia otrzymamy monit o wybranie biblioteki stylów, której chcemy użyć dla strony; w tym artykule wybierzemy styled-components . Voila! Powstaje nasza strona. Aby utworzyć komponent, uruchom npx nx g @nrwl/next:component todo-item ; spowoduje to utworzenie folderu component z komponentem todo-item .

Zużycie API w aplikacji Next.js

W każdej rzeczy do zrobienia będziemy mieć dwa przyciski do edycji i usuwania rzeczy do zrobienia. Funkcje asynchroniczne wykonujące te akcje są przekazywane jako rekwizyty ze strony głównej.

 … export interface TodoItemProps { updateItem(id: string, updatedItem: string): Promise<void>; deleteItem(id: string): Promise<void>; fetchItems(): Promise<any>; item: string; id: string; } export const FlexWrapper = styled.div` width: 100%; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #ccc; padding-bottom: 10px; margin-top: 20px; @media all and (max-width: 470px) { flex-direction: column; input { width: 100%; } button { width: 100%; } } `; export function TodoItem(props: TodoItemProps) { const [isEditingItem, setIsEditingItem] = useState<boolean>(false); const [item, setNewItem] = useState<string | null>(null); return ( <FlexWrapper> <Input disabled={!isEditingItem} defaultValue={props.item} isEditing={isEditingItem} onChange={({ target }) => setNewItem(target.value)} /> {!isEditingItem && <Button onClick={() => setIsEditingItem(true)} > Edit </Button>} {isEditingItem && <Button onClick={async () => { await props.updateItem(props.id, item); //fetch updated items await props.fetchItems(); setIsEditingItem(false) }}> Update </Button>} <Button danger onClick={async () => { await props.deleteItem(props.id); //fetch updated items await await props.fetchItems(); }} > Delete </Button> </FlexWrapper> ); }

W przypadku funkcji aktualizacji mamy dane wejściowe, które są wyłączone, gdy stan isEditingItem ma wartość false . Po kliknięciu przycisku „Edytuj” przełącza stan isEditingItem na true i wyświetla przycisk „Aktualizuj”. W tym miejscu składnik wejściowy jest włączony, a użytkownik może wprowadzić nową wartość; po kliknięciu przycisku „Aktualizuj” wywołuje funkcję updateItem z przekazanymi parametrami i przełącza isEditingItem z powrotem na false .

W komponencie strony home mamy asynchroniczne funkcje realizujące operację CRUD.

 … const [items, setItems] = useState<Array<{ item: string; id: string }>>([]); const [newItem, setNewItem] = useState<string>(''); const fetchItems = async () => { try { const data = await fetch('/api/fetch'); const res = await data.json(); setItems(res.data); } catch (error) { console.log(error); } }; const createItem = async (item: string) => { try { const data = await fetch('/api', { method: 'POST', body: JSON.stringify({ item }), headers: { 'Content-Type': 'application/json', }, }); } catch (error) { console.log(error); } }; const deleteItem = async (id: string) => { try { const data = await fetch('/api', { method: 'DELETE', body: JSON.stringify({ id }), headers: { 'Content-Type': 'application/json', }, }); const res = await data.json(); alert(res.message); } catch (error) { console.log(error); } }; const updateItem = async (id: string, updatedItem: string) => { try { const data = await fetch('/api', { method: 'PATCH', body: JSON.stringify({ id, updatedItem }), headers: { 'Content-Type': 'application/json', }, }); const res = await data.json(); alert(res.message); } catch (error) { console.log(error); } }; useEffect(() => { fetchItems(); }, []); …

W powyższym bloku kodu mamy fetchItems , który zwraca todoArray z serwera. Następnie mamy funkcję createItem , która pobiera ciąg znaków; parametr jest wartością nowej rzeczy do zrobienia. Funkcja updateItem przyjmuje dwa parametry, identyfikator aktualizowanego elementu oraz wartość updatedItem . A funkcja deleteItem usuwa element pasujący do przekazanego identyfikatora.

Aby wyrenderować element do zrobienia, mapujemy stan items :

 … return ( <StyledHome> <h1>Welcome to Home!</h1> <TodoWrapper> {items.length > 0 && items.map((val) => ( <TodoItem key={val.id} item={val.item} id={val.id} deleteItem={deleteItem} updateItem={updateItem} fetchItems={fetchItems} /> ))} </TodoWrapper> <form onSubmit={async(e) => { e.preventDefault(); await createItem(newItem); //Clean up new item setNewItem(''); await fetchItems(); }} > <FlexWrapper> <Input value={newItem} onChange={({ target }) => setNewItem(target.value)} placeholder="Add new item…" /> <Button success type="submit"> Add + </Button> </FlexWrapper> </form> </StyledHome> ); …

Nasz serwer i frontend są już skonfigurowane. Aplikację API możemy obsłużyć uruchamiając npx nx serve todo-api , a dla aplikacji Next.js uruchamiamy npx nx serve todo . Kliknij przycisk "Kontynuuj", a zobaczysz stronę z wyświetlanym domyślnym elementem do zrobienia.

Strona rzeczy do zrobienia aplikacji z domyślną listą rzeczy do zrobienia na serwerze.
Strona elementów do zrobienia w aplikacji. (duży podgląd)

Mamy teraz działającą aplikację Next.js i Express współpracującą w jednym obszarze roboczym.

Nx ma inne narzędzie CLI, które pozwala nam przeglądać wykres zależności naszej aplikacji w naszym uruchomieniu terminala. npx nx dep-graph , a powinniśmy zobaczyć ekran podobny do obrazka poniżej, przedstawiający wykres zależności naszej aplikacji.

Wykres zależności pokazujący relacje między aplikacjami w obszarze roboczym
Wykres dep. aplikacji. (duży podgląd)

Inne polecenia CLI dla Nx

  • nx list
    Wyświetla listę aktualnie zainstalowanych wtyczek Nx.
  • nx migrate latest
    Aktualizuje pakiety w package.json do najnowszej wersji.
  • nx affected
    Wykonuje akcję tylko na dotkniętych lub zmodyfikowanych aplikacjach.
  • nx run-many --target serve --projects todo-api,todo
    Uruchamia polecenie docelowe we wszystkich wymienionych projektach.

Wniosek

Jako ogólny przegląd Nx, w tym artykule omówiono, co oferuje Nx i jak ułatwia nam pracę. Przeszliśmy również przez konfigurowanie aplikacji Next.js w obszarze roboczym Nx, dodawanie wtyczki Express do istniejącego obszaru roboczego i używanie funkcji monorepo do przechowywania więcej niż jednej aplikacji w naszym obszarze roboczym.

Pełny kod źródłowy znajdziesz w repozytorium GitHub. Aby uzyskać dodatkowe informacje o Nx, zapoznaj się z dokumentacją lub dokumentacją Nx dla Next.js.