Webpack 5的模块联邦提供加载部分编译好的代码能力,这个似乎会成为微前端架构的标准实现。
引言
在当前的微前端实现中,我们需要通过一系列的技巧去实现。正如上图所示,微前端的公共依赖加载目前并没有非常好的实现方案。然后,Webpack 5中的模块联邦将会改变这一现状。
模块联邦可以去依赖一个远程模块,这个依赖会在运行时生效,并不影响编译时。因此,这个远程依赖的模块就可以是一个微前端独立模块。同时,每个独立模块都可以申明公共的依赖库,这样也可以避免独立模块间的依赖包的冗余和冲突。
这篇文章将一步步告诉你如何通过Webpack 5的模块联邦特性来搭建一个微前端应用。这里可以找到源代码。
示例
这个例子首先包含一个空壳涵盖两个模块(Home、Flights),这个空壳应用可以按需的加载各个微前端模块。
下面是微前端模块的部分-Flights,这部分其实也可以独立运行。
通过这样的架构可以实现各个模块的独立开发发布,同时有能够按需的进行集成整合。
模块联邦
在过去要实现微前端的架构是非常困难的,尤其是像Webpack这类工具是需要在编译阶段保证全部代码的完整性。懒加载是有可能的,但需要在编译阶段排除掉才行。
在微前端架构下,每个独立模块都需要独立编译打包,并且需要人工引入。大体的代码如下:
import('http://other-microfrontend');复制代码
这样的实现需要依赖external方式的JavaScript人工引入,在Webpack 5中这一实现方式将会得到改变。
模块联邦背后的原理非常简单:宿主系统通过配置名称来引用远程模块,同时在编译阶段宿主系统是不需要了解远程模块的,仅仅在运行时通过加载远程模块的入口文件来实现。
宿主系统实现
宿主系统用于引入远程模块。这个例子会加载一个远程模块mfe1/component,mfe1是配置的远程模块名,component是其中提供的一个文件。
const rxjs = await import('rxjs');const container = document.getElementById('container');
const flightsLink = document.getElementById('flights');
rxjs.fromEvent(flightsLink, 'click').subscribe(async _ => {
const module = await import('mfe1/component');
const elm = document.createElement(module.elementName);
[…]
container.appendChild(elm);
});
复制代码
在Webpack配置中,采用ModuleFederationPlugin可以来申明要使用的远程模块信息。
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");[…]
plugins: [
new ModuleFederationPlugin({
name: "shell",
library: { type: "var", name: "shell" },
remotes: {
mfe1: "mfe1"
},
shared: ["rxjs"]
})
]
复制代码
这样远程模块mfe1就声明完成了,Webpack在编译阶段就会把mfe1相关的引用都忽略,避免将其进行打包。
在shared中可以定义依赖的公共库,这个例子就是rxjs。这样就可以保证整个应用仅仅会加载rxjs库一次,否则的话公共库会被打包进入宿主应用,同时也会在各个子模块中重复出现。
当然,shared的公共库需要保证是一样的版本。同时,宿主系统需要通过dynamic import的方式进行加载:
import * as rxjs from 'rxjs';复制代码
远程模块的实现
远程模块也是一个独立系统,这里采用web component方式实现:
class Microfrontend1 extends HTMLElement {constructor() {
super();
this.attachShadow({ mode: 'open' });
}
async connectedCallback() {
this.shadowRoot.innerHTML = `[…]`;
}
}
const elementName = 'microfrontend-one';
customElements.define(elementName, Microfrontend1);
export { elementName };
复制代码
当然,你可以采用任何一种前端框架来实现,通用的框架库可以用shared的方式在宿主和远程模块之间实现公用。
在远程模块的Webpack配置中,也需要使用ModuleFederationPlugin,将模块暴露出去。
output: {publicPath: "http://localhost:3000/",
[…]
},
[…]
plugins: [
new ModuleFederationPlugin({
name: "mfe1",
library: { type: "var", name: "mfe1" },
filename: "remoteEntry.js",
exposes: {
component: "./mfe1/component"
},
shared: ["rxjs"]
})
]
复制代码
name定义了远程模块的配置名称。通过远程模块名称和暴露出来的组件名,宿主就可以远程进行依赖引用:
import('mfe1/component')复制代码
最后,宿主还需要知道远程模块的url来真正引入。
宿主连接远程模块
宿主系统需要加载远程的入口文件,这个文件是远程模块通过ModuleFederationPlugin打包产生的。
入口文件名定义在filename的配置中,这个例子定义为"remoteEntry.js"。微前端模块的url定义在publicPath属性上。
在宿主系统中引入远程模块入口文件:
<script src="http://localhost:3000/remoteEntry.js"></script>复制代码
在这个例子中,我们提供了两个系统
- 宿主系统:地址是localhost:5000,会加载远程模块入口文件
- 远程模块:地址是localhost:3000,提供了远程模块组件
结论
Webpack 5的模块联邦机制给微前端势必会带来革命性的变化。远程的模块可以独立编译,然后在运行时进行加载,同时还能够定义公共库来避免重复加载。
现在Webpack 5依旧还是beta版本,但我们已经可以预见在不久的将来,模块联邦将成为微前端架构中标准解决方案之一。