Введение
В информационном пространстве есть статьи и видео по теме совместного использования компонентов с помощью Webpack 5. Но, по моему скромному мнению, они содержат ряд серьезных ошибок: не соблюдают логику изложения, оперируют сложной терминологией и описывают абстрактные решения.
Неудивительно, что вы не понимаете материал и по-прежнему сталкиваетесь с трудностями в этом вопросе!
Поэтому предлагаю поэкспериментировать с плагином Webpack 5 Module Federation. Подробный разбор и отличные скриншоты гарантируются!
Примечание. Специально для данной статьи я написал 2 небольших приложения React. Нижепредставленные фрагменты кода взяты как раз из них. Весь код доступен на GitLab: Gerard’s Bicycle Shop (Веломагазин Джерарда) и Gerard’s Blog (Блог Джерарда).
Цель
С помощью React мы создали небольшой веб-магазинчик и блог, которые применяют Webpack 5 в качестве сборщика модулей.
В магазине есть компонент React, отображающий новое предложение. Наша цель — добиться того, чтобы это же предложение отображалось в блоге. Для этого необходимо обеспечить совместное использование компонента.
Сначала рассмотрим оба приложения.
Приложение Gerard’s Bicycle Shop
В самом деле есть чем восхищаться! Помимо всего прочего, не особо примечательного, магазин содержит один отличный компонент — LatestOffer
. Как видно, он выводит изображение и разноцветный текст. Все, что находится внутри оранжевой границы, включая и ее саму, является компонентом LatestOffer
. Он отображен в строке 8 в BicycleShop.jsx
:
import LatestOffer from "../LatestOffer/LatestOffer";
const BicycleShop = () => {
return (
<>
<MainMenu />
<Container style={{ padding: "60px 0 20px 0" }}>
<LatestOffer />
</Container>
...
</>
);
};
Сам компонент определен в отдельном файле LatestOffer.jsx
:
import bicycleImage from "./bicycle.jpg";
import styles from "./LatestOffer.module.scss";
const LatestOffer = () => {
return (
<div className={styles.latestOffer}>
<img src={bicycleImage} alt="Bicycle" />
<div className={styles.banner}>
<div>
<div>Latest offer!</div>
<div>The Jellybean 5000™</div>
</div>
<div>
<div>$ 1,999.00</div>
<div>$ 1,599.00</div>
</div>
</div>
<div className={styles.shadow} />
</div>
);
};
Обратите внимание на то, как мы использовали изображение и стилизацию в компоненте. Выбор пал на модули CSS, но это не обязательный вариант.
Вот так у нас появился небольшой магазинчик с компонентом LatestOffer
.
Переходим ко второму приложению.
Приложение Gerard’s Blog
Помимо магазина, у нас есть еще одно отдельное приложение React для простого блога. Внутри этого блога, а именно в области, обозначенной пунктирной линией, нам нужно отобразить новое предложение из магазина. Как вы помните, в этом и состоит наша цель.
Мы знаем о существовании компонента LatestOffer
. Однако он определен в репозитории Shop
, а не в Blog
.
Как же совместно использовать этот компонент между двумя приложениями? Как передать его из магазина в блог? Ответ — задействовать мощные функциональности Webpack 5 Module Federation. Хотя и придется попотеть, разбираясь в механизме его работы.
Начнем углубляться в тему и будем действовать пошагово.
Сборка кода с Webpack
Оба приложения имеют схожие настройки для сборки кода. Они задействуют Webpack и по большей части обладают идентичной конфигурацией.
Оба приложения имеют одну и ту же конфигурацию module-rules в файле webpack.config.js
:
module.exports = {
mode: "development",
...
module: {
rules: [
{
test: /\.s?css$/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
...
},
},
"sass-loader",
],
},
{
test: /\.jsx?$/,
use: ["babel-loader"],
exclude: /node_modules/,
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: "asset/resource",
},
],
},
...
};
Это гарантирует корректную загрузку и сборку таблиц стилей и файлов JavaScript/JSX. Но поскольку конфигурация Webpack не является главным объектом изучения статьи, то об этих правилах поговорим в другой раз.
Лучше разберемся, как расширить конфигурацию Webpack 5, чтобы предоставить компоненты на одном участке (приложение Shop
) и получить их на другом (приложение Blog
).
Module Federation: предоставление компонентов
Наша цель — поделиться компонентом LatestOffer
из приложения Shop
. Поэтому сначала мы преобразуем файл этого приложения webpack.config.js
, добавив экземпляр ModuleFederationPlugin
. В строках 11–17 рождается магия:
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
mode: "development",
...
module: {
...
},
plugins: [
...
new ModuleFederationPlugin({
name: "SHOP",
filename: "remoteEntry.js",
exposes: {
"./LatestOffer": "./src/components/LatestOffer/LatestOffer",
},
}),
],
};
Передаем в конструктор объект с указанными параметрами.
name
(строка 12). Определяет имя текущего проекта. Как вы помните, мы редактируем конфигурационный файл Webpack проектаShop
. Имя и верхний регистр выбраны произвольно.filename
(строка 13). Поясняется в следующем абзаце.exposes
(строки 14–16). Здесь определяем предоставляемые компоненты и имена, под которыми они будут доступны. В данном примере мы предлагаемLatestOffer
, присваивая ему имя./LatestOffer
и указывая путь к файлу определения компонента в виде строки./src/components/LatestOffer/LatestOffer
. Обратите внимание на обязательное наличие символов./
перед именем. Имя можно выбрать любое, но мы решили его продублировать. При необходимости этим способом можно предоставить несколько компонентов.
Теперь поговорим о filename
— remoteEntry.js
.
Другие приложения, которые намерены задействовать предоставленный компонент LatestOffer
, должны получить его определение, а именно код компонента. Иначе говоря, они должны суметь загрузить этот код.
Допустим, приложение Shop
работает по адресу localhost:8080
(как и в нашем примере).
Настраивая для параметра filename
значение remoteEntry.js
(оно произвольное), мы создаем маршрут localhost:8080/remoteEntry.js
. Как вы уже догадались, по нему среди шаблонного кода мы находим код компонента LatestOffer
:
Мы предъявили компонент и теперь поделимся им из приложения Shop
.
Посмотрим, как получить его на другом участке и отобразить компонент LatestOffer
в приложении Blog
.
Module Federation: удаленные компоненты
На следующем этапе рассмотрим конфигурационный файл Webpack приложения Blog
. Как и в случае с конфигурацией Shop
, добавляем экземпляр ModuleFederationPlugin
с параметрами:
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
mode: "development",
...
module: {
...
},
plugins: [
...
new ModuleFederationPlugin({
name: "BLOG",
remotes: {
SHOP: "SHOP@http://localhost:8080/remoteEntry.js",
},
}),
],
};
Передаем в конструктор следующие параметры:
name
(строка 12). Определяет имя текущего проекта.remotes
(строка 13–15). По сути мы говорим приложению: “Другое приложение под названиемSHOP
предоставляет компоненты! Давай загрузим их в проект!”. Ключ объектаSHOP
— это способ, позволяющий локально ссылаться на внешние компоненты (обсудим это позже). Значение представлено строкой, которая состоит из двух частей, соединенных знаком@
. Первая часть — имя удаленного приложения, а вторая — файл, содержащий код загружаемых компонентов (понемногу становится понятнее).
Теперь переходим к этапу отображения компонента LatestOffer
в Blog
.
Отображение удаленных компонентов
Рассмотрим компонент Blog
, который отображает компонент LatestOffer
:
import React, { lazy, Suspense } from "react";
const LatestOffer = lazy(() => import("SHOP/LatestOffer"));
const Blog = () => {
return (
<div className={styles.blog}>
<h1 className={styles.header}>Gerard's Blog</h1>
...
<Suspense fallback={<span>Loading...</span>}>
<LatestOffer />
</Suspense>
...
</div>
);
};
В строке 3 получаем ссылку на компонент LatestOffer
.
Мы применяем так называемый динамический импорт, и функция React lazy
обеспечивает возможность его выполнения. Эта тема повышенного уровня сложности, и с более подробной информацией по ней можно ознакомиться в документации React.
Загрузив (удаленный!) компонент, отображаем его в строках 10–12. Обратите внимание, что мы обертываем его в компонент высшего порядка (HOC) suspense
с резервным значением fallback
. Это значение будет отображаться во время загрузки удаленного компонента (если он еще не загружен). Как только загрузка выполнена, компонент LatestOffer
отображается в Blog
!
Заключение
На мой взгляд, Webpack 5 Module Federation был изначально слабо раскручен, о чем можно только сожалеть. Материал статьи подтверждает мощные возможности этого плагина. А ведь мы рассмотрели лишь малую часть его потенциала.
Ряд факторов сыграл не в пользу Module Federation.
Неудачное название
Большинство разработчиков, услышав термин “Module Federation”, не сразу вспомнят, о чем идет речь. Другое дело, если бы плагин Webpack назывался “ShareComponentsPlugin” (“Плагин совместного использования компонентов”).
Пугающая терминология
Зачастую в руководствах по Module Federation фигурирует понятие микрофронтендов. Уверен, вы потеряли половину своей аудитории, оперируя этим сложным и весомым термином. Не лучше ли сказать: “Хотите делиться компонентами между приложениями? Сейчас покажу как”.
В процессе разработчики сами увидят, что они могут не только поделиться простым компонентом, но и целым приложением. В этом и таится маленький секрет, поскольку само приложение React является просто еще одним компонентом.
Несовершенная документация
Даже Zack Jackson, активно участвующий в разработке и обслуживании Webpack и Module Federation, отмечает необходимость доработки документации.
Несмотря на эти недочеты, самое главное преимущество Module Federation заключается в его невероятной эффективности. Иначе говоря, мощь без границ!
Берите его в работу! Возможно, скоро вы с коллегами займетесь обслуживанием следующего приложения Web 4.0 на основе микрофронтендов.
Читайте также:
- Руководство по Webpack для начинающих
- Как с нуля создать проект на React, используя Webpack 4 и Babel
- Топ-7 библиотек React
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Gerard van der Put: Sharing React Components With Webpack 5