阅读 10309

Webpack 5模块联邦引发微前端的革命?

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版本,但我们已经可以预见在不久的将来,模块联邦将成为微前端架构中标准解决方案之一。

『奶爸码农』从事互联网研发工作10+年,经历IBM、SAP、陆金所、携程等国内外IT公司,目前在美团负责餐饮相关大前端技术团队,定期分享关于大前端技术、投资理财、个人成长的思考与总结。

文章分类
前端
文章标签