《webpack 5》中的微前端

2020-06-19 02:20:04

应用程序被拆分成较小的应用程序&34;部分";。这些可以是前端组件,如";Header&34;或";Sidebar&34;组件,也可以是逻辑组件,如";Data Fetching Logic";或其他业务逻辑。

使用模块联合,每个部分都将是单独的构建。这些内部版本编译为";Containers";。

在这种关系中,容器是";远程&34;,容器的消费者是";主机";。";远程";可以向";主机";公开模块。";主机";可以使用这些模块。它们被称为";远程模块";。

容器以异步方式公开模块。您可以要求容器加载/下载您想要使用的模块,然后再从容器中使用它们。异步公开允许构建将每个公开的模块及其依赖项放在单独的文件中。这种方式只需要加载使用过的模块,但容器仍然可以将模块捆绑在一起。此外,webpack的通常分块技术也适用于此(如供应商拆分或为公开的模块之间的公共依赖关系创建文件)。这允许保持较低的请求和总下载,从而带来良好的Web性能。

容器的使用者需要能够处理公开模块的异步加载。这里webpack将做一些戏法,我们稍后再来看。

另一个方面,共享模块,也在这里展示。每一方、容器和应用程序都可以将共享模块与版本信息一起放入共享范围。并且他们还能够使用共享范围中的共享模块以及版本要求检查。共享范围将以向各方提供符合版本要求的共享模块的最高可用版本的方式对共享模块进行重复数据消除。

还以异步方式提供和使用共享模块。因此,提供共享模块不需要下载成本。仅下载使用/使用的共享模块。

A";主页(来自团队A)使用下拉列表组件(来自团队B)。主页";上的A";登录链接按需加载(来自团队A)的登录模式(";LoginModal";),该链接使用(来自团队B的)按钮组件。

团队B想要构建一个容器,因此标记了他们的一些模块。";Button";和";DropDown";标记为";Exposed";,因为它们应该被其他团队使用。

";Reaction";标记为";Shared";,以便可能与其他团队共享。

webpack将自动为该容器生成容器条目。这个生成的模块将包含对所有公开和共享模块以及如何加载它们的引用。

当从容器加载Button";时,它只加载按钮块和反应块。当加载DropDown";时,它将只加载下拉块和Reaction块。

加载下拉列表时,如果另一方提供了另一个Reaction版本(可能更高),它将加载下拉块和提供的另一方Reaction版本的块(实际上,它会将加载操作委托给另一方,如果块尚未加载,他们将加载该块)。

团队B中的模块不直接包括在内。相反,仅使用";远程模块";存根。它们在运行时引用容器,并将(在运行时)从容器加载模块。

棘手的部分是异步加载。正常导入是同步的,不能等到模块下载后才能导入。一些webpack魔术将提升所有异步远程模块加载到下一个异步边界(如import())。此时,容器将在下载此块中的普通模块的同时加载远程模块。

一个例子是";Login";链接,它打开";LoginModal";。单击该链接时,将并行下载";LoginModal&34;的代码和";Button";的代码。

存在使用模块联合的ModuleFederationPlugin。不同的属性用于设置不同的方面。

要创建容器,exposes属性是重要的属性。这里指定了容器使用者应该可以访问的所有模块。可以给它们一个公共名称,这是使用者必须使用的名称,并将其指向自己代码库(内部请求)中的一个模块。支持任何模块,可以是javascript、typecript、css、webAssembly、Assets,所有webpack可以在您的代码库中处理的东西。

“使用其他容器”属性是“远程”属性,它是“转到”属性。它是一个对象,也将包含应该在当前版本中使用的容器。关键是一个模块作用域,在该作用域上,容器公开的模块应该可以在自己的代码库中访问。任何以该键开头的请求都将创建一个远程模块,该模块将在运行时加载。该值是容器的位置。默认情况下,外部脚本用作容器位置。在这里,用户将指定脚本文件的URL和全局。此脚本将在运行时加载,并从全局访问容器。

要在任何一侧共享模块,应使用Shared属性。对于简单的情况,可以提供模块说明符列表,将这些模块(在代码库中使用时)标记为共享模块。它们将在当前安装的版本中提供,并在消费软件包的Package.json中指定的版本范围内使用。

