Webpack 模块联合不适用于急切的共享库

我正在研究 Webpack 5 模块联合功能,但在理解为什么我的代码不起作用时遇到了一些麻烦。这个想法与标准模块联合示例所做的非常相似:

app1 - 是主机应用程序 app2 - 是将整个应用程序暴露给 app1 的远程

app1 呈现标题和水平线, app2 应在其下方呈现)

app1app2 都将 reactreact-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');

一切正常。

这让我感到困惑,因为创建者的 official docsblog posts 声明您可以使用 bootstrap.js 方式或将依赖项声明为急切的方式。

如果没有 bootstrap.js 模式,它将不胜感激任何帮助/见解。

这是我正在构建的完整 GitHub 沙箱的链接: https://github.com/vovkvlad/webpack-module-fedaration-sandbox/tree/master/simple

stack overflow Webpack module federation is not working with eager shared libs
原文答案
author avatar

接受的答案

为了使其工作,您需要更改加载远程条目的方式。

  1. 将 app1 的 ModuleFederationPlugin 中的 webpack.config.js 配置更新为:

    
    ...

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"] }
},
}),

...


2. 将  `script`  标签添加到 app1 中  `head`  的  `index.html`  中:


进一步的黑客攻击看起来不错!

更新:

只是为了它:我已经为您的沙盒存储库创建了一个 PR,并带有上述修复: [https://github.com/vovkvlad/webpack-module-fedaration-sandbox/pull/2](https://github.com/vovkvlad/webpack-module-fedaration-sandbox/pull/2) 

答案:

作者头像

只是为了让那些可能错过对最初答案的评论的人清楚:

最初失败的主要原因似乎是 remoteEntry.js 文件是在实际运行主机应用程序的代码之后加载的。

bootstrap.js 方法和将直接脚本 <script src="http://localhost:2002/remoteEntry.js"></script> 添加到 <head></head> 标记都具有完全相同的结果 - 它们使 remoteEntry.js 被加载并在 BEFORE 主应用程序代码之前进行解析。

在引导的情况下,顺序是下一个:

  1. main_bundle 已加载
  2. 将主代码提取到 bootstrap.js 文件中 - 加载 remoteEntry.js
  3. bootstrap.js 已加载,实际运行主应用程序

    enter image description here

由 Oleg Vodolazsky 提议的变体事件顺序是下一个:

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

    enter image description here

如果只是尝试在没有引导程序且没有硬编码脚本的情况下在 <head></head> 中运行应用程序 main_bundleremoteEntry.js 之前加载并且当 main_bundle 尝试实际运行应用程序时,它会失败并出现错误:

enter image description here

作者头像

您可以在 Module Federation 的高级 API 中将依赖项设置为 eager,它不会将模块放在异步块中,而是同步提供它们。这允许我们在初始块中使用这些共享模块。但要小心,因为所有提供的和后备模块将始终被下载。建议仅在您的应用程序的一个点提供它,例如贝壳。

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'));

引导程序.js

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

此方法有效,但可能有局限性或缺点。

通过 ModuleFederationPlugin 为依赖设置 eager: true

webpack.config.js

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

Source

作者头像

在将 NextJS 应用程序用作容器应用程序时,我也遇到了问题。你知道,我们不得不一次又一次地给急切的加载。

我采用了与网络上当前技术不同的方式。我为我的远程库使用了动态加载技术,现在似乎没有一次又一次地获取共享模块。它们只被加载一次。此外,我将整个系统实现为与框架无关,因此您可以将任何框架用作远程应用程序(angular、vue、react、svelte...)。此外,我将 SSR 逻辑移至远程应用程序部分,因此现在它也可以完全支持任何框架的 SSR。它似乎有效,我想在这里分享我的解决方案以帮助社区。我写了一篇带有示例 Github repo 链接的中型博客文章。我详细地讲述了我的方法。

你可以在这里找到详细的帖子: https://medium.com/@metinarslanturkk/how-i-implemented-dynamic-loaded-framework-agnostic-microfrontend-app-with-nextjs-and-react-which-620ff3df4298

这是示例 repo 链接: https://github.com/MetinArslanturk/microfrontend-nextjs-ssr

谢谢, 梅廷