Module Federation in Webpack5(上)

从提供依赖共享的第三方或者其余的Webpack构建中import()模块!运行时引入!欢迎来到Module Federation前端

Module Federation的起源项目为webpack-external-import,现已经并入 Webpack5, v2.2.4为最后独立发布的版本。react

原始文章信息

修改部分

  1. 原文为PPT,章节为本人划分。
  2. 部分图片转换为文字翻译插入文章中。
  3. 我的添加的内容标记为"注"。

动机

Module Federation的动机是为了避免同开发小组间共同开发一个或者多个应用。webpack

应用将被划分为更小的应用块,一个应用块,能够是好比"Header"或者"Sidebar"的前端组件,也能够是逻辑组件好比"Data Fetching Logic"或者其余业务逻辑。git

每一个应用块由不一样的组开发。github

应用应用块共享其余其余应用块或者库。web

这也就是Micro Frontends(MFES)的开发模式。json

现有的方案

先看看现有的Webpack方案:架构

原生ESM模块

  • 无需构建连接的模块
  • 外部模块采用原生加载

挑战:异步

  • 没有exports优化
  • 性能
    • 须要preloading
    • 高请求次数
    • 按需加载须要屡次往返
  • 只能支持ESM,不支持CommonJS, CSS和其余资源文件

单个构建

  • 应用和应用块一块儿构建
  • 外部模块在构建的时候肯定而且被引用

挑战:ide

  • 每次更新都须要全量构建
    • 部署花费的时间很长
  • 多应用不在独立
    • 或者应用间在运行时再也不共享公共模块

DllPlugin

  • 应用块被编译成Dll
  • 应用构建时引用Dll
  • 外部模块在运行时从Dll中被引用

挑战:

  • 每次应用块改变的时候,应用都须要从新构建
    • 额外的部署花费时间
    • 处理编译时依赖的额外架构
  • 额外的Dll须要生成
    • 为了共享依赖
    • 为了手动优化使用过的模块
  • 全部引用的Dll都须要额外的script标签
  • Dll没有按需加载

externals

  • 应用块被构建成库(一个暴露模块一个入口)
  • 应用打包时声明应用块externals
  • 外部模块在运行时被引用,好比从全局变量

挑战:

  • 额外的库须要建立
  • 为了共享依赖
  • 全部引用的都externals须要额外的script标签
  • externals没有按需加载

总结

  • 原生ESM模块在大规模应用中,web性能很差
  • 单个构建在大规模应用中,构建性能很差
  • Dll在多应用中伸缩性很差
  • Dllexternals都须要为共享依赖作不少额外的人工处理

咱们须要一个可伸缩的解决方案,提供如下要求之间的取舍:

  • 良好的构建性能
  • 良好的Web性能
  • 解决依赖共享

因此咱们发明了Module Federation

Module Federation

使用Module Federation时,每一个应用块都是一个独立的构建,这些构建都将编译为容器

容器能够被其余应用或者其余容器应用。

一个被引用的容器被称为remote, 引用者被称为hostremote暴露模块给host, host则可使用这些暴露的模块,这些模块被成为remote模块

使用独立构建,咱们能够为整个系统得到一个良好的构建性能。

概览

