基于MF的微前端实践(一): 搭建项目

26 阅读10分钟

引言

大家好啊,我是长风

你是小阿巴,在一家互联网大厂做前端开发实习

下载

这一天,你的mentor给了你一个紧急的任务,让你重构一个老页面。原来的老页面在老仓库中,并且是用React写的,现在要在新的Monorepo仓库中用Vue来重构该页面。要求3天内必须上线。

你评估了一下,发现时间根本不够,mentor和你说,xx组件不用重构,直接引入线上的就行,只重构其他部分,工作量不大的。

但是要怎么才能保持某个页面中的某个组件不动,只重构其他部分呢?

  • 直接在代码仓库中CV?不行,一方面,新老仓库技术栈不同,React的代码没法直接CVVue项目中;另一方面,老仓库中的依赖关系复杂,组件嵌套组件,直接CV代码会把老仓库的屎山拉到新仓库里
  • iframe直接嵌套?也不行,iframe虽然能够较好地实现应用隔离,但是会有以下问题:
    • 不同iframe之间通信只能用window.postMessage这种原始API,没有类型,需要自己维护协议,调试也非常困难。
    • 路由无法统一,刷新会丢失状态,深链接困难。
    • 性能差,每个ifram都要加载自己依赖的第三方库,哪怕不同的iframe间,有的库是共用的。

难道就要就此认输吗?难道就毫无办法了吗?

images

突然,你灵光乍现,火光冲天,醍醐灌顶,一飞冲天(好了,编不下去了。。。😋)

main

对,就是微前端,这个你之前看到过的技术方案,好像就是专门用来解决这种复杂大型应用的问题的。那我们就一起来看一下,如何通过Module Federation(MF)来实现微前端。

本文属于专栏基于 MF 的微前端实践系列文章,长风会在该专栏详细记录基于 MF来实践微前端架构的过程,欢迎各位读者订阅,点赞,加关注,👍🏻👍🏻👍🏻

什么是Module Federation

Module Federation是一种JavaScript应用分治的架构模式(类似于服务端的微服务),它允许你在多个JavaScript应用程序(或微前端)之间共享代码和资源。这可以帮助你:

  • 减少代码重复
  • 提高代码可维护性
  • 降低应用程序整体大小
  • 提高应用程序的性能

这使得可以创建微前端风格的应用程序,多个系统可以共享代码,并在不需要重新构建整个应用程序的情况下进行动态更新。

什么是Module Federation 2.0

Webpack 5内置的Module Federation相比,Module Federation 2.0除了支持原有的模块导出,模块加载和依赖共享等核心功能,还新增了:

  • 动态类型提示
  • Manifest
  • Federation Runtime
  • Runtime Plugin System
  • Chrome Devtool

等特性。这些增强功能使得Module Federation 2.0更加适合作为构建和管理大型Web应用的微前端架构标准。

Demo快速上手

设置环境

首先使用fnmnvm安装20及以上的LTSNode

# fnm 安装node20
fnm install 20
# 或者nvm install 20

在使用过程总,我们要遵循以下步骤:

  • 识别共享模块:确定要在应用程序之间共享的模块。
  • 创建共享包/仓库:将这些模块添加到共享包或代码仓库中。
  • 确保访问权限:确保每个应用程序都可以访问共享包或代码仓库。
  • 配置构建插件:配置每个应用程序的WebpackRspack配置文件以使用Module Federation
  • 使用共享模块:根据需要在应用程序中使用共享模块。

新项目创建

你可以使用create-module-federation脚手架来创建一个Module Federation项目,调用以下命令:

pnpm create module-federation@latest

然后再CLI中进行交互式地选择,这里你可以先创建一个消费者Consumer

image-20260312113332627

根据Next steps的提示进行安装后,进入项目。根目录下会有一个module-federation.config.ts的配置文件,如下图:

image-20260312115418376

这里已经提前给你配置了一个生产者provider,是一个https://unpkg.com提供的远程组件。

注意这里在remote对象中,配置的远程组件的字段名叫provider,所以在下面的App.tsx中使用的时候,要从provider中导入:

image-20260312115909164

这个名称是给远程模块命名的本地别名,可以任意自定义,不一定非得是provider,但是在module-federation.config.ts中的remote对象中配置的字段名要和在App.tsx中导入的包名一致。

这里提到了两个概念生产者消费者

顾名思义,

所谓生产者,指的是通过Module Federation构建插件设置了exposes暴露某些模块给其他JavaScript应用消费的应用,即Provider,注意,生产者同时也可以作为一个消费者去消费其他生产者提供的模块。

所谓消费者,指的是通过Module Federation构建插件设置了remotes消费其他生产者的模块的应用,即Comsumer,同理,消费者同时也可以作为一个生产者。

module-federation.config.ts中的相关配置,要通过plugin导入到项目对应的bundler(如webpack, rsbuild, vite等)中,才能生效。

示例项目,采用的是rsbuild进行构建,所以,rsbuild.config.ts文件如下:

// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginModuleFederation } from '@module-federation/rsbuild-plugin';
import moduleFederationConfig from './module-federation.config';

export default defineConfig({
  plugins: [pluginReact(), pluginModuleFederation(moduleFederationConfig)],
});

调试

开启调试模式

