Webpack模块联邦与急切共享库不兼容。

25

我正在研究Webpack 5的模块联邦功能,但是有些困难理解为什么我的代码无法正常工作。 这个想法与标准的模块联邦示例非常相似:

app1 - 是主机应用程序 app2 - 是一个远程应用程序,向app1公开整个应用程序

(app1渲染标题和水平线,下面应该呈现app2)

app1app2都在weback.config.js中声明了reactreact-dom作为共享的单例依赖项:

// 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


我对总体概念有点迷惑,特别是:如果我有一个 shell 和不同的 UI,比如 A 和 B(微前端),那么 shell 是否可以打包 React、react-dom、materialui 依赖项,而 A、B 包则不需要? 在我的实验中,我发现 shell、A 和 B 的捆绑包大小相同,因此我怀疑 react、react-dom 等正在所有应用程序中捆绑,这打破了通过共享远程依赖项来减少捆绑包大小的目标。谢谢您的澄清! - cesarpachon
@cesarpachon 可以做到。要做到这一点,您必须配置联合插件以正确解决依赖项。我已经有一段时间没有使用它了,但是从我记得的来看:主机应用程序、应用程序A和应用程序B都应将React声明为共享依赖项,并且所需版本应该符合要求。在这种情况下,它只会被加载一次。 此外,不要忘记将React设置为急切模式,因为运行时中应该只有一个这样的库。 - volk
4个回答

17

为了让那些可能错过初始答案评论的人更清楚,我来解释一下:

似乎最初失败的主要原因是在实际运行宿主应用程序的代码之后加载了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

enter image description here

通过Oleg Vodolazsky提出的变体事件顺序如下:

  1. 首先加载remoteEntry.js,因为它直接添加到html文件中,webpack的main_bundle在远程入口链接后附加到<head></head>
  2. 加载main_bundle并运行应用程序

在此输入图片描述

如果只是尝试运行应用程序,没有使用Bootstrap并且没有在<head></head>中硬编码脚本,则main_bundle将在remoteEntry.js之前被加载,由于main_bundle试图实际运行应用程序,因此会出现错误:

在此输入图片描述


7
为了使其正常运行,您需要更改加载远程入口的方式。
  1. 更新webpack.config.js中应用程序1的ModuleFederationPlugin配置为以下内容:
...

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. 在您的应用程序app1的index.html文件的head标签中添加script标签:
<script src="http://localhost:2002/remoteEntry.js"></script>

祝你在进一步的黑客攻击中好运!

更新:

只是为了,我已经在你的沙箱存储库创建了一个PR,其中包含上述修复: https://github.com/vovkvlad/webpack-module-fedaration-sandbox/pull/2


3
为了澄清事情:您提出的更改是有效的,但据我通过实验了解,最终结果是remoteEntry.js应该在实际运行应用程序的捆绑包之前加载。这基本上就是bootstrap.js所做的——它使主应用程序在加载main_bundleremoteEntry.js之后加载。当使用您提出的更改时,它也会执行相同的操作——它在硬编码的脚本标记与remoteEntry.js之后下载remoteEntry.js以及main_bundle由Webpack添加。 - volk
这里是一个链接,包含所有三种情况的截图:https://imgur.com/a/63WTCMg - volk
4
我遇到了同样的问题。建议的解决方案不具备可扩展性,如果我们正在谈论开发或是 POC,那还好,但是当你拥有 3 个或以上环境的生产就无法使用这个解决方案。 如果有人有一个适用于动态导入的解决方案,我非常感兴趣知道您是如何解决这个问题的。 - pauldcomanici
1
有没有关于不使用引导文件或将脚本添加到所有远程条目的解决方案的消息? - krutoo
有人能在这里提供帮助吗?https://dev59.com/utdxpIgBRmDukGFE36p5 - Pranav MS

2
你可以在Module Federation的高级API中设置依赖为“eager”,这样不会将模块放入异步块中,而是同步提供它们。这允许我们在初始块中使用这些共享模块。但要小心,因为所有提供和回退模块都将被下载。建议只在应用程序的一个点(例如shell)提供它。
Webpack的网站强烈建议使用异步边界。它将分离出更大块的初始化代码,以避免任何额外的往返并提高性能。
例如,你的入口文件看起来像这样:
index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

让我们创建 bootstrap.js 文件,并将入口文件的内容移动到其中,然后将该 bootstrap 导入入口文件:
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'));

这种方法可行,但可能存在限制或缺点。

通过ModuleFederationPlugin,在webpack.config.js中将eager: true设置为依赖项。

// ...
new ModuleFederationPlugin({
  shared: {
    ...deps,
    react: {
      eager: true,
    },
  },
});

Source


除了 import(".bootstrap"),在 index.js 文件中是否还需要其余的导入和 ReactDom.Render()? - Chunky Chunk
@ChunkyChunk 不需要,因为我们已经在引导文件中有它了。 - hassan ketabi
1
我没有注意到“+”和“-”。现在我明白你写的了。 - Chunky Chunk

2
我在使用NextJS应用程序作为容器应用程序时也遇到了问题。你知道,我们不得不一次又一次地提供急切加载。
我采用了与当前Web技术不同的方法。我为我的远程库使用了动态加载技术,似乎现在不会再次获取共享模块。它们只被加载一次。此外,我实现了完整的系统框架无关,因此您可以使用任何框架作为远程应用程序(angular,vue,react,svelte...)。此外,我将SSR逻辑移动到远程应用程序部分,因此现在它可以完全支持任何框架的SSR。它看起来很有效,我想在这里分享我的解决方案以帮助社区。我写了一篇带有示例Github存储库链接的中等博客文章。我详细介绍了我的方法。
你可以在这里找到详细的文章: https://medium.com/@metinarslanturkk/how-i-implemented-dynamic-loaded-framework-agnostic-microfrontend-app-with-nextjs-and-react-which-620ff3df4298 这是示例存储库链接:https://github.com/MetinArslanturk/microfrontend-nextjs-ssr 谢谢,Metin

非常有趣的东西!你真的投入了很多工作。感谢指南和git示例。我实际上花了相当长的时间看你的代码,主要是next-host。使用它,我终于能够加载我的react远程而不会收到“无效钩子”消息。 - Dror Bar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接