![](https://user-gold-cdn.xitu.io/2020/6/19/172cb4ff1a4e243d?w=3059&h=1567&f=png&s=1406764)

看图说话,这就是Module Federation概览, 展现了2个概念: 暴露模块和共享模块。

容器经过异步的方式暴露模块。你将在使用容器中的模块前,请求容器加载(下载)你想要的模块。异步暴露模块将容许构建结果将不一样的暴露模块和他们的依赖一块儿,放在不一样的文件中。从而使得只有须要使用的模块会被加载,可是容器依旧将不一样的模块一块儿打包。并且也将使用webpack的chunk机制(vendor分割或者建立一个文件包含不一样暴露模块之间的公共依赖等).这将帮助咱们有效下降请求数量和下载大小,从而得到良好的Web性能。

容器的消费者(也就是应用者)须要能处理异步加载暴露模块(注: 同步import代码语义保持不变,可是运行时应转换为异步加载), Webpack在这里作了特殊的处理,咱们后续会解释。

图上还展现了共享模块的概念。 每个部分,好比容器, 应用,均可以在共享scope中,添加共享模块(携带版本信息),同时也能够从 共享scope中, 加载共享模块(须要执行版本检查)。 共享scope将会经过给每一个消费者提供版本要求内的最大可用版本的方式,对共享模块进行冗余剔除。

共享模块依旧异步暴露和异步加载。因此提供共享模块没有额外的下载消耗,只有须要的共享的模块将会被下载。

一个例子

上面是一个独立构建的例子。

HomePage(Team A开发)使用了组件Dropdown(Team 开发)。HomePage按需引用了LoginModal(Team A开发),LoginModal使用了Button组件 (Team 开发)。

全局的全部模块几乎都依赖了react。

让咱们放出Module Federation跑跑。

Team B


从Team B的角度,它只关心这些东西。

Team B但愿构建一个容器,而且给予这些模块标记。

Button和Dropdown是"Exposed",他们将能够被其余小组的人引用。react将是"Shared", 它将能够被其余小组的人共享。

如今轮到Webpack上场了

webpack将会为这个容器生成一个容器入口. 这个模块将包含全部暴露模块和共享模块的引用和加载方式。

每一个暴露模块都和他们的依赖一块儿,被放入独立的文件。

每一个共享模块也会被放入一个独立的文件。

当从容器中加载Button时,只会加载button chunk和react chunk. Dropdown也是同理。

当加载Dropdown,可是其余部分已经提供了另外一个版本的react(多是更高版本),则将会加载dropdown chunk以及其余部分提供的react chunk(经过其余部分请求,若是这个react chunk还没有加载)。

Team A

如今来看看Team A如何使用Team B的这个容器

来自Team B的模块并非直接被打包进来(注: 引入Team B提供的容器入口的时候),而是只打包了remote module的标记。在运行时将会引用容器而且会从容器加载模块。

webpack在这里作了特殊处理,就是异步请求。一个常规的import是同步的,并不会等待模块下载。webpack在这里作了黑魔法,将全部的异步remote module的加载处理成了异步语义(就像import()). 此时,就能够在加载本chunk的其余普通模块的同时,经过容器并行加载remote module

好比上图中,当HomePage请求打开LoginModal的时候,LoginModal和Button的代码将并行加载。

共享模块也是相似。

ModuleFederationPlugin

咱们经过设置ModuleFederationPlugin来使用Module Federation,配置属性少不了。

对于建立容器,重点是exposes属性,这个属性指明了这个容器的消费者将能够获取什么模块。能够给暴露的模块取名,同时要说明对应的内部模块的入口。任何Webpack能处理的模块它都支持,JS,TS,CSS, WebAssembly等等。

对于消费(引用)容器来讲,要设置remotes属性, 这个属性是一个对象,包含了全部当前构建可用的容器。key值是当前代码可访问的容器暴露模块的scope(注:就是前缀)。任何以key值开头的模块请求都在运行时请求remote module。值是容器的位置。默认状况下,容器的入口来自script externals(注意: 引用的容器的entry文件经过script标签的方式手动引入页面)。固然也能够指定URL、script文件,或者全局对象均可以。这个脚本(注意: 引用的容器的entry文件)在运行时会被加载,并且能够从全局对象中访问。

若是想共享模块,就须要设置shared属性。最简单的例子就是列举一堆共享的模块名称,他们将会以当前安装的版本提供,而且被消费(注:被其余人引用)时候会按照引用者package.json中的版本要求来加载。

每一个选项都有一些高级选项,一个值得注意的选项是共享模块里的singleton: true。它能确保运行时模块的单例。这能知足有些库好比react等不该初始化屡次的要求。这种状况下,版本要求不符将会在运行时产生一个警告信息。

还有更多高级选项好比覆写版本或者版本要求,关闭版本推断,文件名,容许使用不一样来源的库或者外部依赖(NodeJS中),或者更严格版本警告(直接给错误而不是警告)。

相关文章
相关标签/搜索