Федерация модулей 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 ответа

Решение

Чтобы заставить его работать, вам нужно изменить способ загрузки удаленной записи.

  1. Обновите свой 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"] }
    },
}),

...
  1. Добавлять 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>к тегу имеет точно такой же результат - они загружаются и анализируются ПЕРЕД основным кодом приложения.

В случае бутстрапа порядок следующий:

  1. загружен
  2. поскольку основной код извлекается в файл - загружается
  3. bootstrap.js загружается, что фактически запускает основное приложение

в предложенном Олегом Водолазским варианте порядок мероприятий следующий:

  1. загружается первым, так как он напрямую добавляется к html файл и веб-пакет добавляются после ссылки remoteEntry
  2. загружается и запускает приложение

и в случае просто попытки запустить приложение без начальной загрузки и без жестко запрограммированного скрипта в <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

Спасибо, Метин

Другие вопросы по тегам