模块联合共享API
#javascript #webpack #性能 #distributedsystems

这是模块联合会最有效的API之一,但是需要更多关于其工作方式以及如何帮助您的分布式应用程序性能的文档。

在这篇文章中,我们将探索共享API的各个方面,并解释如何使用它以及何时派上用场。 ð

共享API是什么?

那么,共享的API到底是什么? ðÖ是模块联合会中插件配置选项的一部分。您可以将其传递一个名为shared的数组或对象,其中包含其他联合应用程序可以共享和使用的依赖项列表(又称“远程”)。

new ModuleFederationPlugin({
  name: "host",
  filename: "remoteEntry.js",
  remotes: {},
  exposes: {},
  shared: [],
});

API定义:

shared (object | [string]):一个对象或一系列字符串,其中包含可以由其他联合应用程序共享和消费的依赖项列表。

eager (boolean):如果是true,则依赖性将急切地加载并在主机应用程序启动后立即提供给其他联合应用程序。如果为false,则在联合应用程序首次要求时,依赖性将懒惰地加载。

singleton (boolean):如果是真的,依赖项将被视为单身人士,只有一个实例将在所有联合应用程序中共享。

requiredVersion (string):指定依赖项的必需版本。如果联合应用程序试图加载依赖项的不兼容版本,则将加载两个副本。如果将singleton选项设置为true,则将在控制台中打印警告。

你为什么需要它?

当您有了联合模块时,它们会分别捆绑在一起,并包含所有功能所需的依赖项。但是,当它们在主机应用程序中使用时,可以下载相同依赖项的多个副本。这可能会损害性能,并使用户下载的JavaScript超过必要。 ð°

这是共享API真正方便的地方:您可以避免下载相同依赖项的多个副本并提高应用程序的性能。 ð

防止重复

让我们坚持WebPack的典型示例,然后共享和重复删除lodash

假设您有两个模块A和模块B,并且它们都需要lodash才能独立运行。

但是,当它们在将两个模块汇集在一起​​的主机应用程序中使用时,共享API就会发挥作用。如果已经有预加载的,可用的lodash的共享副本,则模块A和模块B将使用该副本而不是加载自己的独立副本。

此副本可以由主机或其中的其他远程应用程序提供。

new ModuleFederationPlugin({
  ...
  shared: ["lodash"],
});

提示:遥控器和主机都必须在“共享”中添加相同的依赖性才能进行消费。

Module Federation Sharing lodash

它是如何工作的?

如果您熟悉Dynamic Imports,则模块联合会几乎相同。它要求一个模块并返回一个承诺,该承诺可以使用包含来自exposes中声明的moduleName的所有导出的对象。

模块联邦的异步性质使shared API如此灵活。

异步依赖性加载

当需要模块时,它将加载一个名为remoteEntry.js的文件,列出了模块需求的所有依赖项。由于此操作是异步的,因此容器可以检查所有remoteEntry文件,并列出每个模块在shared中声明的所有依赖项;然后,主机可以加载单个副本并与所有需要它的模块共享。

因为shared依靠异步操作来检查和解决依赖项,如果您的应用程序或模块同步加载并声明shared中的依赖项,则您可能会遇到以下错误:

Uncaught Error: Shared module is not available for eager consumption

要解决上述错误,有两个选项:

急切的消费

new ModuleFederationPlugin({
  ...
  shared: { 
      lodash: {
          eager: true,
        },
});

个体依赖性可以标记为eager: true;此选项不会将依赖项放在异步块中,因此可以同步提供。但是,这意味着这些依赖关系将始终下载,可能会影响捆绑包的大小。推荐的解决方案是通过将其包裹到异步边界中,将模块异步加载:

使用异步边界:

注意:这仅适用于应用程序的输入点;通过模块联合消耗的远程模块会自动包裹在异步边界中。

要创建异步边界,请使用动态导入来确保您的入口点异步运行:

index.js

import('./bootstrap.js');

bootstrap.js

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

版本控制

那么,如果两个远程模块使用同一依赖项的不同版本,会发生什么?

ð¥?

nope ...一切都起作用了!

版本控制可能是共享API的最佳功能(如果不是最好的)功能之一,并且默认情况下也是启用的。

如果这些依赖项的语义版本范围不匹配,则模块联合足够强大,可以识别它们并提供单独的副本,因此您不会意外地加载包含破坏更改的错误版本。显然,这可能会引起性能问题,因为您最终下载了依赖关系的不同版本,但至少您的应用不会破坏。

Singleton加载

我知道你在想什么;如果我想保证始终加载一个给定依赖项的副本该怎么办。 (咳嗽反应,咳嗽)

singlton: true传递给依赖项对象,正是...

shared: {
        react: {
          singleton: true,
          requiredVersion: "^18.0.0",
        },
        "react-dom": {
          singleton: true,
          requiredVersion: "^18.0.0"
        },
      },

这是要注意的东西:如果您的一个远程模块试图加载已标记为单身人士的不兼容的依赖性版本,则WebPack将在控制台中打印以下警告:

但不要惊慌!构建实际上不会破裂,WebPack将继续捆绑和加载您的应用程序。但是,警告是要让您知道,由于兼容性问题,可能存在一些意外的行为。因此,如果您看到此警告,那么去对齐您的依赖性以避免任何问题是个好主意。

缺点 /权衡

一切都是权衡的,但是只有在您知道它们存在的情况下,您才能接受并减轻它们。这是您可能使用共享API遇到的一些问题:

依赖关系在运行时不匹配

由于应用程序是通过另一个WebPack进程在不同时间点构建的,因此它们不共享相同的依赖关系图,而且我们只能依靠语义版本范围来重复绘制并提供相同的依赖关系版本。

可能在某种情况下,您已经使用库的1.0.0构建和测试了遥控器,但是当主机加载时,因为语义版本范围^1.0.0满足了1.1.0,您可能最终在生产中在生产中加载1.1.0这可能存在或可能没有兼容性问题。

可以通过尽可能多地对齐版本来缓解这种方法(使用带有单个软件包的monorepo可能会有所帮助)。

这种缺点涉及我们对语义版本的信任范围而不是模块联合和共享API。在分布式系统(类似于微服务)中,需要合同来保证系统的稳定性和可靠性。在共享API的情况下,语义版本范围是合同(可能不是一个好的合同)。

根据我的经验,没有更好的替代方案来共享对分布式前端应用程序的依赖性,即使共享API并不完美,它也是我们现在拥有的最好的。

结论

总而言之,模块联合共享API是改善分布式应用程序性能的强大工具。它使您可以在模块之间共享依赖关系,并避免不必要的重复,从而产生更快的负载时间和更好的整体性能