什么是module federation
module federation用来解决多个应用之间「代码共享」的问题,让我们更加优雅的实现「跨应用」的代码共享。
❝Module federation allows a JavaScript application to dynamically run code from another bundle/build, on both client and server.
❞
简单来说就是允许「运行时」动态决定代码的引入和加载。
「动态」,包含两个含义:
- 按需,可以把一个包拆开来加载其中一部分;
- 运行时,跑在浏览器而非 node 编译时;
假如现在有两个应用,A应用里有一个Input组件,B应用里有个Image组件。我们需要将B里的Image组件移植到A里去,并且保证后续迭代一致。可以用哪些方法呢?
- 直接拷贝
- 将Image组件打包,发布到我们的npm上
一方法简单直接,最大的问题是不能同步,假如B应用的开发修改了Image组件,此时AB应用的Image就不一致,可能带来一些问题。二方法是现在用的比较多的,但也存在打包发布npm更新这些步骤。
module federation使用
创建两个应用A、B。 A的结构如下:
├─package-lock.json
├─package.json
├─webpack.config.js
├─src
| ├─App.js
| ├─root.js
| ├─index.js
| └input.js
├─public
| └index.html
B的结构如下:
├─package-lock.json
├─package.json
├─webpack.config.js
├─src
| ├─App.js
| ├─root.js
| ├─image.js
| └index.js
├─public
| └index.html
AB的不同在于内部各自有一个Input组件和一个Image组件。
此时,如果我们想在A应用里调用B应用的Image组件,使用webpack5的方法。
几个概念
module:每一个源码 js 文件其实都可以看成一个 module chunk:每一个打包落地的 js 文件其实都是一个 chunk,每个 chunk 都包含很多 module host:在页面加载过程中(当 onLoad 事件被触发)最先被初始化的 Webpack 构建 remote:被 host 消费的 webpack 构建
第一步:配置webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
new ModuleFederationPlugin({
name: "appA",
filename: "remoteEntry.js",
exposes: {
"./input": "./src/input",
},
remotes: {
appB: "appB@http://localhost:3002/remoteEntry.js",
},
shared: ['react', 'react-dom'],
})
name:标记当前服务的名字 filename:提供给其他服务的加载文件 exposes:表示当前应用是一个remote,exposes内的模块可以被其他的host引用,引用方式为import({expose}) remotes:表示当前应用是一个host,可以使用remotes里的模块 shared:公共依赖,解决重复依赖,提升性能
注意点:A、B项目需要同步修改,都使用webpack5。
第二步:改动引用
应用A:
此处引用的Image来自B应用,地址就是我们在webpack.config.js里所配置的地址。
启动两个项目。
此时查看A应用,可以发现A应用引用了B应用的js文件。
此外,配置shared公共依赖后,两个应用引入的react和react-dom为同一个,减少了应用的重复加载,提升了性能。
我们在配置项目的时候,同时对两个项目的webpack.config.js都配置了ModuleFederationPlugin,两个项目可以互为remote,可以正常使用。
此时改动B应用的Image组件,A应用里的Image组件动态更新了。(牛逼了!)
原理探究
使用import
我们通常是将逻辑直接放在index里使用,这里却放在了root.js里,而入口文件使用了import('./root')。
如果直接放index,会报错。原因是加载B应用的Image组件依赖B应用的remoteEntry文件,直接运行可能导致remoteEntry文件尚未加载完毕,导致出错。从js加载顺序也能看出remoteEntry是先于src_image_js.js加载的。而import()保证了引入文件是个异步逻辑。

加载分析
main.js先加载了appB的remoteEntry.js
而B应用里的remoteEntry.js暴露了exposes里配置的组件,供A应用使用。
A应用里处理一些运行时的代码外会执行A应用的主逻辑root.js
而root.js打包完后的文件src_root_js.js内部是一些具体代码的转换。其他都是以往的正常的__webpack_require__函数来获得的module,其中来自B应用的Image组件是这样引用的:
可以看到使用了__webpack_require__.e来加载,这个方法在A应用的main.js里有定义:
通过传入的chunkId在__webpack_require_.f中查找对应的chunk
「webpack_require」.f中有remotes方法,因为Image是来自remotes的,简化一下remotes方法发现它最终是要return一个promises。会在__webpack_rquire__.e里使用promise调用。

调用的最终结果就是引用B应用里的Image组件。
再观察src_root_js.js文件的第一行,实际上它将window上挂载了一个“webpackChunk_webpack5_demo_appb"对象,我们所需要的remotes全在这上面可以获得。
可知:module federation的模块共享实际上就是将module放在了全局变量上。
对比其他方案
dll
dll是预打包一些公共的不会变化的模块。假如模块变更还是需要重新dll。
externals
将某些包排除在打包文件中,通过运行时去加载。可能需要改动html文件;依赖包全量引用浪费资源;不是所有包都支持umd。
缺点
必须全部使用webpack5,可能会有其他问题。
❝所以今天(2020-10-10)webpack 5.0.0 发布了,但这并不意味着它已经完成了,没有 bug,甚至功能完整。 就像 webpack 4 一样,我们通过修复问题和增加功能来继续开发。 在接下来的日子里,可能会有很多 bug 修复。功能会在以后出现
❞

参考文章
webpack.docschina.org/concepts/mo… module-federation.github.io/blog/get-st… github.com/module-fede… medium.com/@eymaslive/… developer.aliyun.com/article/755… mp.weixin.qq.com/s/iS-prT1xZ…