Федерация модулей Webpack не работает с нетерпеливыми общими библиотеками

Я изучал функцию объединения модулей Webpack 5, и у меня возникли проблемы с пониманием того, почему мой код не работает. Идея очень похожа на то, что делают стандартные примеры объединения модулей:

app1 - это хост-приложение app2 - это удаленный доступ ко всему приложению app1

(app1 отображает заголовок и горизонтальную линию, ниже которой должен отображаться app2)

И 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 } },
    }),
    ...
  ],
};

В App1 index.js у меня есть следующий код:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";


ReactDOM.render(<App />, document.getElementById("root"));

Компонент App1 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 до bootstrap.js и в index.js сделаю

import('./bootstrap');

Все работает нормально.

Меня это смущает как официальные документы и сообщения в блоге создателя заявляют, что вы может сделать bootstrap.js способ ИЛИ объявить зависимость как нетерпеливую.

Был бы признателен за любую помощь / понимание того, почему он не работает без шаблона bootstrap.js.

Вот ссылка на полную песочницу GitHub, которую я создавал: https://github.com/vovkvlad/webpack-module-fedaration-sandbox/tree/master/simple


person volk    schedule 09.02.2021    source источник


Ответы (2)


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

  1. Обновите конфигурацию ModuleFederationPlugin в 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 в app1:
<script src="http://localhost:2002/remoteEntry.js"></script>

Хороший взгляд с дальнейшим взломом!

ОБНОВИТЬ:

Просто ради этого: я создал PR для вашего репозитория песочницы с исправлениями, описанными выше: https://github.com/vovkvlad/webpack-module-fedaration-sandbox/pull/2

person Oleg Vodolazsky    schedule 15.03.2021
comment
Итак, чтобы прояснить ситуацию: предлагаемые вами изменения работают, но, насколько я понял из экспериментов с ними, суть заключается в том, что remoteEntry.js должен быть загружен ДО пакета, который фактически запускает приложение. И это в основном то, что делает bootstrap.js - оно заставляет основное приложение загружаться после main_bundle и remoteEntry.js. При использовании предложенного вами изменения оно делает то же самое - загружает remoteEntry.js до main_bundle, поскольку main_bundle добавляется веб-пакетом после жестко запрограммированного тега сценария с remoteEntry.js - person volk; 15.03.2021
comment
Вот ссылка со скриншотами всех трех ситуаций: imgur.com/a/63WTCMg - person volk; 15.03.2021
comment
У меня такая же проблема. И предлагаемое решение не масштабируется, если мы говорим о разработке или POC в порядке, но когда у вас есть готовые к производству приложения с 3+ средами, это решение не будет работать. Если у кого-то есть решение, которое работает с динамическим импортом, мне действительно интересно, как вы решили эту проблему. - person darkyndy; 18.03.2021

Просто чтобы прояснить для тех, кто может пропустить комментарий к первоначальному ответу:

Похоже, что основная причина того, почему это изначально не удалось, заключалась в том, что remoteEntry.js файл был загружен после кода, который фактически запускает хост-приложение.

И подход bootstrap.js, и добавление прямого сценария <script src="http://localhost:2002/remoteEntry.js"></script> к тегу <head></head> имеют одинаковый результат - они заставляют remoteEntry.js загружаться и анализироваться ПЕРЕД кодом основного приложения.

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

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

введите описание изображения здесь

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

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

введите описание изображения здесь

и в случае простой попытки запустить приложение без начальной загрузки и без жестко запрограммированного сценария в <head></head> main_bundle загружается до remoteEntry.js, и поскольку main_bundle пытается запустить приложение, он завершается с ошибкой:

введите описание изображения здесь

person volk    schedule 15.03.2021