微前端架构学习笔记
前言
之前遇到过一个需求,有两个项目分别由两个不同的部门负责,不同技术栈,不同代码仓库:
- A 项目是官网,负责展示产品亮点等信息,有多个入口可以进入 B 项目中的不同页面。
- B 项目是业务线,负责处理具体的功能,可以跳转到 A 项目中登录/注册等。
现在想在这样跨域的两个项目之间,仅通过两个客户端,如何传递客户从 A 项目中上传的文件,使得 B 项目可以接收文件再处理后续的业务逻辑。
因为并未引入微前端方案,最终通过 传递文件的 DataURL + IndexedDB 的方式来实现。
跑题一点,每个文件都可以生成 DataURL 和 BlobURL,在这篇文章中不展开讲了,之后会更新一篇文章记录两者的使用。
最终决定使用 DataURL 的原因是,涉及到链接跳转的动作,会导致 BlobURL 丢失。
如果引入了微前端的方案,那么这个问题就很好解决,下面将详细记录微前端相关的笔记。
1.什么是微前端?
微前端(Micro Frontends)是一种将前端应用程序拆分成多个较小、独立、可管理的部分的架构方法。
图片来源于micro-app官网
微前端借鉴微服务架构(Micro Services),将一个前端应用程序拆分成多个子应用,子应用可以分别独立开发、测试、部署和维护。
微前端平台
single-spa
将多个独立的单页面应用组合到一个父应用中的微前端框架
特点
- 无框架限制
对于 React,可以使用 single-spa-react
对于 Vue,可以使用 single-spa-vue
对于 Angular,可以使用 single-spa-angular
- 可以与其他微前端框架(如 qiankun)结合使用
- 主应用与子应用无耦合,每个子应用都可以独立加载和卸载
缺点
- 配置较为繁琐,需要开发者手动管理每个子应用的生命周期和加载
要求子应用暴露三个生命周期方法(
bootstrap
、mount
、unmount
),并且需要对子应用的入口进行适当的修改。适用场景
- 需要多种前端技术栈共存的项目
- 可以与现有的应用逐步迁移到微前端架构中
以 Vue3.0 为例,借用 single-spa-vue
npm install vue@next single-spa-vue
目录结构
my-micro-frontend/ ├── dist/ # 构建后的输出 ├── public/ │ └── index.html # 主页面,包含单页面应用入口 ├── src/ │ ├── main.ts # 主应用,加载子应用 │ ├── vue-app.ts # 子应用生命周期方法 │ ├── vue-app.vue # Vue 3 组件 ├── tsconfig.json # TypeScript 配置 ├── webpack.config.js (or vite.config.ts) └── package.json
配置子应用
// vue-app.vue <template> <div id="app"> <h1>Vue 3 Micro Frontend</h1> <p>This is a micro frontend app rendered using Vue 3 and Single-spa!</p> </div> </template> <script setup lang="ts"> /** * 这个组件通过 single-spa 在主应用中渲染 */ </script>
在
vue-app.ts
中,使用 singleSpaVue 暴露bootstrap
、mount
和unmount
方法。// vue-app.ts import { createApp, App as VueApp } from "vue"; import { singleSpaVue } from "single-spa-vue"; import App from "./App.vue"; // 定义 Single-spa 生命周期方法的类型 const vueLifecycles = singleSpaVue({ createApp, appOptions: { render: (h: any) => h(App), // 渲染根组件 }, }); // 暴露 Single-spa 所需的生命周期方法 export const bootstrap: (props: any) => Promise<void> = vueLifecycles.bootstrap; export const mount: (props: any) => Promise<void> = vueLifecycles.mount; export const unmount: (props: any) => Promise<void> = vueLifecycles.unmount;
配置主应用
主应用负责注册子应用并启动
single-spa,
不需要直接渲染 Vue 应用。在主应用中不需要手动调用createApp(App).mount('#app')
,都由single-spa-vue
管理。// main.ts import { registerApplication, start } from "single-spa"; // 注册 Vue 子应用 registerApplication( "vue-app", // 子应用名称 () => import("./vue-app.ts"), // 动态导入子应用生命周期方法 (location) => location.pathname.startsWith("/vue-app") // 激活条件 ); // 启动 single-spa start();
配置 Webpack 或 Vite
// webpack.config.js const path = require("path"); module.exports = { entry: "./src/main.ts", output: { path: path.resolve(__dirname, "dist"), filename: "bundle.js", publicPath: "/", }, resolve: { extensions: [".ts", ".js", ".vue", ".json"], alias: { vue: "vue/dist/vue.esm-bundler.js", }, }, module: { rules: [ { test: /\.ts$/, loader: "ts-loader", exclude: /node_modules/, }, { test: /\.vue$/, loader: "vue-loader", }, ], }, plugins: [ new (require("vue-loader").VueLoaderPlugin)(), ], devServer: { historyApiFallback: true, }, };
// vite.config.ts import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; export default defineConfig({ plugins: [vue()], build: { target: "esnext", outDir: "dist", lib: { entry: "src/vue-app.ts", name: "VueApp", fileName: "vue-app", formats: ["es"], }, rollupOptions: { external: ["vue"], }, }, });
qiankun
基于 single-spa 的微前端实现库,提供了更易用的 API
特点
- 无框架限制
- 支持按需加载子应用,支持懒加载、动态加载
- 提供沙箱机制,可以隔离不同微前端应用的全局状态
- 支持使用插件系统来扩展功能
缺点
- 封装了 single-spa,因此继承了对子应用的生命周期管理
适用场景
- 适合已有单页面应用架构,并计划迁移或拆分成多个微前端应用的场景
MicroApp
基于 WebComponent 的微前端框架,使用原生浏览器支持的 Web Component 标准来封装和管理微前端应用
特点
- 将每个微前端应用封装为 Web Component,子应用能够更好地与其他应用隔离
- Web Component 是原生浏览器技术,MicroApp 不依赖任何前端框架
- 支持自定义生命周期函数,能精细控制子应用的加载、渲染、销毁等过程
缺点
- Web Component 在不同浏览器的兼容性可能会存在差异
- 对开发者来说,需要一定的 Web Component 知识
适用场景
- 适合希望轻量化的微前端架构,或者不希望依赖第三方框架的场景
- 对应用的封装性和隔离性有较高要求的场景
Module federation
Webpack 5 引入的一个新特性,允许将一个应用的模块动态加载到另一个应用中。
特点
- 能够共享模块,比如 React 和 Vue 等常用依赖,避免重复加载
- 可以按需加载子应用,并支持运行时动态决定要加载哪些模块
- 不依赖框架,是直接基于 Webpack 的构建系统
缺点
- 配置复杂,需要对 Webpack 的运行机制有较深入的了解
- 很少提供封装好的 API,开发者需要更多的手动管理
适用场景
- 适合已经使用 Webpack 的项目,特别是对微前端的集成要求较高的场景
2.微前端的架构实现方式
1)基于 URL 路由
通过 URL 路由来加载不同的微前端子应用
假设有一个电商平台,主应用是一个统一的首页展示,而不同的页面(如产品详情页、用户中心、购物车等)分别由不同的子应用负责。
- 使用主应用中的路由管理器来控制子应用的加载和渲染。
- 主应用的路由会根据 URL 地址来决定展示哪个子应用的页面。
// 主应用路由配置示例
const routes = [
{ path: '/products', component: ProductApp },
{ path: '/cart', component: CartApp },
{ path: '/user', component: UserApp }
];
2)基于 Web Components
使用 Web Components 技术来将不同的子应用封装成独立的组件。这样可以确保每个子应用的样式和功能相互隔离,避免冲突。
假设有一个新闻网站,主应用展示统一的导航栏和布局,而每个新闻分类(如国内新闻、国际新闻、科技新闻)由不同的微前端子应用负责。
- 每个子应用作为一个独立的 Web Component 封装,主应用直接通过
<my-product-app></my-product-app>
这样的标签来加载和渲染。 - Web Components 提供了封装功能,避免了样式和脚本冲突。
基础写法
// 子应用:ProductApp 使用 Web Components 封装
class ProductApp extends HTMLElement {
connectedCallback() {
this.innerHTML = `<div>Product List</div>`;
}
}
customElements.define('my-product-app', ProductApp);
// 主应用直接使用 Web Component
<my-product-app></my-product-app>
复杂结构写法
// 定义子应用组件 ProductApp
class ProductApp extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }); // 使用 Shadow DOM,避免样式污染
}
connectedCallback() {
this.render();
}
render() {
const template = document.createElement('template');
template.innerHTML = `
<style>
.product-list {
color: #333;
font-size: 16px;
}
.product-item {
margin: 10px 0;
}
</style>
<div class="product-list">
<div class="product-item">Product 1</div>
<div class="product-item">Product 2</div>
<div class="product-item">Product 3</div>
</div>
`;
// 将模板内容插入 Shadow DOM
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}
// 注册自定义元素
customElements.define('my-product-app', ProductApp);
// 在主应用中
<my-product-app></my-product-app>
通过 Shadow DOM 进行样式隔离,也可以使用外部样式表。Shadow DOM
有两个主要好处:
- 样式封装:子应用的样式不会影响到主应用或其他子应用。
- DOM 隔离:确保子应用的内部结构和样式不会被外部应用干扰。
使用外部样式表
class ProductApp extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
render() {
// 引入外部样式
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css'; // 外部 CSS 文件
// 模板内容
const template = document.createElement('template');
template.innerHTML = `
<div class="product-list">
<div class="product-item">Product 1</div>
<div class="product-item">Product 2</div>
<div class="product-item">Product 3</div>
</div>
`;
// 将样式和模板添加到 Shadow DOM
this.shadowRoot.appendChild(link);
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}
3)基于 JavaScript 动态加载
使用 JavaScript 动态加载工具(如 Webpack)来按需加载不同的子应用。
假设有一个在线教育平台,主应用加载时并不加载所有课程内容,而是按需加载不同课程模块。
- 在主应用中通过
import()
动态加载子应用的 JavaScript 模块。
// 使用 Webpack 动态加载子应用
const loadCourseApp = () => {
import(/* webpackChunkName: "course-app" */ './course-app')
.then(module => {
module.renderCoursePage();
})
.catch(err => {
console.error('Error loading course app:', err);
});
};
4)基于 iFrame
每个子应用运行在一个独立的 iFrame 中,彼此之间无任何依赖。
假设有一个跨域的企业管理平台,不同的部门使用不同的子系统(如人事管理、财务管理等),每个部门的系统是独立的。
- 每个子应用运行在独立的 iFrame 中,iFrame 嵌入到主应用页面中。
- 可以完全隔离不同的子系统,避免了跨域和样式冲突的问题。
<!-- 主应用使用 iFrame 嵌入子应用 -->
<iframe src="https://example.com/finance" width="100%" height="500px"></iframe>
<iframe src="https://example.com/hr" width="100%" height="500px"></iframe>
3.类单页应用
4.参考文章链接
https://tech.meituan.com/tags/%E5%BE%AE%E5%89%8D%E7%AB%AF.html
https://wujie-micro.github.io/doc/guide/
https://juejin.cn/post/7113503219904430111
https://developer.jdcloud.com/article/2898
原文地址:https://blog.csdn.net/XZZXBC/article/details/144210931
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!