今天查阅Rsbuild的文档,发现有一个菜单“模块联邦”,从来没听说过,便点进去看一看究竟,官方说法如下:
Module Federation 是一种 JavaScript 应用分治的架构模式(类似于服务端的微服务),它允许你在多个 JavaScript 应用程序(或微前端)之间共享代码和资源。
Rspack 团队与 Module Federation 的开发团队密切合作,并为 Module Federation 提供一流的支持。
也就是说这个是可以跨项目共享代码和资源的,听起来好像微前端架构?但是模块联邦与之前的微前端方案略有不同,区别在我看来主要是是这个:
模块联邦 并不会借助类似iframe或者shadow dom的方案来实现代码之间的共享,而是采用了script 插入远程 js chunk 入口, 然后通过创建新的 ExternalModule 模块去加入核心的动态加载模块的 runtime 代码
那么今天借着这个机会正好学一下,模块联邦在rsbuild中如何使用,以及记录一下坑
首先通过rsbuild脚手架创建两个项目:
pnpm create rsbuild@latest
然后我这里选择的是vue3框架,根据官网配置模块联邦模块联邦 - Rsbuild,然后坑点来了,
基座应用跟远程应用的入口必须采用动态import格式,否则会报错:
解决这个问题,首先在src下创建一个bootstrap.js文件,然后将index.js中的内容粘到bootstrap.js中,最后在index.js中
import("./bootstrap")
基座应用的rsbuild.config.js如下:
import { defineConfig } from '@rsbuild/core'; import { pluginVue } from '@rsbuild/plugin-vue'; export default defineConfig({ plugins: [pluginVue()], moduleFederation:{ options:{ name:"root", remotes:{ 'child':'child@http://localhost:3001/entry.js' }, shared:{ vue:{ singleton:true, } } } } });
子应用的rsbuild.config.js如下:
import { defineConfig } from "@rsbuild/core"; import { pluginVue } from "@rsbuild/plugin-vue"; export default defineConfig({ plugins: [pluginVue()], server: { port: 3001, }, moduleFederation:{ options:{ name:'child', exposes:{ './MButton.vue':'./src/MButton.vue' }, filename:'entry.js', shared:{ vue:{ singleton:true, } } } } });
需要注意的一点是,子应用的exposes中"./MButton",必须要用./相对路径开头,如果直接写MButton.vue,则会报错:
最后在基座应用中,这样使用:
<template> <div class="content"> <h1>主应用</h1> <p>Start building amazing things with Rsbuild.</p> <MButtonFromChild text="主应用使用" @clicked="onClick" /> </div> </template> <script setup lang="ts"> import { defineAsyncComponent } from 'vue'; const MButtonFromChild = defineAsyncComponent(()=>import('child/MButton.vue')) function onClick() { console.log('主应用点击') } </script> <style scoped> .content { display: flex; min-height: 100vh; line-height: 1.1; text-align: center; flex-direction: column; justify-content: center; } .content h1 { font-size: 3.6rem; font-weight: 700; } .content p { font-size: 1.2rem; font-weight: 400; opacity: 0.5; } </style>
最后来看看效果:
DONE!