Optimieren von Next.js-Anwendungen mit Nx
Veröffentlicht: 2022-03-10In diesem Artikel erfahren Sie, wie Sie mithilfe von Nx und seinen umfangreichen Funktionen eine leistungsstarke Next.js-Anwendung optimieren und erstellen. Wir gehen durch die Einrichtung eines Nx-Servers, das Hinzufügen eines Plugins zu einem bestehenden Server und das Konzept eines Monorepos mit einer praktischen Visualisierung.
Wenn Sie als Entwickler Anwendungen optimieren und anwendungsübergreifend wiederverwendbare Komponenten effektiv erstellen möchten, zeigt Ihnen dieser Artikel, wie Sie Ihre Anwendungen schnell skalieren und wie Sie mit Nx arbeiten. Um mitzumachen, benötigen Sie grundlegende Kenntnisse des Next.js-Frameworks und von TypeScript.
Was ist Nx?
Nx ist ein Open-Source-Build-Framework, das Sie beim Entwerfen, Testen und Erstellen in jeder Größenordnung unterstützt – es lässt sich nahtlos in moderne Technologien und Bibliotheken integrieren und bietet gleichzeitig eine robuste Befehlszeilenschnittstelle (CLI), Caching und Abhängigkeitsverwaltung. Nx bietet Entwicklern fortschrittliche CLI-Tools und Plugins für moderne Frameworks, Tests und Tools.
In diesem Artikel konzentrieren wir uns darauf, wie Nx mit Next.js-Anwendungen funktioniert. Nx bietet Standardtools zum Testen und Gestalten in Ihren Next.js-Anwendungen, wie Cypress, Storybook und styled-components. Nx erleichtert ein Monorepo für Ihre Anwendungen und erstellt einen Arbeitsbereich, der den Quellcode und die Bibliotheken mehrerer Anwendungen enthalten kann, sodass Sie Ressourcen zwischen Anwendungen gemeinsam nutzen können.
Warum Nx verwenden?
Nx bietet Entwicklern eine angemessene Menge an sofort einsatzbereiten Funktionen, einschließlich Boilerplates für End-to-End (E2E)-Tests Ihrer Anwendung, eine Styling-Bibliothek und ein Monorepo.
Die Verwendung von Nx bringt viele Vorteile mit sich, und wir werden einige davon in diesem Abschnitt durchgehen.
- Graphbasierte Aufgabenausführung
Nx verwendet verteilte graphbasierte Aufgabenausführung und Berechnungs-Caching, um Aufgaben zu beschleunigen. Das System plant Aufgaben und Befehle unter Verwendung eines Graphensystems, um zu bestimmen, welcher Knoten (dh Anwendung) jede Aufgabe ausführen soll. Dadurch wird die Ausführung von Anwendungen gehandhabt und die Ausführungszeit effizient optimiert. - Testen
Nx bietet vorkonfigurierte Testwerkzeuge für Unit-Tests und E2E-Tests. - Caching
Nx speichert auch das zwischengespeicherte Projektdiagramm. Dadurch kann es nur aktualisierte Dateien erneut analysieren. Nx verfolgt Dateien, die seit dem letzten Commit geändert wurden, und ermöglicht es Ihnen, nur diese Dateien zu testen, zu erstellen und Aktionen auszuführen. Dies ermöglicht eine ordnungsgemäße Optimierung, wenn Sie mit einer großen Codebasis arbeiten. - Abhängigkeitsdiagramm
Mit dem visuellen Abhängigkeitsdiagramm können Sie untersuchen, wie Komponenten miteinander interagieren. - Cloud-Speicher
Nx bietet auch Cloud-Speicher und GitHub-Integration, sodass Sie Links mit Teammitgliedern teilen können, um Projektprotokolle zu überprüfen. - Code-Sharing
Das Erstellen einer neuen gemeinsam genutzten Bibliothek für jedes Projekt kann ziemlich anstrengend sein. Nx eliminiert diese Komplikation und gibt Ihnen die Möglichkeit, sich auf die Kernfunktionalität Ihrer App zu konzentrieren. Mit Nx können Sie Bibliotheken und Komponenten anwendungsübergreifend gemeinsam nutzen. Sie können sogar wiederverwendbaren Code zwischen Ihren Front-End- und Back-End-Anwendungen austauschen. - Unterstützung für Monorepos
Nx bietet einen Arbeitsbereich für mehrere Anwendungen. Mit diesem Setup kann ein GitHub-Repository die Codequelle für verschiedene Anwendungen in Ihrem Arbeitsbereich enthalten.
Nx für veröffentlichbare Bibliotheken
Mit Nx können Sie veröffentlichungsfähige Bibliotheken erstellen. Dies ist wichtig, wenn Sie Bibliotheken haben, die Sie außerhalb des Monorepos verwenden werden. In jedem Fall, in dem Sie organisatorische UI-Komponenten mit Nx Storybook-Integration entwickeln, erstellt Nx veröffentlichungsfähige Komponenten neben Ihren Geschichten. Die veröffentlichbaren Komponenten können diese Komponenten kompilieren, um ein Bibliothekspaket zu erstellen, das Sie in einer externen Registrierung bereitstellen können. Sie würden beim Generieren der Bibliothek die Option --publishable
verwenden, im Gegensatz zu --buildable
, das zum Generieren von Bibliotheken verwendet wird, die nur im Monorepo verwendet werden. Nx stellt die publizierbaren Bibliotheken nicht automatisch bereit; Sie können den Build über einen Befehl wie nx build mylib
(wobei mylib
der Name der Bibliothek ist), der dann ein optimiertes Bundle im Ordner dist
/ mylib
erstellt, das in einer externen Registrierung bereitgestellt werden kann.
Nx bietet Ihnen die Möglichkeit, einen neuen Arbeitsbereich mit Next.js als Voreinstellung zu erstellen oder Next.js zu einem vorhandenen Arbeitsbereich hinzuzufügen.
Um einen neuen Arbeitsbereich mit Next.js als Voreinstellung zu erstellen, können Sie den folgenden Befehl verwenden:
npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo
Dieser Befehl erstellt einen neuen Nx-Arbeitsbereich mit einer Next.js-App namens „todo“ und mit styled-components
als Stilbibliothek.
Dann können wir die Next.js-Anwendung mit dem folgenden Befehl zu einem vorhandenen Nx-Arbeitsbereich hinzufügen:
npx nx g @nrwl/next:app
Erstellen einer Next.js- und Nx-Anwendung
Das Nx-Plugin für Next.js enthält Tools und Executors zum Ausführen und Optimieren einer Next.js-Anwendung. Um zu beginnen, müssen wir einen neuen Nx-Arbeitsbereich mit next
als Voreinstellung erstellen:
npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo
Der obige Codeblock generiert einen neuen Nx-Arbeitsbereich und die Next.js-Anwendung. Wir werden aufgefordert, Nx Cloud zu verwenden. Für dieses Tutorial wählen wir „Nein“ und warten dann, bis unsere Abhängigkeiten installiert sind. Sobald das erledigt ist, sollten wir einen ähnlichen Dateibaum haben:
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
Im apps
-Ordner haben wir unsere Next.js-Anwendung „todo“ mit dem vorkonfigurierten E2E-Test für die To-Do-App. All dies wird automatisch mit dem leistungsstarken Nx-CLI-Tool generiert.
Um unsere App auszuführen, verwenden Sie den npx nx serve todo
. Sobald Sie mit der Bereitstellung der App fertig sind, sollten Sie den folgenden Bildschirm sehen:
Erstellen der API
An dieser Stelle haben wir den Arbeitsbereich eingerichtet. Als Nächstes erstellen wir die CRUD-API, die wir für die Next.js-Anwendung verwenden werden. Dazu verwenden wir Express; Um die Monorepo-Unterstützung zu demonstrieren, werden wir unseren Server als Anwendung im Arbeitsbereich erstellen. Zuerst müssen wir das Express-Plugin für Nx installieren, indem wir diesen Befehl ausführen:
npm install --save-dev @nrwl/express
Sobald dies erledigt ist, können wir unsere Express-App im bereitgestellten Arbeitsbereich einrichten. Führen Sie den folgenden Befehl aus, um eine Express-App zu generieren:
npx nx g @nrwl/express:application --name=todo-api --frontendProject=todo
Der Befehl nx g @nrwl/express:application
generiert eine Express-Anwendung, an die wir zusätzliche Spezifikationsparameter übergeben können; um den Namen der Anwendung anzugeben, verwenden Sie das Flag --name
; Um die Front-End-Anwendung anzugeben, die die Express-App verwendet, übergeben Sie den Namen einer App in unserem Arbeitsbereich an --frontendProject
. Für eine Express-App stehen einige weitere Optionen zur Verfügung. Wenn dies erledigt ist, haben wir eine aktualisierte Dateistruktur im apps
-Ordner mit dem hinzugefügten todo-api
Ordner.
happynrwl ┣ apps ┃ ┣ todo ┃ ┣ todo-api ┃ ┣ todo-e2e ┃ ┗ .gitkeep …
Der todo-api
Ordner ist ein Express-Boilerplate mit einer main.ts
Eintragsdatei.
/** * 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);
Wir werden unsere Routen in dieser App erstellen. Zu Beginn initialisieren wir ein Array von Objekten mit zwei Schlüssel-Wert-Paaren, item
und id
, direkt unter der App-Deklaration.
/** * 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() }, ]; …
Als nächstes richten wir die Route zum Abrufen aller To-do-Listen unter app.get()
:
… app.get('/api', (req, res) => { res.status(200).json({ data: todoArray, }); }); …
Der obige Codeblock gibt den aktuellen Wert von todoArray
. Anschließend haben wir Routen zum Erstellen, Aktualisieren und Entfernen von To-Do-Elementen aus dem Array.
… 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', }); }); …
Um ein neues Aufgabenelement zu erstellen, benötigen wir lediglich den Wert des neuen Elements als Zeichenfolge. Wir generieren eine ID, indem wir die ID des letzten Elements im Array auf dem Server erhöhen. Um ein vorhandenes Element zu aktualisieren, übergeben wir den neuen Wert für das Element und die ID des zu aktualisierenden Elementobjekts. Auf dem Server würden wir jedes Element mit der forEach
-Methode durchlaufen und das Element an der Stelle aktualisieren, an der die ID mit der mit der Anfrage gesendeten ID übereinstimmt. Um schließlich ein Element aus dem Array zu entfernen, würden wir die ID des zu entfernenden Elements mit der Anfrage senden; Dann filtern wir das Array und geben ein neues Array aller Elemente zurück, die nicht mit der ID übereinstimmen, die mit der Anfrage gesendet wurde, und weisen das neue Array der todoArray
Variablen zu.
Hinweis: Wenn Sie im Anwendungsordner Next.js nachsehen, sollten Sie eine Datei proxy.conf.json
mit der folgenden Konfiguration sehen:
{ "/api": { "target": "http://localhost:3333", "secure": false } }
Dadurch wird ein Proxy erstellt, der es allen API-Aufrufen ermöglicht, Routen, die mit /api
übereinstimmen, auf den todo-api
Server auszurichten.
Generieren von Next.js-Seiten mit Nx
In unserer Next.js-Anwendung generieren wir eine neue Seite, home
und eine item-Komponente. Nx stellt uns ein CLI-Tool zur Verfügung, mit dem wir ganz einfach eine Seite erstellen können:
npx nx g @nrwl/next:page home
Beim Ausführen dieses Befehls werden wir aufgefordert, die Stilbibliothek auszuwählen, die wir für die Seite verwenden möchten. Für diesen Artikel wählen wir styled-components
. Voila! Unsere Seite ist erstellt. Um eine Komponente zu erstellen, führen npx nx g @nrwl/next:component todo-item
; Dadurch wird ein component
mit der todo-item
Komponente erstellt.
API-Verbrauch in der Next.js-Anwendung
In jedem To-Do-Element haben wir zwei Schaltflächen, um das To-Do-Element zu bearbeiten und zu löschen. Die asynchronen Funktionen, die diese Aktionen ausführen, werden als Requisiten von der Homepage übergeben.
… 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> ); }
Für die Aktualisierungsfunktion haben wir eine Eingabe, die deaktiviert ist, wenn der Zustand isEditingItem
false
ist. Sobald auf die Schaltfläche „Bearbeiten“ geklickt wird, wird der Zustand „ isEditingItem
“ auf „ true
“ gesetzt und die Schaltfläche „Aktualisieren“ angezeigt. Hier wird die Eingabekomponente aktiviert und der Benutzer kann einen neuen Wert eingeben; Wenn auf die Schaltfläche „Aktualisieren“ geklickt wird, ruft sie die Funktion updateItem
mit den übergebenen Parametern auf und isEditingItem
zurück auf false
.
In der home
Komponente haben wir die asynchronen Funktionen, die die CRUD-Operation ausführen.
… 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(); }, []); …
Im obigen Codeblock haben wir fetchItems
, das todoArray
vom Server zurückgibt. Dann haben wir die createItem
Funktion, die einen String akzeptiert; Der Parameter ist der Wert des neuen To-Do-Elements. Die updateItem
Funktion benötigt zwei Parameter, die ID des zu aktualisierenden Elements und den updatedItem
Wert. Und die Funktion deleteItem
entfernt das Element, das der übergebenen ID entspricht.
Um das Aufgabenelement zu items
, ordnen wir den Status des Elements zu:
… 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> ); …
Unser Server und Frontend sind nun eingerichtet. Wir können die API-Anwendung bedienen, indem wir npx nx serve todo-api
ausführen, und für die Next.js-Anwendung führen wir npx nx serve todo
. Klicken Sie auf die Schaltfläche „Weiter“, und Sie sehen eine Seite mit dem standardmäßigen To-Do-Element.
Wir haben jetzt eine funktionierende Next.js- und Express-Anwendung, die in einem Arbeitsbereich zusammenarbeiten.
Nx verfügt über ein weiteres CLI-Tool, mit dem wir das Abhängigkeitsdiagramm unserer Anwendung in unserem Terminallauf anzeigen können. Führen npx nx dep-graph
aus, und wir sollten einen Bildschirm ähnlich dem Bild unten sehen, der das Abhängigkeitsdiagramm unserer Anwendung darstellt.
Andere CLI-Befehle für Nx
-
nx list
Listet die aktuell installierten Nx-Plugins auf. -
nx migrate latest
Aktualisiert die Pakete inpackage.json
auf die neueste Version. -
nx affected
Führt die Aktion nur für die betroffenen oder geänderten Apps aus. -
nx run-many --target serve --projects todo-api,todo
Führt den Zielbefehl für alle aufgelisteten Projekte aus.
Fazit
Als allgemeinen Überblick über Nx hat dieser Artikel behandelt, was Nx bietet und wie es uns die Arbeit erleichtert. Wir haben auch die Einrichtung einer Next.js-Anwendung in einem Nx-Arbeitsbereich, das Hinzufügen eines Express-Plug-ins zu einem vorhandenen Arbeitsbereich und die Verwendung der Monorepo-Funktion zum Unterbringen von mehr als einer Anwendung in unserem Arbeitsbereich durchlaufen.
Den vollständigen Quellcode finden Sie im GitHub-Repository. Weitere Informationen zu Nx finden Sie in der Dokumentation oder in der Nx-Dokumentation für Next.js.