为了便于排查问题,Module Federation提供了调试模型,你可以在执行构建时添加FEDERATION_DEBUG=true环境变量或者在浏览器执行localStorege.setItem('FEDERATION_DEBUG', 'true')来开启Module Federation的调试模式。

在构建时添加环境变量可以使用最简单的命令行参数方式:

# 调试开发模式
FEDERATION_DEBUG=true pnpm dev

# 调试生成模式
FEDERATION_DEBUG=true pnpm build

也可以在package.json中,添加一个自定义的脚本:

image-20260312141545919

这样后续只需要运行pnpm debug即可开启调试模式。

运行时添加环境变量,则直接在控制台执行以下代码即可:

localStorage.setItem('FEDERATION_DEBUG', 'true')

可以进行相关测试,未开启调试模式时,启动应用,terminal输出如下:

image-20260312142328403

开启调试模式后,控制台输出如下:

image.png

这里给出了很多以[Module Federation]开头的调试信息,包括开始拉取远程类型,构建等步骤的耗时以及详细的调用栈信息,方便你排查问题。

Chrome Devtool

微前端架构不同于传统的单体应用的开发模式,其分开开发,部署,调试的特征使其需需要一套新的调试工具来满足新的使用场景,比如:

  • 在开发Module Federation时怎么验证模块在实际项目中的效果
  • Module Federation的依赖是否和宿主环境进行了复用
  • 当前页面加载了哪些Module Federation
  • Module Federation的依赖关系
  • Module Federation间的数据流转是怎么样的

Chrome Devtool提供了以下能力:

  • 支持Module Federation代理功能,将线上页面中的Module Federation代理到用户本地的Module Federation
  • 切换线上页面Module Federation版本,来进行快速的功能验证
  • 支持查看模块依赖信息
  • 支持筛选特定依赖模块的信息

关于 Chrome Devtool 的限制:

必须使用mf-manifest.json才可以使用Chrome devtool提供的可视化和代理能力

使用场景

DevTools 提供了多个功能面板,适用于开发环境以及生产环境的不同调试需求:

  • Proxy(代理):用于将线上或测试环境的模块代理的本地开发环境
    • 支持本地服务端口号,例如 key: appA -> value: http://localhost:3000/mf-manifest.json,页面将直接加载3000端口的Module Federation内容。
    • 支持使用 mf-manifest.json文件地址形式,例如 key: appA -> value: https://xxx/static/mf-manifest.json,页面将加载指定地址的 Module Federation内容。
  • Module Info(模块信息):用于查看当前页面加载的所有 Federation 模块的详细信息系,包括版本,入口地址等。
  • Dependency Graph(依赖关系图):以可视化的方式展示模块之间的依赖引用关系,帮助理清复杂的微前端架构。
  • Shared(共享依赖):深入分析 Shared Dependencies 的使用情况。
    • 查看已加载和未加载的共享依赖
    • 分析共享依赖的版本复用情况(Loaded / Reused)
    • 检查单例(Singleton)、严格版本(Strict Version)等配置的生效状态。

如何安装

打开 Module Federation 插件详情页, 点击 添加到 Chrome 按钮

img

如何使用

插件提供了 Devtools 面板

  • F12 打开开发者工具,选择点击 Module Federation tab,进入代理页面,便可对依赖的模块进行代理调试

image.png

整体交互

如下图所示,代理页面( Proxy 选项卡上提供了 add new proxy moduleproducer selectorversion or local port or custom entry等选项操作。

  • 通过选择 producer selector 选择出目标页面需要代理的一个模块;
  • 通过 version or local prot 选择或者输入指定的地址(包括端口号和 mf-manifest.json 结尾的地址),进行代理操作;
  • 如果需要同时代理多个模块,点击 add nnew proxy module 区域,增加对应的代理模块。
  • 支持多 Tab 隔离:在多个标签页中同时打开使用了 Module Federation 的页面时,每个 Tab 的代理规则和模块信息都是相互独立的。你在 Tab A 中设置的代理规则不会影响 Tab B,反之亦然。这允许你同时调试多个环境或应用状态。

img转存失败,建议直接上传图片文件

如何将本地开发的模块代理到线上

  • 首先需要在本地启动生产者

image.png

  • 然后将启动成功的 manifest 地址,输入到代理页面的版本选择输入框内
  • 之后调整本地的生产者代码,消费者页面将会自动 Reload

image.png

总结

本文从一个日常开发中的问题引入,如何在重构过程中复用线上应用的部分组件,介绍了Module Federation的相关概念,同时给出了一个快速上手的 Demo 搭建步骤,并总结了调试的相关方法,特别是 Chrome Devtool 的使用方法。希望通过本文能够使读者对微前端MF有一个宏观的认识和了解,并能够通过 Demo 验证相关概念。

在本专栏下一篇文章中,我们会在本地再创建一个消费者的 Demo,并进行代理调试,感兴趣的读者,欢迎订阅👏🏻👏🏻👏🏻。

好了,这篇文章就到这里啦,如果对您有所帮助,欢迎点赞,收藏,分享👍👍👍。您的认可是我更新的最大动力。由于笔者水平有限,难免有疏漏不足之处,欢迎各位大佬评论区指正。

本文属于专栏基于 MF 的微前端实践系列文章,长风会在该专栏详细记录基于 MF来实践微前端架构的过程,欢迎各位读者订阅,点赞,加关注,👍🏻👍🏻👍🏻

往期推荐✨✨✨

我是长风,我们下期见!