Оглавление:
- Об авторе
- Микрофронтенды, Nx и монорепозитории
- Next.js, Nx + Next.js
- Федерация модулей, Next.js + Федерация модулей
- Запуск проекта
- Создание новых страниц и новых приложений
- Запуск в среде разработки
- Генерация новых компонентов
- Установка nextjs-mf
- Создание хуков
- Развертывание проектов на Vercel + частные зависимости с Vercel
- Ссылки
- Автор
- Микрофронтенды
- Nx и монорежим
- Плюсы
- Минусы
- Next.js
- Nx + Next.js
- Федерация модулей
- Next.js + Федерация модулей
- О проекте
- Запуск проекта
- Генерация новых страниц
- Генерация новых приложений
- Запуск в среде разработки
- Генерация новых компонентов
- Установка nextjs-mf
- Создание крючков
- Развертывание проектов на Vercel
- Частные зависимости с помощью Vercel
Автор
Бруно Сильва, разработчик Next.js в Valor Software при поддержке команды, создал решение, описанное ниже, и этот материал, который первоначально был опубликован в блоге Valor.
Статья также доступна на португальском языке: https://valor-software.com/articles/nx-next-js-e-module-federation. Наслаждайтесь чтением и опытом разработчика 🙂
Микрофронтенды
Вы наверняка слышали о микросервисах и о том, сколько преимуществ они дают как для масштабируемости бэкенд-приложения, так и для команды, которая его разрабатывает. А теперь представьте, если бы эти же преимущества можно было перенести на фронтенд. Вот об этом мы и поговорим сегодня.
Сначала давайте поговорим о том, что такое микрофронтенд и в чем его преимущества. Микрофронтенды — это способ организовать как приложение, так и команду, которая его разрабатывает. Подумайте о следующем: веб-приложение обычно состоит из нескольких ресурсов, и в зависимости от размера приложения оно зависит от определенной синхронности между командами, которые его разрабатывают, чтобы новые версии выпускались в производство. Давайте рассмотрим пример: Представьте, что у нас есть сайт по продаже онлайн-курсов, и этот сайт имеет следующие функции:
- Институциональная страница (целевая страница/маркетинг)
- Каталог / расширенный поиск
- Оформление заказа
- Страница проигрывателя курсов
- Страница загрузки
- Форум (для взаимодействия студентов с преподавателями)
- Страницы настроек аккаунта (для студентов и репетиторов)
Представьте себе, что каждая из этих функций может быть легко разделена на микросервисы, когда речь идет о бэкенде, но чаще всего фронтенд оказывается своего рода «монолитом», содержащим единый стек и заставляющим всю команду постоянно синхронизироваться, когда новая функция запускается в производство. Вы видите это? Очевидно, что мы могли бы разделить каждую часть этой электронной коммерции на отдельные приложения для небольших команд с уникальными обязанностями и, возможно, разными стеками. Подумайте об этом, команда, отвечающая за оформление заказа, могла бы работать на 100% сосредоточившись на улучшении потока покупок пользователя, уменьшая шум, который заставлял их отказываться от покупки, или, например, команда по каталогам и институциональная команда (целевая страница), которая могла бы работать над различными маркетинговыми направлениями, а также команда, отвечающая за видео область платформы, которая была бы сосредоточена на обеспечении скорости / высокого качества в доставке занятий студентам.
В любом случае, можно привести множество примеров использования микрофронтендов, но поскольку цель этой статьи — быть немного более практичной, давайте пропустим эту часть, и если вас больше интересует понимание преимуществ использования микрофронтендов, я предлагаю вам прочитать об этом подробнее в этой статье.
Nx и монорежим
Как мы видели ранее, можно разделить некоторые веб-приложения на микрофронтенды, что поначалу может вызвать у вас некоторые вопросы, такие как: «Но тогда мне нужно разделить все приложения на отдельные репозитории? Представьте, какая головная боль будет тестировать все это!», и здесь мы поговорим о mono репозиториях. Репозиторий Mono или Monorepo — это единый git-репозиторий, который предназначен для управления всем исходным кодом приложения, что дает нам ряд преимуществ и некоторые недостатки, вот некоторые из них:
Плюсы
- Стандартизация (lint) кода для всей команды
- Управление тестированием в одном месте
- Централизация управления зависимостями
- Повторное использование кода между приложениями благодаря централизации зависимостей
- Прозрачность, так как мы можем видеть весь код из одного рабочего пространства
Минусы
- Папка .git может стать большой из-за большого количества вкладов, поскольку вся команда вносит коммиты в один и тот же проект
- Увеличение времени сборки некоторых приложений в зависимости от уровня зависимостей и размера общих файлов/данных
- Отсутствие гранулярности разрешений, поскольку вся команда должна иметь доступ к монорепо, теряется возможность ограничить доступ определенных пользователей к определенным частям приложения. Посмотрев на преимущества и контекст, я увидел, что это была бы отличная возможность использовать Nx в качестве менеджера для нашего проекта. Nx — это менеджер монорепо с огромным набором плагинов для облегчения создания новых приложений, библиотек, тестов, выполнения сборок, стандартизации lint, централизации, управления зависимостями и многих других функций.
Next.js
Бесспорно, что в настоящее время Next.js является одним из веб-фреймворков, который в последнее время получает все большее распространение, и все это благодаря ряду возможностей, таких как рендеринг на стороне сервера, статическая оптимизация, маршрутизация файловой системы, маршруты API и стратегии выборки данных, которые он предлагает. Next.js — потрясающий инструмент, но мы будем считать, что вы уже знакомы с ним и перейдем к следующей части.
Nx + Next.js
По словам команды Nx, их философия разработки очень похожа на философию Visual Studio Code, в которой они сосредоточены на поддержании мощного и универсального инструмента, в то время как расширения, или плагины, являются основополагающими для повышения продуктивности работы с ним. Таким образом, @nrwl/next — это плагин, который мы будем использовать для создания и управления нашими приложениями с Next.js в рабочем пространстве Nx.
Федерация модулей
Module Federation — это функция Webpack 5, которая появилась для того, чтобы сделать возможным передачу частей приложения другому во время выполнения. Это дает возможность нескольким приложениям, скомпилированным с помощью webpack, перепрофилировать части своего кода по мере взаимодействия пользователя с ними, что подводит нас к следующему шагу.
Next.js + Федерация модулей
Давайте начнем с нашего первого примера в этой статье, где мы говорим о приложении электронной коммерции, теперь представьте, что наша команда маркетологов решает создать мега-кампанию «Черная пятница» и решает изменить несколько частей нашего приложения, вставив различные компоненты с динамическими баннерами, каруселями, обратными отсчетами, тематическими предложениями и т.д… Это, вероятно, стало бы головной болью для всех команд, ответственных за наши микрофронтальные приложения, поскольку каждая из них должна была бы реализовать новые требования маркетинговой команды в своих проектах, и это должно было бы быть очень хорошо протестировано и синхронизировано, чтобы все прошло правильно и ничего не было выпущено раньше времени… В любом случае, все это может легко породить много работы и много головной боли для команды, но именно здесь приходит очень мощная Module Federation.
Благодаря ей, только одна команда будет отвечать за разработку новых компонентов вместе с их соответствующей логикой, а остальная команда будет отвечать только за реализацию использования этих новых дополнений, которые могут принести с собой хуки, компоненты в React, и т.д.
К сожалению, реализовать и использовать возможности Module Federation в Webpack с Next.js не так просто, поскольку вам потребуется глубоко понять, как работают оба инструмента, чтобы иметь возможность создать решение, облегчающее интеграцию между ними. К счастью, такое решение уже существует и имеет несколько функций, включая поддержку SSR (рендеринга на стороне сервера), эти инструменты называются nextjs-mf и nextjs-ssr, и вместе мы рассмотрим пробное приложение, которое я создал, чтобы показать вам силу этих инструментов вместе.
⚠️ Внимание: для работы приложения с функциями Module Federation вам необходимо иметь доступ к плагину nextjs-mf или nextjs-ssr, который в настоящее время требует платной лицензии!
О проекте
Этот проект покажет, как создать основу для полностью масштабируемого приложения как в производстве, так и в разработке. В нем мы увидим несколько небольших примеров того, как можно использовать упомянутые выше инструменты.
Запуск проекта
Первоначально нам потребуется установить Nx в наше окружение для обработки команд, необходимых для управления нашим монорепом. Для этого откройте терминал и запустите его:
npm i -g nx
После этого перейдите в каталог, где вы хотите создать проект, и выполните команду ниже. Эта команда будет использовать @nrwl/next для создания нашего рабочего пространства (monorepo) и нашего первого приложения:
npx create-nx-workspace@latest --preset=next
Интерактивный терминал проведет вас через весь процесс создания, вы можете следовать за ним, как я сделал ниже:
Как только это будет сделано, вы должны дождаться создания рабочего пространства (monorepo) и загрузки зависимостей проекта, после чего вы можете открыть vscode в корне рабочего пространства, в моем случае:
code ./nextjs-nx-module-federation
Посмотрев в проводнике файлов, можно увидеть, что проект имеет структуру, подобную этой:
├── apps
│ ├── store (...)
│ └── store-e2e (...)
├── babel.config.json
├── jest.config.ts
├── jest.preset.js
├── libs
├── nx.json
├── package.json
├── package-lock.json
├── README.md
├── tools
│ ├── generators (...)
│ └── tsconfig.tools.json
├── tsconfig.base.json
└── workspace.json
ℹ️ Обратите внимание, что наше приложение в Next.js находится внутри папки «apps», в этой папке будут находиться все остальные приложения, которые вы будете создавать, также мы можем увидеть другие файлы конфигурации нашего рабочего пространства. Важно отметить, что во всем проекте есть только одна папка «node_modules», это происходит потому, что все зависимости будут находиться в одном месте, в корне репозитория.
Генерация новых страниц
Плагин @nrwl/next имеет несколько генераторов и команд, которые служат для автоматизации создания страниц, компонентов и других общих структур в проекте.
Зная это, мы создадим нашу первую страницу с помощью генератора под названием «page», для чего выполним в терминале следующую команду
nx g @nrwl/next:page home --project=store
ℹ️ Обратите внимание, что мы используем флаг —project, чтобы указать генератору, в каком проекте должна быть создана новая страница.
В результате будет создана страница под названием «home», которая будет расположена по адресу
apps/store/pages/home/index.tsx
Генерация новых приложений
Теперь нам нужно создать еще одно приложение, которое мы назовем «checkout». В отличие от первого приложения, которое мы создали вместе с рабочим пространством, нам нужно будет использовать следующую команду для создания нового приложения Next.js в текущем рабочем пространстве:
nx g @nrwl/next:app checkout
Ваша папка «apps» должна выглядеть следующим образом:
├── apps
│ ├── checkout (...)
│ ├── checkout-e2e (...)
│ ├── store (...)
│ └── store-e2e (...)
...
Запуск в среде разработки
Чтобы увидеть, что наши изменения работают, нам нужно выполнить следующую команду в терминале:
nx serve store
ℹ️ serve — это команда-исполнитель.
Также мы можем запустить все приложения одновременно, используя:
nx run-many --target=serve --all
ℹ️ Обратите внимание, что мы используем флаг —target, чтобы указать nx, какой исполнитель мы хотим запустить на всех проектах.
Генерация новых компонентов
Как мы видели ранее, у нас есть возможность создавать структуры в нашем приложении с помощью инструмента Nx CLI, сейчас мы создадим простой компонент кнопки в проекте «checkout», для чего выполним следующую команду:
nx g @nrwl/next:component buy-button --project=checkout
Теперь давайте отредактируем компонент в директории ниже, чтобы он выглядел следующим образом
apps/checkout/components/buy-button/buy-button.tsx
Мы будем использовать этот простой компонент приложения «checkout» в приложении «store» для примера совместного использования кода с Module Federation, и это переводит нас к следующему шагу.
Установка nextjs-mf
⚠️ Внимание: для работы приложения с функциями Module Federation вам необходимо иметь доступ к плагину https://app.privjs.com/package?pkg=@module-federation/nextjs-mf[[nextjs-ssr^]], который в настоящее время требует платной лицензии!
Чтобы установить инструмент, нам нужно войти в [PrivJs}(https://privjs.com/^) с помощью npm, для этого выполните следующую команду:
npm login --registry <https://r.privjs.com>
После этого файл с вашими учетными данными будет сохранен в ~/.npmrc. Теперь вы можете установить nextjs-mf с помощью команды ниже:
npm install @module-federation/nextjs-mf —registry https://r.privjs.com.
Теперь нам нужно изменить наш файл «next.config.js» в обоих проектах, чтобы установленный плагин мог работать, для этого откройте следующие файлы:
- apps/store/next.config.js
- apps/checkout/next.config.js Вы увидите, что в них используется плагин Nx, нам нужно будет поддерживать его, для этого сделайте файлы каждого проекта похожими на эти:
- store/next.config.js
- checkout/next.config.js Вы заметите, что в этом файле используются две переменные окружения, нам нужно будет определить их в каждом проекте, поэтому создайте файл «.env.development.local» в каждом проекте и оставьте в каждом файле следующие значения:
NEXT_PUBLIC_CHECKOUT_URL=http://localhost:4200
NEXT_PUBLIC_STORE_URL=http://localhost:4300
Пока никаких новых изменений не видно, но мы уже можем использовать ресурсы Module Federation, но перед этим мы сделаем некоторые изменения в нашей среде разработки, чтобы приложения могли общаться без генерации предупреждений в консоли с помощью локального столкновения портов, для этого откройте и отредактируйте следующие файлы:
«apps/store/project.json».
{
// ...
"targets": {
// ...
"serve": {
// ...
"options": {
"buildTarget": "checkout:build",
"dev": true,
"port": 4300
},
// ...
},
// ...
}
«apps/checkout/project.json»
{
// ...
"targets": {
// ...
"serve": {
// ...
"options": {
"buildTarget": "checkout:build",
"dev": true,
"port": 4200
},
// ...
},
// ...
}
Для того чтобы компонент стал федеративным, мы должны добавить его в файл «next.config.js», открыть файл и добавить новую запись в объект «exposes»:
module.exports = withFederatedSidecar({
// ...
exposes: {
'./buy-button': './components/buy-button/buy-button.tsx',
},
// ...
})(nxNextConfig);
Теперь, когда все настроено, мы должны перезапустить любой запущенный процесс next и импортировать компонент кнопки, который мы создали в проекте «checkout» в проект «store», используя ресурсы Module Federation, для этого откройте страницу «home», которую мы создали в проекте «store» и импортируйте динамическую функцию Next.js, как показано ниже:
import dynamic from 'next/dynamic';
Эта функция поможет нам импортировать компонент только на стороне клиента, поэтому добавьте следующий фрагмент кода на страницу:
const BuyButton = dynamic(
async () => import('checkout/buy-button'),
{
ssr: false,
}
);
А затем мы можем использовать компонент в содержимом страницы
export function Page() {
return (
<div className={styles['container']}>
<h1>Welcome to Store!</h1>
<BuyButton onClick={() => alert('Hello, Module Federation!')}>Add to Cart</BuyButton>
</div>
);
}
Теперь вы можете увидеть следующий результат
Создание крючков
Одной из возможностей nextjs-mf является объединение функций, включая хуки. Важной деталью является то, что мы не можем импортировать хуки асинхронно, что заставляет нас принять решение, при котором мы импортируем функции с помощью «require», а страница или компонент, использующий хук, загружается лениво/асинхронно, что мы называем «top-level-await».
Сначала нам нужно будет создать хук, для этого мы сделаем простую функцию состояния. Создайте файл в приложении «checkout» в «apps/checkout/hooks/useAddToCart.ts» и вставьте в него приведенный ниже код:
import { useState } from 'react';
export default function useAddToCartHook() {
const [itemsCount, setItemsCount] = useState<number>(0);
return {
itemsCount,
addToCart: () => setItemsCount((i) => i + 1),
clearCart: () => setItemsCount(0),
};
}
Как только это будет сделано, добавьте файл в список модулей, открываемых в файле «next.config.js»:
module.exports = withFederatedSidecar({
// ...
exposes: {
'./buy-button': './components/buy-button/buy-button.tsx',
'./useAddToCartHook': './hooks/useAddToCart.ts'
},
// ...
})(nxNextConfig);
Чтобы импортировать хук, давайте создадим новую страницу, которая будет импортироваться асинхронно, для этого создайте новую папку в приложении магазина под названием async-pages. Создайте файл custom-hook.tsx, который будет нашей страницей в папке async-pages, затем добавьте в файл следующий код:
// typing for the hook
type UseAddToCartHookType = () => UseAddToCartHookResultType;
// hook function return typing
type UseAddToCartHookResultType = {
itemsCount: number;
addToCart: () => void;
clearCart: () => void;
};
// hook default value
let useAddToCartHook = (() => ({})) as UseAddToCartHookType;
// import the hook only on the client-side
if (process.browser) {
useAddToCartHook = require('checkout/useAddToCartHook').default;
}
export function Page() {
// on server side extracts the values as undefined
// on the client side extracts the hook values
const { itemsCount, addToCart, clearCart } =
useAddToCartHook() as UseAddToCartHookResultType;
return (
<div>
<h1>Welcome to Custom Hook!</h1>
<p>
Item Count: <strong>{itemsCount}</strong>
</p>
<button onClick={addToCart}>Add to Cart</button>
<button onClick={clearCart}>Clear Cart</button>
</div>
);
}
// here you can use the getInitialProps function normally
// it will be called on both server-side and client-side
Page.getInitialProps = async (/*ctx*/) => {
return {};
};
export default Page;
Теперь нам нужно создать страницу в папке «pages», которая будет загружать нашу страницу асинхронно, для этого используйте команду ниже:
nx g @nrwl/next:page custom-hook --project=store
Теперь откройте только что созданный файл страницы и добавьте следующий код
import dynamic from 'next/dynamic';
import type { NextPage, NextPageContext } from 'next';
// import functions from page in synchronously way
const page = import('../../async-pages/custom-hook');
// lazy import the page component
const Page = dynamic(
() => import('../../async-pages/custom-hook')
) as NextPage;
Page.getInitialProps = async (ctx: NextPageContext) => {
// capture the getInitialProps function from the page
const getInitialProps = ((await page).default as NextPage)?.getInitialProps;
if (getInitialProps) {
// if the function exists, call the function on server-side and client-side
return getInitialProps(ctx);
}
return {};
};
export default Page;
Теперь вы можете увидеть следующий результат
На момент написания этой статьи возможны некоторые ошибки, поэтому если вы сомневаетесь, посмотрите на этот проект, который я создал в качестве доказательства концепции, я активно работаю с Zackary, чтобы сделать его актуальным и функциональным.
Развертывание проектов на Vercel
Процедура, которую мы сейчас выполним, будет проходить на Vercel, но мы можем без особого труда повторить ее на других платформах бессерверного хостинга, таких как Netlify, AWS Amplify, Serverless с плагином для Next.js или даже самостоятельно, используя Docker с частным сервером.
Мы можем выполнить процесс двумя способами: через интерфейс или через CLI, но для облегчения процесса мы сделаем это через интерфейс, вам просто нужно разместить проект на GitHub, чтобы мы могли импортировать его в несколько кликов, как только проект будет на GitHub, вы можете открыть эту страницу на Vercel, чтобы развернуть первое приложение… именно, хотя это монорепо, мы настроим все так, чтобы были сделаны отдельные развертывания.
Сначала мы развернем приложение «checkout», потому что у него меньше зависимостей, для этого выберите репозиторий, как на следующем изображении, и нажмите на кнопку для его импорта:
Выберите имя для приложения на открывшемся экране, но помните, что мы собираемся проделать тот же шаг для приложения «store», поэтому задайте разное имя для каждого проекта. Мы должны изменить некоторые команды для сборки проекта на вкладке «Build and Output Settings», для этого отметьте опцию override и оставьте поля, как показано ниже:
Команда сборки (checkout)
npx nx build checkout --prod
Выходной каталог (checkout)
dist/apps/checkout/.next
Пока пропустим раздел переменных окружения, поскольку у нас нет URL-адресов, на которых будут размещены приложения, мы можем нажать кнопку «Deploy». Вы можете заметить, что у нас может возникнуть ошибка во время сборки, но не волнуйтесь, если это произойдет, мы скоро решим эту проблему. Теперь мы собираемся развернуть наше приложение «store» и проделаем те же шаги, что и раньше, только изменим некоторые поля на вкладке «Build and Output Settings». Команда сборки (store)
npx nx build store --prod
Выходной каталог (store)
dist/apps/store/.next
Как только это будет сделано, мы можем нажать на кнопку «Развернуть». Опять же, вы заметите, что сборка привела к ошибке, но это неважно, главное, что у нас теперь есть два URL-адреса двух проектов, и мы можем использовать их для настройки нашей среды. Теперь перейдите в панель настроек каждого приложения и установите следующие переменные окружения
Обратите внимание, что я использую URL «развертывания», который я сделал для своего магазина приложений, вы должны сделать это с URL, который Vercel сгенерировал для вашего, не забудьте определить две переменные окружения «NEXT_PUBLIC_CHECKOUT_URL» и «NEXT_PUBLIC_STORE_URL», каждая с соответствующим URL производства.
Частные зависимости с помощью Vercel
Если вы откроете журналы сборки проекта, то заметите, что в обоих случаях ошибка одна и та же, вероятно, что-то вроде этого
npm ERR! 403 403 Forbidden - GET <https://r.privjs.com/@module-federation%2fnextjs-mf/-/nextjs-mf-3.5.0.tgz> - You must be logged in to install/publish packages.
npm ERR! 403 In most cases, you or one of your dependencies are requesting
npm ERR! 403 a package version that is forbidden by your security policy, or
npm ERR! 403 on a server you do not have access to.
npm ERR! A complete log of this run can be found in:
npm ERR! /vercel/.npm/_logs/2022-06-24T21_11_19_939Z-debug-0.log
Error: Command "npm install" exited with 1
Это происходит потому, что Vercel не имеет необходимых учетных данных для доступа к пакету, который находится в частном репозитории, чтобы предоставить доступ к репозиторию, нам нужно настроить переменную окружения под названием «NPM_RC», значение этой переменной должно быть таким же, как в файле «~/.npmrc», который был создан при использовании команды «npm login».
Для этого просто создайте новую переменную в панели настроек переменных окружения Vercel под названием «NPM_RC» и вставьте в нее все содержимое файла «~/.npmrc», если у вас есть сомнения, прочитайте этот документ.
Наконец, вы можете открыть вкладку «Deployments» и «Redeploy» вашего приложения!
Перейдя по URL «store» приложения, вы можете увидеть, что кнопка, исходный код которой находится в проекте «checkout», «переброшена» на наш сайт.
Ссылки
- Thoughtworks — Микрофронтенды
- Module Federation — O futuro do microfrontend
- Что такое микрофронтенды?
- Федерация модулей Webpack 5 — Зак Джексон — CityJS Conf 2020
- Плюсы и минусы монорежима, объяснение
- Next.js с федерацией модулей
- Как использовать частные зависимости в Vercel?
- Nx с Next.js