所有属性还支持高级配置选项。一个值得注意的高级选项是共享模块的singleton:true选项。它确保在运行时只创建模块的单个实例。这对于某些库是必需的,例如,Reaction不喜欢在同一应用程序中被多次实例化。在这种情况下,无效的版本范围只会在运行时导致警告。

更高级的选项允许覆盖或禁用自动推断的值,如版本、Required dVersion或FileName,允许使用库和外部的不同方式,例如用于Node.js或选择更严格的版本检查(当版本范围无效时,这会导致错误而不是警告)。

现在我们已经建造了所有这些独立的容器。现在是把它们放在一起的时候了。这就是有趣的开始。

在客户端使用托管方法的一种方法是巧妙地结合使用服务器相对URL和基于锁定版本重定向请求的简单API。

每个应用程序都放在不同的(子)域上。示例:app.example.com和admin.examples。可以为应用程序的多个阶段添加额外的嵌套。示例:myapp.example.com用于生产版本,staging.myapp.example.com用于临时版本。

所有容器都由指向所有域上可用的API的服务器相对URL引用:/_remotes/<;name>;。取决于将解析为绝对URL(如https://staging.myapp.example.com/_remotes/analytics.)的应用程序。API现在可以从该请求中提取引用应用程序和目标容器。它将在应用程序的锁文件中查找容器,并将请求重定向到容器的最终url,例如302location:https://static.example.com/analytics/3fe867c.js.。

重定向有点不完美,因为它需要往返到服务器2次才能加载容器。在第二次访问时,容器本身可能被缓存,但是由于重定向不能被缓存(以允许随时更改锁文件),它仍然需要一次往返。

/_remotes/API可以直接使用容器javascript进行响应。这会将初始加载减少到1个往返,但第二个访问案例仍将使用1个往返,因为我们无法正确缓存API。

因此,在保留API解决方案作为备用方案的同时,我们添加了一个附加层。我们使用一些服务器逻辑生成HTML,并将脚本标记直接注入到最终的容器URL。当脚本标签上设置了数据-webpack=<;名称>;时,webpack中的模块联盟就可以获取它们。(它也可以直接拾取暴露的全局变量,但是当所有脚本标签都是异步的,并且应用程序脚本可能在容器脚本之前执行时,需要Data-webpack。在本例中,加载逻辑将找到Data-webpack脚本标记并将事件处理程序附加到它。)。

这将导致初始页面加载的一次往返,第二次访问的0次往返。

从技术上讲,也可以将容器脚本直接内联到HTML中,因为它们非常小(只有几kB)。这将导致初始页面加载的圆角也为0。但是这种解决方案在扩展许多容器时有点问题,因为它会增大HTML的大小并阻止容器的缓存。

要使用Everygreen方法,请始终重定向到容器的最新版本。

在服务器端使用托管方法的一种方法是,对编译后的dist文件也使用npm/yer Package.json。

对于这种方法,您需要一个私有的包注册表,您可以将编译后的dist文件推送到该注册表。

要部署容器,请使用webpack输出文件和Package.json创建一个包。在本例中,我们使用@Containers/a作为编译后的容器包的前缀。分析容器包可能如下所示:

要创建使用容器的应用程序,您可以执行相同的操作(可能使用不同的前缀,如@apps/),并将容器包指定为依赖项:

现在,您可以在服务器引导过程中运行NPM安装/纱线。这会将容器从您的私有注册表下载到node_module中。模块联合运行时将通过REQUIRED(";@CONTAINER/analytics";)加载它们。所有代码都将集中在同一个node.js进程中。

要使用托管方法,请将Package-lock.json/ya n.lock文件与dist Package.json一起保存。您可以将此文件作为应用程序报告的一部分提交。您可以使用多个锁定文件,例如,用于试运行和生产环境。

要更新到容器的最新版本,通常可以使用NPM/YORAN实用程序,或者可以删除并重新生成锁定文件。

要使用Everygreen方法,只需保留一个Package.json,而不保留锁定文件。混合方法可能需要一些额外的工具。

你看,这里没有明确的取胜途径。模块联盟并不是在所有类别中都是完美的,它在所有类别中都是一个很好的权衡。

每个属性都委托给一个较低级别的插件,从技术上讲,该插件也可以单独使用。值得一提的是SharePlugin,它也可以用来共享不同系统中的模块。