Оптимизация приложений Next.js с помощью Nx
Опубликовано: 2022-03-10В этой статье мы рассмотрим, как оптимизировать и создать высокопроизводительное приложение Next.js с использованием Nx и его богатых функций. Мы рассмотрим, как настроить сервер Nx, как добавить плагин к существующему серверу, а также концепцию монорепозитория с практической визуализацией.
Если вы разработчик, стремящийся оптимизировать приложения и эффективно создавать повторно используемые компоненты в приложениях, эта статья покажет вам, как быстро масштабировать ваши приложения и как работать с Nx. Чтобы продолжить, вам потребуются базовые знания фреймворка Next.js и TypeScript.
Что такое Nx?
Nx — это платформа сборки с открытым исходным кодом, которая помогает вам проектировать, тестировать и строить в любом масштабе, легко интегрируясь с современными технологиями и библиотеками, обеспечивая при этом надежный интерфейс командной строки (CLI), кэширование и управление зависимостями. Nx предлагает разработчикам расширенные инструменты и плагины CLI для современных фреймворков, тестов и инструментов.
В этой статье мы сосредоточимся на том, как Nx работает с приложениями Next.js. Nx предоставляет стандартные инструменты для тестирования и стилизации в ваших приложениях Next.js, таких как Cypress, Storybook и styled-components. Nx упрощает монорепозиторий для ваших приложений, создавая рабочее пространство, в котором могут храниться исходный код и библиотеки нескольких приложений, что позволяет вам совместно использовать ресурсы между приложениями.
Зачем использовать Nx?
Nx предоставляет разработчикам разумный объем готовых функций, включая шаблоны для сквозного (E2E) тестирования вашего приложения, библиотеку стилей и монорепозиторий.
Использование Nx дает много преимуществ, и мы рассмотрим некоторые из них в этом разделе.
- Выполнение задачи на основе графа
Nx использует распределенное выполнение задач на основе графа и кэширование вычислений для ускорения задач. Система будет планировать задачи и команды, используя графическую систему, чтобы определить, какой узел (т.е. приложение) должен выполнять каждую задачу. Это управляет выполнением приложений и эффективно оптимизирует время выполнения. - Тестирование
Nx предоставляет предварительно настроенные инструменты тестирования для модульного тестирования и тестирования E2E. - Кэширование
Nx также хранит кешированный граф проекта. Это позволяет повторно анализировать только обновленные файлы. Nx отслеживает файлы, измененные с момента последней фиксации, и позволяет тестировать, создавать и выполнять действия только с этими файлами; это обеспечивает правильную оптимизацию при работе с большой базой кода. - График зависимости
Визуальный график зависимостей позволяет проверить, как компоненты взаимодействуют друг с другом. - Облачное хранилище
Nx также предоставляет облачное хранилище и интеграцию с GitHub, так что вы можете делиться ссылками с членами команды для просмотра журналов проекта. - Совместное использование кода
Создание новой общей библиотеки для каждого проекта может быть довольно утомительным. Nx устраняет эту сложность, позволяя вам сосредоточиться на основных функциях вашего приложения. С помощью Nx вы можете обмениваться библиотеками и компонентами между приложениями. Вы даже можете совместно использовать повторно используемый код между интерфейсными и внутренними приложениями. - Поддержка монорепозиториев
Nx предоставляет одно рабочее пространство для нескольких приложений. При такой настройке один репозиторий GitHub может содержать исходный код для различных приложений в вашей рабочей области.
Nx для публикуемых библиотек
Nx позволяет создавать публикуемые библиотеки. Это важно, когда у вас есть библиотеки, которые вы будете использовать за пределами монорепозитория. В любом случае, когда вы разрабатываете организационные компоненты пользовательского интерфейса с интеграцией Nx Storybook, Nx создаст публикуемые компоненты вместе с вашими историями. Публикуемые компоненты могут компилировать эти компоненты для создания пакета библиотек, который можно развернуть во внешнем реестре. Вы должны использовать параметр --publishable
при создании библиотеки, в отличие от --buildable
, который используется для создания библиотек, которые используются только в монорепозитории. Nx не развертывает публикуемые библиотеки автоматически; вы можете вызвать сборку с помощью такой команды, как nx build mylib
(где mylib
— это имя библиотеки), которая затем создаст оптимизированный пакет в папке dist
/ mylib
, который можно будет развернуть во внешнем реестре.
Nx дает вам возможность создать новую рабочую область с Next.js в качестве предустановки или добавить Next.js в существующую рабочую область.
Чтобы создать новую рабочую область с Next.js в качестве предустановки, вы можете использовать следующую команду:
npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo
Эта команда создаст новую рабочую область Nx с приложением Next.js с именем «todo» и со styled-components
в качестве библиотеки стилей.
Затем мы можем добавить приложение Next.js в существующую рабочую область Nx с помощью следующей команды:
npx nx g @nrwl/next:app
Создание приложения Next.js и Nx
Плагин Nx для Next.js включает в себя инструменты и исполнители для запуска и оптимизации приложения Next.js. Для начала нам нужно создать новую рабочую область Nx с предустановкой next
:
npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo
Приведенный выше блок кода создаст новое рабочее пространство Nx и приложение Next.js. Мы получим приглашение использовать Nx Cloud. Для этого руководства мы выберем «Нет», а затем дождемся установки наших зависимостей. Как только это будет сделано, у нас должно получиться дерево файлов, похожее на это:
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
В папке с apps
у нас будет наше приложение Next.js «todo» с предварительно настроенным тестом E2E для приложения to-do. Все это автоматически генерируется с помощью мощного инструмента Nx CLI.
Чтобы запустить наше приложение, используйте npx nx serve todo
. Когда вы закончите обслуживать приложение, вы должны увидеть экран ниже:
Создание API
На этом этапе мы настроили рабочее пространство. Далее нужно создать CRUD API, который мы будем использовать в приложении Next.js. Для этого мы будем использовать Express; чтобы продемонстрировать поддержку монорепозитория, мы создадим наш сервер как приложение в рабочей области. Во-первых, мы должны установить плагин Express для Nx, выполнив эту команду:
npm install --save-dev @nrwl/express
Как только это будет сделано, мы готовы настроить наше приложение Express в предоставленной рабочей области. Чтобы создать приложение Express, выполните следующую команду:
npx nx g @nrwl/express:application --name=todo-api --frontendProject=todo
Команда nx g @nrwl/express:application
сгенерирует приложение Express, которому мы можем передать дополнительные параметры спецификации; чтобы указать имя приложения, используйте флаг --name
; чтобы указать внешнее приложение, которое будет использовать приложение Express, передайте имя приложения в нашей рабочей области в --frontendProject
. Несколько других опций доступны для приложения Express. Когда это будет сделано, у нас будет обновленная файловая структура в папке apps
с добавленной в нее папкой todo-api
.
happynrwl ┣ apps ┃ ┣ todo ┃ ┣ todo-api ┃ ┣ todo-e2e ┃ ┗ .gitkeep …
Папка todo-api
представляет собой шаблон Express с файлом входа 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);
Мы будем создавать наши маршруты внутри этого приложения. Для начала мы инициализируем массив объектов с двумя парами ключ-значение, item
и id
, прямо под объявлением приложения.
/** * 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() }, ]; …
Далее мы настроим маршрут для получения всех списков дел в app.get()
:
… app.get('/api', (req, res) => { res.status(200).json({ data: todoArray, }); }); …
Приведенный выше блок кода вернет текущее значение todoArray
. Впоследствии у нас появятся маршруты для создания, обновления и удаления элементов списка дел из массива.
… 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', }); }); …
Чтобы создать новый элемент списка дел, все, что нам нужно, — это значение нового элемента в виде строки. Мы сгенерируем идентификатор, увеличив идентификатор последнего элемента в массиве на сервере. Чтобы обновить существующий элемент, мы должны передать новое значение для элемента и идентификатор объекта элемента, который необходимо обновить; на сервере мы просматриваем каждый элемент с помощью метода forEach
и обновляем элемент в том месте, где идентификатор совпадает с идентификатором, отправленным с запросом. Наконец, чтобы удалить элемент из массива, мы должны отправить идентификатор удаляемого элемента вместе с запросом; затем мы фильтруем массив и возвращаем новый массив всех элементов, не соответствующих идентификатору, отправленному с запросом, присваивая новый массив переменной todoArray
.
Примечание. Если вы посмотрите в папку приложения Next.js, вы должны увидеть файл proxy.conf.json
со следующей конфигурацией:
{ "/api": { "target": "http://localhost:3333", "secure": false } }
Это создает прокси-сервер, позволяющий всем вызовам API к маршрутам, соответствующим /api
, нацеливаться на сервер todo-api
.
Создание страниц Next.js с помощью Nx
В нашем приложении Next.js мы создадим новую страницу, home
страницу и элемент элемента. Nx предоставляет нам инструмент командной строки для простого создания страницы:
npx nx g @nrwl/next:page home
После запуска этой команды мы получим приглашение выбрать библиотеку стилей, которую мы хотим использовать для страницы; для этой статьи мы выберем styled-components
. Вуаля! Наша страница создана. Чтобы создать компонент, запустите npx nx g @nrwl/next:component todo-item
; это создаст папку component
с компонентом todo-item
.
Использование API в приложении Next.js
В каждом элементе списка дел у нас будет две кнопки для редактирования и удаления элемента списка дел. Асинхронные функции, выполняющие эти действия, передаются в качестве реквизита с домашней страницы.
… 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> ); }
Для функции обновления у нас есть вход, который отключен, когда состояние isEditingItem
равно false
. После нажатия кнопки «Изменить» состояние isEditingItem
переключается на true
и отображается кнопка «Обновить». Здесь компонент ввода включен, и пользователь может ввести новое значение; когда нажимается кнопка «Обновить», она вызывает функцию updateItem
с переданными параметрами и переключает isEditingItem
обратно в false
.
В компоненте home
страницы у нас есть асинхронные функции, выполняющие операцию 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(); }, []); …
В блоке кода выше у нас есть fetchItems
, который возвращает todoArray
с сервера. Затем у нас есть функция createItem
, которая принимает строку; параметр является значением нового элемента списка дел. Функция updateItem
принимает два параметра: идентификатор обновляемого элемента и значение updatedItem
. И функция deleteItem
удаляет элемент, соответствующий переданному идентификатору.
Чтобы отобразить элемент списка дел, мы сопоставляем состояние 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> ); …
Наш сервер и интерфейс настроены. Мы можем обслуживать приложение API, запустив npx nx serve todo-api
, а для приложения Next.js мы запускаем npx nx serve todo
. Нажмите кнопку «Продолжить», и вы увидите страницу с отображаемым элементом списка дел по умолчанию.
Теперь у нас есть работающее приложение Next.js и Express, работающее вместе в одном рабочем пространстве.
У Nx есть еще один инструмент CLI, который позволяет нам просматривать график зависимостей нашего приложения в нашем терминале. Запустите npx nx dep-graph
, и мы должны увидеть экран, похожий на изображение ниже, изображающее граф зависимостей нашего приложения.
Другие команды CLI для Nx
-
nx list
Список установленных в настоящее время плагинов Nx. -
nx migrate latest
Обновляет пакеты вpackage.json
до последней версии. -
nx affected
Выполняет действие только с затронутыми или измененными приложениями. -
nx run-many --target serve --projects todo-api,todo
Запускает целевую команду во всех перечисленных проектах.
Заключение
В качестве общего обзора Nx в этой статье рассказывается о том, что предлагает Nx и как он облегчает нам работу. Мы также рассмотрели настройку приложения Next.js в рабочей области Nx, добавление подключаемого модуля Express в существующую рабочую область и использование функции монорепозитория для размещения более одного приложения в нашей рабочей области.
Вы найдете полный исходный код в репозитории GitHub. Для получения дополнительной информации о Nx ознакомьтесь с документацией или документацией по Nx для Next.js.