Объединение микрофронтов на Nx в один проект

от автора

Если вы, как и я, заинтересовались микрофронтами и пробуете развернуть проект на Nx, то возможно, у вас встанет вопрос, как в итоге объединить несколько своих микрофронтов в общий проект. По крайней мере, те статьи, которые я находил по этой теме, рассказывали про то, как создать в Nx несколько проектов (в т.ч. на разных фреймворках), как создать к ним компоненты и либы, и на этом всё заканчивалось. Разобравшись, решил оставить инструкцию для других.

Вводная информация
  • Структуру Nx и базовый принцип работы трогать не будем. Предполагается, что вы уже с этим знакомы;

  • Для сборки используем Webpack;

Для того, чтобы объединить несколько проектов в один, мы используем плагин Module Federation в вебпаке. Он позволяет объединять несколько разных сборок. В случае с Nx, есть два варианта настройки: простой и посложнее. Простой подойдёт в том случае, если вы только начали и ещё не успели создать свои микрофронты. В этом случае мы сразу создадим и отдельные микрофронты, и итоговое сборное приложение. Если вы уже успели создать какие-то приложения, то подойдёт вариант посложнее: мы создадим общее приложение и настроим конфиги вручную.

З.Ы. Есть ещё вариант «Самый сложный», это когда мы не создаём специально общее приложение, а настраиваем его из уже существующего, но этот вариант мы сегодня не будем рассматривать.

Простой вариант

