Module federation让一个JS APP能够在server端或者client端运行来自其他
bundle/build
中的代码。
背景
共享code/组件
一直是一个永恒的话题。
在同一APP中想要复用/共享一段代码很简单,可以提出一个function,如果是UI级别的复用,那么就是提出一个组件,通过模块的import/export实现。
但是在不同的APP中想要共享/复用一段代码/组件就没有那么简单了,通常都只是通过提出一个npm包,放在npm register中统一管理,不同的APP如果想要使用,就从npm register安装。如果遇到版本升级,所有使用该库的APP都需要手动进行升级,依然不是很方便。
What is Module Federation?
Module Federation是webpack5中的新功能,这个功能能够让一个JS APP动态
的从另外一个JS APP加载代码,利用最小的花费实现代码的复用。
从另一个JS APP中加载来的代码通常被叫做federated module code
。
加载来的代码还能够直接使用当前APP中的shared dependency
作为自己的dependency, 如果当前APPshared dependencies
中并没有其所需的dependency,那么webpack会从federated module code所在的APP中下载。
Terminology
-
Module federation
: 在多APP(浏览器/nodejs)中实现动态
地代码共享, -
A host
: 其实就是一个可以自己运行的APP,但是会作为消费者,从其他的APP中请求federated module code。 -
A remote
: 其实是另一个APP, 但是会提供federated module code -
Bidirectional-hosts
: 即可以自己运行作为host
消费其他APP的federated module code,也可以作为remote
为其他的APP提供federated module code。
SPA Bidirectional-hosts
假设我们计划做一个SPA, 这个APP可能有三个页面:
- Dashboard页面
- SRP页面
- PDP页面
我们期待这个三个页面可以独立开发,独立部署,独立运行
, 那么webpack federation就是一个很好的工具。
每一个页面都是一个单独的APP,有独立的repo,独立开发,独立部署,独立运行。
因此每一个页面,当然也是每一个APP都是bi-directional hosts
.
任何一个页面(APP),如果先打开(先load)那么就是host
, 如果要跳转到其他页面,也能够利用react router,做一个动态import
从其他page(APP)中,把相应页面的组件加载过来。此时在新的页面上刷新,那么当前页面所在的APP就会被load,此时当前页面所在的APP就变成了host。
这样,由于每一个page(APP)
- 都使用react router成为了一个SPA,因此可以作为host
- 都使用webpack federation module,将当前页面的根组件作为federated code暴露出去,因此也可以作为remote。
这样,如果你了解过Route级别的code split,那么页面之间的跳转就不再需要external跳转,就只需要使用动态import,从remote将新页面的组件取回即可。
如何处理federated component的dependency
加入App A暴露了一个APP component, 这时候APP B作为消费者会使用APP component,但是APP component 可能依赖于
"react", "react-dom" 。
那我们的处理方案是否和一般的npm package一样,将该package需要使用的所有依赖都包含在这个npm package中?
这样做会出现的问题:
- 包非常的大
- 如果npm package需要依赖react,如果其将react打入到了最终的npm包中,如果APP B也是一个react app,那么极有可能出现APP B中有两React,这个是绝对不能被React 允许的
这种时候对于NPM包我们会想到使用peer dependency,对就是直接让包使用宿主中的依赖。
同理webpack federation也提供了shared
字段,也就是明确写明,当前的repo中能够用于和remote共享的依赖是那些。
这样从其他APP引入的component可以直接使用宿主的shared dependency,尽可能减少code duplication
实例
APP1
- APP1中将会一个根组件
APPContainer
, 这个组件可以被其他APP消费 - APP1也是一个SPA, 可以独立运行,也可以作为消费者消费其他APP的组件
- 由于APP1会做消费者(host),因此会和federated Module共享依赖
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
// other webpack configs...
plugins: [
new ModuleFederationPlugin({
name: '_app_one_remote', // 当前APP作为remote暴露组件时的APP的名字
library: 'app_one_remote', // 当前APP作为remote暴露组件时的library名字
filename: 'remoteEntry.js',
// 所有被暴露的组件会打包到这个文件中,同理使用者也需要从这里引入
remotes: {
app_two: "app_two_remote",
app_three: "app_three_remote"
}, // 定义该库作为host时可能要引用的remote
exposes: {
'AppContainer': './src/App'
}, // 定义该库作为remote时,要暴露出去的组件。左边是相对路径和组件名字(其他库使用时候),右边是该组件在本库内的路径
shared: ["react", "react-dom","react-router-dom"]// 和引入的组件公用的dependency
})
]
};
那么当APP1作为remote的时候,其他的库是如何使用APP1暴露出去的组件APPContainer呢?很简单、
APP1已经被部署和独立运行后,由于webpack的构建,该APP会有不同的入口和bundle。
- 正常的入口是index.js保证APP的运行
- 另外一个入口是暴露出去的组件
因此一旦这些打出来的静态文件被服务器server,比如服务运行在localhost:3000,
- 你可以通过访问
http://localhost:3000
看到APP正常运行 - 也可以访问
http://localhost:3000/app_one_remote.js
获取到该APP暴露出来的组件。
作为consumer你可以这样引入来自APP1的组件
- 配置webpack,使其可以顺利的找到remote的组件
const ModuleFederationPlugin = require('webpack-plugin-module-federation');
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: '_app_two_remote',
library: 'app_two_remote',
filename: 'remoteEntry.js',
libraryTarget: 'global',
remotes: {
'app_one_remote': '_app_one_remote'
},
expose: {
App: './src/App'
},
}),
]
};
- 首先需要在HTML中引入remote的组件包文件
<head>
<script src="http://localhost:3001/remoteEntry.js"></script>
</head>
<body>
<div id="root"></div>
</body>
- 动态import将要使用的组件从HTML中加载回来的remoteEntry.js文件中引入
import React, { lazy, Suspense, useState } from 'react';
import Footer from './Footer';
const AppContainer = lazy(() => import('app_one_remote/AppContainer')); // federated
export default () => {
return (
<>
<Suspense fallback={'fallback'}>
<AppContainer />
</Suspense>
<p>
// ....
</p>
<Footer />
</>
);
};
使用场景
- Domain
通常对于一个可能涉及多个不同sub site的网站,比如举个例子房产网站,类似于安居客之类的。这样的网站大多会涉及多种不同的业务,比如二手房屋买卖,新房买卖,房屋出租等,这些不同的领域通常由不同的组来维护,最适合使用webpack module federation做成micro frontend。
不同的组独立开发独立部署自己的网站,同时:
- 将自己的网站的APP组件暴露出来
- 能够动态引入其他网站的APP组件
- Widget
专门独立部署一个APP,其中包含多个不同的widget,或者UI组件专门供给不同的APP
image.png
网友评论