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

Posted

技术标签:

【中文标题】Webpack 模块联合不适用于急切的共享库【英文标题】:Webpack module federation is not working with eager shared libs 【发布时间】:2021-05-13 08:05:51 【问题描述】:

我正在研究 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.jsbootstrap.jsindex.js 中提取所有内容就可以了

import('./bootstrap');

一切正常。

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

如果没有bootstrap.js 模式,如果它无法工作,我们将不胜感激。

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

【问题讨论】:

【参考方案1】:

您可以在 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'));

bootstrap.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

【讨论】:

【参考方案2】:

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

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

bootstrap.js 方法和将直接脚本 &lt;script src="http://localhost:2002/remoteEntry.js"&gt;&lt;/script&gt; 添加到 &lt;head&gt;&lt;/head&gt; 标记具有完全相同的结果 - 它们使 remoteEntry.js 被加载和解析主应用程序的代码。

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

    main_bundle 已加载 由于主要代码被提取到bootstrap.js文件中-remoteEntry.js被加载 bootstrap.js 已加载,实际运行主应用程序

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

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

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

【讨论】:

【参考方案3】:

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

    将 app1 的 webpack.config.js 中的 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"] 
    ,
),

...
    script 标签添加到app1 中index.htmlhead
<script src="http://localhost:2002/remoteEntry.js"></script>

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

更新:

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

【讨论】:

所以只是为了让事情变得清楚:您提出的更改确实有效,但据我所知,从玩弄它的情况下,底线是应该在之前加载 remoteEntry.js实际运行应用程序的包。这基本上就是bootstrap.js 的作用——它使主应用程序在main_bundleremoteEntry.js 之后加载当使用您提出的更改时,它的作用相同——它在main_bundle 之前将remoteEntry.js 下载为main_bundle在使用 remoteEntry.js 硬编码脚本标记后由 webpack 添加 这里是所有三种情况的截图链接:imgur.com/a/63WTCMg 我遇到了同样的问题。并且建议的解决方案无法扩展,如果我们谈论的是开发或 POC 是可以的,但是当您拥有具有 3 个以上环境的生产就绪应用程序时,该解决方案将不起作用。如果有人有适用于动态导入的解决方案,我真的很感兴趣你是如何解决这个问题的。 有没有关于没有引导文件或向所有远程入口添加脚本的解决方案的消息?

以上是关于Webpack 模块联合不适用于急切的共享库的主要内容,如果未能解决你的问题,请参考以下文章

文件嵌套不适用于类或共享库项目

使用多个 git 存储库的 Webpack 模块联合。有例子吗?

如何使用 Webpack 建立一个私有的 React 组件共享库

Angular 模块联合单例服务多次启动

文件上传到 azure 文件共享不适用于更大的文件

如何使用 webpack 忽略模块?