В терминале заходим в наш монорепозиторий и запускаем команду:
nx g @nx/react:host main --remotes=name,name2, где

  • nx/react — это модуль, с помощью которого мы создаём итоговое приложение (в данном случае, внезапно, на реакте, могут быть варианты @nx/angular, @nx/js и т.д.,

  • main — название итогового проекта,

  • name,name2 — названия ваших микрофронтовых проектов.

Простота варианта заключается как раз во флаге --remotes. Мы можем сразу создать все проекты, которые нам нужны, и автоматически все связи будут настроены, у нас будет возможность запустить как один конкретный проект командой nx serve name, так и общую сборку командойnx serve main (автоматически запустит и все связанные проекты тоже).

Вариант посложнее

Если у нас уже есть проекты и нам нужен только хост, то запускаем команду nx g @nx/react:host main. Дальше нам нужно в общем проекте и в каждом микрофронте сделать некоторые настройки.

Для начала исходная точка: у нас монорепозиторий org, в нём два микрофронта (org и name) и хост (то есть, итоговый сборный проект) main.

Скрин

В сборном проекте:

  1. В файле webpack.config.prod.js нам нужно указать наши удалённые проекты, которые будем подтягивать:

Скрин

Код
const { composePlugins, withNx } = require('@nx/webpack'); const { withReact } = require('@nx/react'); const { withModuleFederation } = require('@nx/react/module-federation');  const baseConfig = require('./module-federation.config');  const prodConfig = {   ...baseConfig,   remotes: [     ['org', 'http://localhost:4201/'],     ['name', 'http://localhost:4202/'],   ], };  // Nx plugins for webpack to build config object from Nx options and context. module.exports = composePlugins(   withNx(),   withReact(),   withModuleFederation(prodConfig) ); 

  1. В файле module-federation.config.js указываем названия удалённых проектов:

Скрин

Код
module.exports = {   name: 'main',   remotes: ['org', 'name'], }; 

  1. В файле src/remotes.d.ts декларируем новые модули:

Скрин

Код
// Declare your remote Modules here // Example declare module 'about/Module';  declare module 'org/Module'; declare module 'name/Module'; 

  1. В файле src/app/app.tsx импортируем наши микрофронты и настраиваем роутинг так, как нам нужно:

Скрин

Код
import * as React from 'react'; import styles from './app.module.scss';  import { Link, Route, Routes } from 'react-router-dom';  const OrgPage = React.lazy(() => import('org/Module'));  const NamePage = React.lazy(() => import('name/Module'));  export function App() {   return (     <React.Suspense fallback={null}>       <main className={styles.content}>         <nav>           <ul className={styles.nav}>             <li>               <Link className={styles.navlink} to="/org">                 Org               </Link>             </li>              <li>               <Link className={styles.navlink} to="/name">                 Name               </Link>             </li>           </ul>         </nav>         <Routes>           <Route path="/org" element={<OrgPage />} />            <Route path="/name" element={<NamePage />} />         </Routes>       </main>     </React.Suspense>   ); }  export default App; 

В проектах (на примере org):

  1. Добавляем файл module-federation.config.js:

Скрин

Код
module.exports = {   name: 'org',   exposes: {     './Module': './src/remote-entry.ts',   }, }; 

  1. Соответственно, добавляем файл src/remote-entry.ts:

Скрин

Код
export { default } from './app/app';

  1. Обновляем webpack.config.js, добавляем настройку moduleFederation:

Скрин

Код
const { composePlugins, withNx } = require('@nx/webpack'); const { withReact } = require('@nx/react'); const { withModuleFederation } = require('@nx/react/module-federation');  const baseConfig = require('./module-federation.config');  const config = {   ...baseConfig, };  // Nx plugins for webpack to build config object from Nx options and context. module.exports = composePlugins(   withNx(),   withReact(),   withModuleFederation(config) ); 

  1. Добавляем продовский конфиг webpack.config.prod.js:

Скрин

Код
module.exports = require('./webpack.config');

  1. Одно из самых неочевидных действий. Если вы посмотрите на проект main, то вы увидите, что входным файлом является main.ts, в котором находится импорт из файла bootstrap.tsx:

Скрин

В то время, как в проекте org входным файлом является main.tsx, в котором и находится разметка:

Скрин

Так вот необходимо микрофронты привести к тому же виду, что и хост. То есть, создаём файл bootstrap.tsx, в него переносим разметку, переименовываем main.tsx в main.ts и делаем импорт. Если этого не сделать, проект не взлетит и будет ошибка Uncaught Error: Shared module is not available for eager consumption (подробнее об этом здесь).

  1. Нужно обновить файл project.json. Обновляем точку входа на main.ts:

Скрин

Добавляем ссылку на продовский конфиг ("webpackConfig": "apps/org/webpack.config.prod.js"):

Скрин

Обновляем разделы serve и serve-static, в частности, прописываем порты. Если их не прописать, то каждый проект по отдельности запустить получится, а общую сборку — нет.
Хост по дефолту взял себе порт 4200, поэтому на проекты мы ставим 4201, 4202 и т.д.:

Скрин

Итоговый код конфига
{   "name": "org",   "$schema": "../../node_modules/nx/schemas/project-schema.json",   "sourceRoot": "apps/org/src",   "projectType": "application",   "targets": {     "build": {       "executor": "@nx/webpack:webpack",       "outputs": ["{options.outputPath}"],       "defaultConfiguration": "production",       "options": {         "compiler": "babel",         "outputPath": "dist/apps/org",         "index": "apps/org/src/index.html",         "baseHref": "/",         "main": "apps/org/src/main.ts",         "tsConfig": "apps/org/tsconfig.app.json",         "assets": ["apps/org/src/favicon.ico", "apps/org/src/assets"],         "styles": ["apps/org/src/styles.scss"],         "scripts": [],         "isolatedConfig": true,         "webpackConfig": "apps/org/webpack.config.js"       },       "configurations": {         "development": {           "extractLicenses": false,           "optimization": false,           "sourceMap": true,           "vendorChunk": true         },         "production": {           "fileReplacements": [             {               "replace": "apps/org/src/environments/environment.ts",               "with": "apps/org/src/environments/environment.prod.ts"             }           ],           "optimization": true,           "outputHashing": "all",           "sourceMap": false,           "namedChunks": false,           "extractLicenses": true,           "vendorChunk": false,           "webpackConfig": "apps/org/webpack.config.prod.js"         }       }     },     "serve": {       "executor": "@nx/react:module-federation-dev-server",       "defaultConfiguration": "development",       "options": {         "buildTarget": "org:build",         "hmr": true,         "port": 4201       },       "configurations": {         "development": {           "buildTarget": "org:build:development"         },         "production": {           "buildTarget": "org:build:production",           "hmr": false         }       }     },     "lint": {       "executor": "@nx/linter:eslint",       "outputs": ["{options.outputFile}"],       "options": {         "lintFilePatterns": ["apps/org/**/*.{ts,tsx,js,jsx}"]       }     },     "serve-static": {       "executor": "@nx/web:file-server",       "defaultConfiguration": "development",       "options": {         "buildTarget": "org:build",         "port": 4201       },       "configurations": {         "development": {           "buildTarget": "org:build:development"         },         "production": {           "buildTarget": "org:build:production"         }       }     },     "test": {       "executor": "@nx/jest:jest",       "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],       "options": {         "jestConfig": "apps/org/jest.config.ts",         "passWithNoTests": true       },       "configurations": {         "ci": {           "ci": true,           "codeCoverage": true         }       }     }   },   "tags": [] } 

Всё готово. Запускаем хост командой nx serve main, переходим на http://localhost:4200 — вы прекрасны!

Обращаю ваше внимание на то, что запуская хост, мы запускаем и все связанные проекты. Поэтому если вы перейдёте на http://localhost:4201, то увидите там наш проект org.

Теперь вы можете запускать проекты как по отдельности (чтобы работать с одним конкретным проектом, не поднимая всё остальное), так и общую сборку.

Спасибо, что воспользовались услугами нашей авиакомпании, happy hacking!


ссылка на оригинал статьи https://habr.com/ru/articles/745614/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *