Федерация модулей Webpack не работает с нетерпеливыми общими библиотеками
Я изучал функцию объединения модулей Webpack 5, и у меня возникли некоторые проблемы с пониманием того, почему мой код не работает. Идея очень похожа на то, что делают стандартные примеры объединения модулей:
- это хост-приложение - это удаленный доступ ко всему приложению
(отображает заголовок и горизонтальную линию, ниже которой должны отображаться)
Обе
app1
и
app2
заявляет
react
и
react-dom
как их общие, одноэлементные, нетерпеливые зависимости в
weback.config.js
:
// app1 webpack.config.js
module.exports = {
entry: path.resolve(SRC_DIR, './index.js');,
...
plugins: [
new ModuleFederationPlugin({
name: "app1",
remotes: {
app2: `app2@//localhost:2002/remoteEntry.js`,
},
shared: { react: { singleton: true, eager: true }, "react-dom": { singleton: true, eager: true } },
}),
...
],
};
// app2 webpack.config.js
module.exports = {
entry: path.resolve(SRC_DIR, './index.js');,
...
plugins: [
new ModuleFederationPlugin({
name: "app2",
library: { type: "var", name: "app2" },
filename: "remoteEntry.js",
exposes: {
"./App": "./src/App",
},
shared: { react: { singleton: true, eager: true }, "react-dom": { singleton: true, eager: true } },
}),
...
],
};
В App1index.js у меня есть следующий код:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
Приложение1
App.js
компонент следующий:
import React, { Suspense } from 'react';
const RemoteApp2 = React.lazy(() => import("app2/App"));
export default function App() {
return (
<div>
<h1>App 1</h1>
<p>Below will be some content</p>
<hr/>
<Suspense fallback={'Loading App 2'}>
<RemoteApp2 />
</Suspense>
</div>
);
}
Но когда я запускаю приложение, я получаю следующую ошибку:
Uncaught Error: Shared module is not available for eager consumption: webpack/sharing/consume/default/react/react?1bb3
at Object.__webpack_modules__.<computed> (consumes:133)
at __webpack_require__ (bootstrap:21)
at fn (hot module replacement:61)
at Module../src/index.js (main.bundle.a8d89941f5dd9a37d429.js:239)
at __webpack_require__ (bootstrap:21)
at startup:4
at startup:6
Если я извлечу все от и до
index.js
Сделаю
import('./bootstrap');
Все работает нормально.
Это сбивает меня с толку, поскольку в официальных документах и сообщениях в блогах создателя говорится, что вы можете поступить любым способом ИЛИ объявить зависимость как нетерпеливую.
Был бы признателен за любую помощь / идеи о том, почему он не работает без
bootstrap.js
шаблон.
Вот ссылка на полную песочницу GitHub, которую я создавал: https://github.com/vovkvlad/webpack-module-fedaration-sandbox
4 ответа
Чтобы заставить его работать, вам нужно изменить способ загрузки удаленной записи.
- Обновите свой
ModuleFederationPlugin
config вwebpack.config.js
для app1 к этому:
...
new ModuleFederationPlugin({
name: "app1",
remoteType: 'var',
remotes: {
app2: 'app2',
},
shared: {
...packageJsonDeps,
react: { singleton: true, eager: true, requiredVersion: packageJsonDeps.react },
"react-dom": { singleton: true, eager: true, requiredVersion: packageJsonDeps["react-dom"] }
},
}),
...
- Добавлять
script
тег кhead
вашейindex.html
в приложении1:
<script src="http://localhost:2002/remoteEntry.js"></script>
Хороший взгляд с дальнейшим взломом!
ОБНОВИТЬ:
Просто ради этого: я создал PR для вашего репозитория песочницы с исправлениями, описанными выше: https://github.com/vovkvlad/webpack-module-fedaration-sandbox/pull/2
Просто чтобы прояснить для тех, кто может пропустить комментарий к первоначальному ответу:
Похоже, что основная причина того, почему это изначально не удалось, заключалась в том, что файл был загружен после кода, который фактически запускает хост-приложение.
Оба подхода и добавление прямого сценария
<script src="http://localhost:2002/remoteEntry.js"></script>
к тегу имеет точно такой же результат - они загружаются и анализируются ПЕРЕД основным кодом приложения.
В случае бутстрапа порядок следующий:
- загружен
- поскольку основной код извлекается в файл - загружается
-
bootstrap.js
загружается, что фактически запускает основное приложение
в предложенном Олегом Водолазским варианте порядок мероприятий следующий:
- загружается первым, так как он напрямую добавляется к
html
файл и веб-пакет добавляются после ссылки remoteEntry - загружается и запускает приложение
и в случае просто попытки запустить приложение без начальной загрузки и без жестко запрограммированного скрипта в
<head></head>
загружается до
remoteEntry.js
и, как
main_bundle
пытается запустить приложение, но выдает ошибку:
Вы можете установить зависимость как активную внутри расширенного API-интерфейса Module Federation, который не помещает модули в асинхронный фрагмент, а предоставляет их синхронно. Это позволяет нам использовать эти общие модули в начальном блоке. Но будьте осторожны, так как все предоставленные и резервные модули всегда будут загружены. Рекомендуется предоставлять его только в одной точке вашего приложения, например, в оболочке.
Веб-сайт Webpack настоятельно рекомендует использовать асинхронную границу. Он разделит код инициализации на более крупный фрагмент, чтобы избежать дополнительных обходов и повысить производительность в целом.
Например, ваша запись выглядела так:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
Давайте создадим файл bootstrap.js, переместим в него содержимое записи и импортируем этот загрузочный файл в запись:
index.js
+ import('./bootstrap');
- import React from 'react';
- import ReactDOM from 'react-dom';
- import App from './App';
- ReactDOM.render(<App />, document.getElementById('root'));
bootstrap.js
+ import React from 'react';
+ import ReactDOM from 'react-dom';
+ import App from './App';
+ ReactDOM.render(<App />, document.getElementById('root'));
Этот метод работает, но может иметь ограничения или недостатки.
Установка eager: true для зависимости через ModuleFederationPlugin
webpack.config.js
// ...
new ModuleFederationPlugin({
shared: {
...deps,
react: {
eager: true,
},
},
});
Я также столкнулся с проблемами при использовании приложения NextJS в качестве приложения-контейнера. Вы знаете, нам приходилось давать рвущиеся заряды снова и снова.
Я следовал другому пути, отличному от нынешних методов в Интернете. Я использовал метод динамической загрузки для своей удаленной библиотеки, и кажется, что общие модули теперь не загружаются снова и снова. Они загружаются только один раз. Кроме того, я реализовал полную систему как независимую от фреймворка, поэтому вы можете использовать любой фреймворк в качестве удаленного приложения (angular, vue, react, svelte...). Кроме того, я перенес логику SSR в часть удаленного приложения, чтобы теперь она могла полностью поддерживать SSR и для любой платформы. Кажется, это сработало, и я хотел поделиться своим решением здесь, чтобы помочь сообществу. Я написал сообщение в блоге среднего размера с примером ссылки на репозиторий Github. Я рассказал свой подход с деталями.
Вы можете найти подробный пост здесь: https://medium.com/@metinarslanturkk/how-i-implemented-dynamic-loaded-framework-agnostic-microfrontend-app-with-nextjs-and-react-what-620ff3df4298
А это пример ссылки репо: https://github.com/MetinArslanturk/microfrontend-nextjs-ssr
Спасибо, Метин