Rspack、Vite争先支持的模块联盟(MF)究竟是啥?

文摘   2024-10-25 09:19   中国香港  


前言

image.png


作者:前端下饭菜

原文:https://juejin.cn/post/7427173759713296393


什么是模块联盟(Module Federation)?让 JavaScript 应用间共享代码更加简单,团队协作更加高效。

类似于服务端的微服务,Module Federation是一种支持前端应用分治的架构模式,它允许你在多个应用或微前端应用之间共享功能级代码,这种方案的好处是:

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

什么是Module Federation 2.0?
除了老版本已支持的模块导出、模块加载、依赖共享之外,额外支持了:

  • 📝 Manifest:定义Module Federation元数据信息;
  • 🚀 动态类型提示:使用远端模块时,和引入npm包一样的体验,支持类型提示,并且还支持热更新;
  • 🎨 Module Federation 运行时:支持通过运行时API注册共享依赖、动态注册和加载远程模块;
  • 🧩 运行时插件系统:提供了一套轻量的运行时插件系统,提供各种生命周期钩子,并可修改MF配置;
  • 🛠️ Chrome Devtool:开发调试利器;

目前有哪些构建框架支持Module Federation?

  • rspack: 需安装插件@module-federation/enhanced;
  • webpack: 需安装插件@module-federation/enhanced;
  • rsbuild: 需安装插件@module-federation/rsbuild-plugin;
  • Vite: 需安装插件@module-federation/vite, 不支持dtsdev配置选项;

案例

以实现一个共享的计数器为例,在远端remote项目中实现一个计数器组件,其他host项目可通过MF方式引入并使用。

image.png

基础框架准备

在host、remote目录下分别按顺序执行以下步骤:

  1. 在项目新建目录:
  • packages
    • host
    • remote
  1. host、remote都使用npm create rsbuild@latest创建以Rsbuild、Vue3为基础框架的项目,框架选择使用VueTypeScript

通过以上步骤我们建立了host、remote两个可运行的APP,接下来进入正题,开始搭建支持Module Federation的应用。

安装MF环境

remotehost项目安装Module Federation的rsbuild插件:

npm add @module-federation/enhanced
npm add @module-federation/rsbuild-plugin --save-dev

使用Vue的tsx实现组件,因此需要对rsbuild安装支持jsx的相应插件:

npm add @rsbuild/plugin-vue-jsx @rsbuild/plugin-babel -D

rsbuild.config.ts中添加配置:

plugins: [
pluginBabel({
include: /.(?:jsx|tsx)$/,
}),
pluginVue(),
pluginVueJsx(),
],

remote实现共享组件

components/counter/index.tsx添加计时器组件代码,和写常规的Vue组件无任何区别。

import { defineComponent } from "vue";
import './index.css';

export default defineComponent({
  props: {
    count: {
      typeNumber,
      required: true,
    },
  },
  emits: ['increase'],
  setup(props: { count: number }, { emit }) {
    console.log(props);
    return () => ( <button
      class="counter-button"
      onClick={ () => emit('increase', props.count) }>
      Remote counter: { props.count }
    </button>)
  },
});

组件提供了count属性以及事件increase接下来需要在rs.build.ts中通过module federation插件将counter组件以remote方式提供给其他项目。在plugins下添加pluginModuleFederation插件配置

pluginModuleFederation({
  name: 'remote',
  exposes: {
    './counter''./src/components/counter/index.tsx',
  },
}),

以dev方式启动remote项目,然后访问http://localhost:3001/mf-manifest.json,查看返回结果。

image.png

module federation 2.0其中的一个特性就是引入minifest元数据信息,这里返回的正是使用pluginModuleFederation定义的remote信息。先着重看exposes列表,每一项即为我们在配置中定义的导出模块,例如 ./counter对应的就是返回信息中idremote:counter一项 ,其中assets下包含cssjs属性。

  • css: 还记得在components/counter/index.tsx中的代码有通过import 'index.css'引入css文件吗?而static/css/async/__federation_expose_counter.css正是index.css bundle后的css文件。

  • js: manifest.json包含两个js文件,一个是组件代码,另一个是Federation运行时代码;

    • static/js/async/__federation_expose_counter.js: counter组件bundle后的js文件;
    • static/js/vendors-node_modules_rspack_core_dist_cssExtractHmr_js-node_modules_vue_dist_vue_runtime_esm--6bd317.js:还记得Module Federation V2.0提出的 Module Federation 运行时 特性吗?正是该文件所包含的运行时代码;

host使用共享组件

host项目的rsbuild.config.ts中,为plugins部分添加pluginModuleFederation插件配置:

 pluginModuleFederation({
  name: 'host',
  remotes: {
    remote: 'remote@http://localhost:3001/mf-manifest.json'
  }
})

相比于v1.0,v2.0配置非常简单,仅需指定远端的manifest.json地址即可,而manifest.json文件就包含了远端模块或组件的元信息。

App.vue文件中引入remote提供的counter组件并实现交互:

// script
const Counter = defineAsyncComponent(() => import('remote/counter'))

const onInrement = (val: number) => {
count.value = val + 1;
}
// template
<Counter :count="count" @increase="onInrement"></Counter>

代码中使用defineAsyncComponent异步加载远端组件,这样的好处是安需动态加载,能减少应用包的大小。

虽然现在可以使用远端的remote/counter组件,但使用过程没有智能提示也是让人头痛。这不Module Federation 2.0就为我们带来了类型提示特性。

远端默认开启类型提示,所以仅需要在Host侧的tsconfig.json添加配置,将类型提示引入到项目。

{
  "compilerOptions": {
    ...
    "paths": {
      "*": ["./@mf-types/*"]
    }
  },
}

HostModule Federation插件会将远端的@mf-types下载下来并放到根目录下的@mf-types文件夹中。目录结构如下所示:

image.png

目录中包含了3种类型定义:

  • 运行时API:V2.0支持动态加载远端模块,根目录下的index.d.ts文件就包含了动态加载相关的API定义,具体有@module-federation/runtime@module-federation/enhanced/runtime@module-federation/runtime-tools模块的类型定义;
  • 远端组件、模块类型:如上文中定义的Counter组件,其类型定义包含在@mf-types/remote/compiled-types/src/components/counter/index.d.ts目录下;
  • 依赖项类型:由于组件中引用了Vue,因此在@mf-types/remote/node_modules下能看到Vue的类型定义文件;

添加了类型提示配置后,我们写组件时就能方便的查看到远端组件定义的属性。

image.png

MF使用问题

css全局污染

和微前端框架类似,Module Federation也有CSS全局污染的问题。

remote定义的counter组件有使用样式counter-button,假如host也同样定义了同名的class counter-button,并设置background-color: #F00。运行结果是远端的样式将本地的样式覆盖,也就是说本地的样式被远端样式给污染了,这不是我们想要的结果。

image.png

Module Federation在后续计划有提到sandbox,如果能为共享模块提供沙箱模式,那css问题也会迎刃而解。

依赖复用

跨项目消费模块往往会碰到重复依赖加载依赖单例限制等问题,这些问题可以通过设置 shared 来解决。

  • 重复依赖加载

    例如remotehost都在使用loadash,并且lodash包体积比较大。那么可以将其配置到shared选项,在两个项目的rsbuild.config.ts同时添加:

     shared: {
        lodash: {
          singleton: true,
          eager: true,
        }
      }

    重新请求mf-manifest.json文件,返回内容有增加shared相关信息。如果host已经加载过lodash模块,则不会再从远端请求lodash文件static/js/async/vendors-node_modules_lodash_lodash_js.js

    image.png
  • 依赖单例限制

由于react项目只允许单例运行,不能多次加载,这时也需要将其配置到shared中。这种场景,需要在hostremotersbuild.config.ts同时配置:

shared: ['react'react-dom']

State状态

假如在remote项目中的组件有使用pinia创建store,那么在host使用时会报如下错误:

image.png

原因是远端的pinia实体是在main.ts通过app.use(createPinia())创建,而直接使用组件时不会走main.ts流程。

要解决这个问题,需要动态的获取app实体,并在useStore()之前,保证createPinia已经执行。可通过以下方式动态获取app实体。

import { getCurrentInstance } from 'vue';
const { appContext } = getCurrentInstance()!;
appContext.app.use(createPinia());

Vite不支持类型提示

image.png

@module-federation/vite目前还不支持dts配置,也就是不支持Module Federation v2.0类型提示特性。如果想使用类型提示,只能绕道至Webpack或者Rsbuild

manifest.json越来越大

image.png

上图为远端打包后的结果,生产环境也包含mf-manifest.json。远端的模块信息通过mf-manitest.json提供给应用端,但随着远端提供的模块越来越多并且依赖项也越来越多,manifest.json会指数级增长,那会不会演变成Modufle Federation的卡点问题?

总结

Module Federation的主要应用场景是微前端,可以满足多个微前端之间的代码共享。相比于当前各种微前端框架,Module Federation的优势非常明显:

  1. 不需要引入一个庞大的微前端架构,也不需要对现有代码做过多改造,微前端相关的逻辑交给构建层(webpack、rsbuild、vite等)统一处理,因此不管是新、老项目,上手成本都比较低;

  2. 开发效率高,像多仓库或者发布npm包的方式,一方面开发时代码体量比较大,另一方面发布过程也比耗时间。而Module Federation架构,通过网络来共享代码模块,因此模块提供方提供一个url,应用方就可以开始使用,并且体验上和使用npm包完全一致。

🕰️ Module Federation 未来

Module Federation 希望能成为构建大型 Web 应用的一个架构方式,类似后端的微服务。后续计划包括的内容:

  • 提供完善的 Devtool 工具
  • 提供更多的上层框架能力 Router、Sandbox、SSR
  • 提供大型 Web 应用基于 Module Federation 的最佳实践

demo代码:github.com/cnmapos/mod…[1]

参考

  1. rsbuild-plugin-vue-jsx[2]
  2. 何时使用shared[3]
  3. Module Federation官网[4]


最后:
React Hook 深入浅出
CSS技巧与案例详解
vue2与vue3技巧合集
VueUse源码解读

JavaScript 每日一练
每天一道JavaScript 实战题,让大家平时多多积累实用的知识,提高开发效率,才有更多的时间摸鱼。
 最新文章