微前端
js 隔离
css 隔离
元素隔离
生命周期
预加载
数据通信
应用跳转
多层嵌套
说明
- 使用的是 Mermaid 的 flowchart 语法,Markdown 渲染器如 Typora、VitePress、一些 Git 平台都支持。
- 保留了:
- 基座应用
main-vue3
- 各子应用:
child-nuxt2-home
、child-vue2-job
、child-vue3-enterprise
、child-react18-about
- 框架的核心逻辑,如 JS 隔离、样式隔离、生命周期、应用通信、虚拟路由等
- 右侧的初始化流程
◆ 多种前沿技术栈
◆ 多项目实操,一人分饰几角
◆ 微前端应用场景、落地方案
◆ 巨石应用开发流程
◆ 架构思维、架构能力
◆ 打造企业级微前端应用
两个问题
- 什么是微前端
- 背景 屎山代码(开发、运行、打包,但又不能将其废掉,还要把需求增量到项目中)【巨石应用】
- 基本概念 最早出现在 2016 年(借鉴了微服务的架构)
- 核心思想 容器应用(主应用)子应用(微应用)
- 使用场景
- 增量升级 减少冲突 提高效率
- 灵活性,(技术栈无关)可以使用不同的构建工具
- 稳定性 各微应用各司其职,聚合平台
- 独立性 独立开发、测试、部署
- iframe single-spa qiankun micro-app
<iframe src="https://www.example.com" sandbox></iframe>
简单易用,天然沙箱(完全隔离),隔离太完美,刷新即丢失(白屏时间长加载速度慢)
-
single-spa 是最早的微前端框架(单页面微前端模式),在基座上维护一个基座路由表,微前端框架鼻祖,改造成本大,沙箱不完美,应用通信能力差(单页面),不支持 esmodule,所以也不支持 vite
```js
// 1. 注册
// 2. 加载
import { registerApplication } from 'single-spa';registerApplication({
name: 'app',
app: () => {
loadScripts('./chunk-a.js');
loadScripts('./chunk-b.js');
return loadScripts('./entry.js');
},
});singleSpa.start();
``` - Qiankun 基本 single-spa 也有提升
- 它是通过 html entry
- 更完备的沙箱方案
- 适配成本高,同样对基座和子应用做一些改动,不支持 vite
-
Micro-app 京东技术团队
- 低侵入性(适配成本低),开箱即用
- 文档易读
- 更好的兼容性(支持 webpack,vite)
- 为什么要学习
现代微前端架构理论
之前通过 nginx 配置不同的入口来实现
- 团队自治 跨多团队合作开发困难
- 核心思想 开发、部署成本
- 场景落地 系统的渐进性、动态性
graph LR
主分支[主分支] -->|git仓库| 分支1
主分支 --> 分支2
主分支 --> 分支3
主分支 --> 分支4
分支1 --> 团队1
团队1 -->|commit| 赵 --> 钱 --> 孙
分支2 --> 团队2
团队2 -->|commit| 李 --> 周 --> 吴
分支3 --> 团队3
团队3 -->|commit| 郑 --> 王 --> 冯
分支4 --> 团队4
团队4 -->|commit| 陈 --> 褚 --> 卫
每个仓库都会进行独立的部署
graph LR
主分支[主分支] -->|git仓库| 团队1仓库
主分支 --> 团队2仓库
主分支 --> 团队3仓库
主分支 --> 团队4仓库
团队1仓库 -->|commit| 赵 --> 钱 --> 孙
团队2仓库 -->|commit| 李 --> 周 --> 吴
团队3仓库 -->|commit| 郑 --> 王 --> 冯
团队4仓库 -->|commit| 陈 --> 褚 --> 卫
独立开发(每个应用都是一个独立的应用)
技术选型
微前端框架技术框架无关,我们可以选择任何前端的技术框架
微前端应用 = 微前端框架 + JS 框架 + UI 框架 + 构建工具
- 微前端框架:Micro-app
- JS 框架:Vue3、Vue2、Nuxt2、React18
- UI 框架:Element-ui、Element-plus
- 构建工具:Webpack5、Vite4、Vue-cli5
整体架构思路为 CustomElement + HTMLEntry:
- micro-app 标签:上可以设置各种配置,比如开启 iframe 沙箱、开启 SSR 模式、开启 keep-alive 模式、关闭沙箱、数据通信。
- HTMLEntry:就是以 HTML 文件作为入口地址进行渲染。
主要功能:
生命周期、环境变量、虚拟路由、js 沙箱、样式隔离、元素隔离、数据通信等等
生命周期
- created:
<micro-app>
标签初始化后,加载资源前触发。 - beforemount:加载资源完成后,开始渲染之前触发。
- mounted:子应用渲染结束后触发。
- unmount:子应用卸载时触发。
环境变量
__MICRO_APP_PUBLIC_PATH__
__MICRO_APP_BASE_ROUTE__
虚拟路由系统
通过虚拟路由系统,我们可以方便地进行导航守卫、跨应用的跳转,提升开发效率,并且子应用运行在这套虚拟路由系统中,和主应用的路由进行隔离,避免相互影响,如:
- 主应用控制子应用跳转
- 子应用控制主应用跳转
- 子应用控制其它子应用跳转
js 沙箱
确保子应用之间 全局变量/事件不冲突
样式隔离
确保子应用之间样式互相不干扰
.test {
color: red;
}
/* 转换为 */
micro-app[name='xxx'] .test {
color: red;
}
元素隔离
元素隔离的概念来自 ShadowDom,即 ShadowDom 中的元素可以和外部的元素重复但不会冲突。micro-app
模拟实现了类似 ShadowDom 的功能:
- 元素不会逃离
<micro-app>
元素边界。 - 子应用只能对自身的元素进行增、删、改、查的操作。
通信
主子通信
子应用全局通信
其它能力
预加载,缓存等
系统架构
graph TD
A[基座应用 main-vue3] -->|登录| B[micro-app 框架逻辑]
B --> C[JS 隔离]
B --> D[样式隔离]
B --> E[元素隔离]
B --> F[生命周期]
B --> G[应用通信]
B --> H[虚拟路由系统]
B --> I[预加载]
B --> J[资源地址补全]
B --> K[...]
B --> L[子应用 child-nuxt2-home]
B --> M[子应用 child-vue2-job]
B --> N[子应用 child-vue3-enterprise]
B --> O[子应用 child-react18-about]
P[初始化 micro-app] --> Q[嵌入子应用]
Q --> R[通用组件]
R --> S[统一鉴权]
4 个子应用和 1 个主应用
nvm 对 nodejs 版本管理
什么是 nvm
nvm 是 node 的包管理工具,它可以帮助我们在不同的项目环境中使用不同的 node 版本,所以在启用不同项目时,可能遇到报错。
例如:如果我们本次教程的所使用到的 nuxt3 和 vite 搭建不同的项目,就是依赖于不同的 node 环境。
- nuxt3 依赖的 node 版本 >= v14.16.0
- vite >= 12.0.0
所以我们的电脑里需要配置两种 node,在我们当前的项目中使用对应的 node 环境,如何使得不同版本的 node 共存系统呢,请看下文。
nvm 的安装
下载
下载,选择某一个版本的 nvm,安装 nvm-setup.zip。
主子应用的功能分配
主应用:基座(main-vue3)
功能:拿到 token 然后派发到各个子应用
- 登录 /api/auth/login POST
- 登出 /api/auth/logout POST
接口说明:
- 登录:传入账号和密码,返回 token。
- 登出:清除 token。
// POST /api/auth/login
{
"username": "test",
"password": "123456"
}
// 返回
{
"token": "mocked-token-123"
}
子应用:首页(child-nuxt2-home)
功能:
- 首页列表 /api/home/list GET
mock 示例:
// GET /api/home/list
[
{ "id": 1, "title": "首页内容一" },
{ "id": 2, "title": "首页内容二" }
]
子应用:找工作(child-vue2-job)
功能:
- 职位列表 /api/job/list GET
- 职位详情 /api/job/detail/:id GET
mock 示例
// GET /api/job/list
[
{ "id": 101, "title": "前端开发", "company": "阿里巴巴" },
{ "id": 102, "title": "后端开发", "company": "腾讯" }
]
// GET /api/job/detail/101
{
"id": 101,
"title": "前端开发",
"description": "负责前端页面开发",
"company": "阿里巴巴"
}
子应用:找企业(child-vue3-job)
功能:
- 企业列表 /api/company/list GET
- 企业详情 /api/company/detail/:id GET
mock 示例:
// GET /api/company/list
[
{ "id": 201, "name": "字节跳动", "industry": "互联网" },
{ "id": 202, "name": "百度", "industry": "AI" }
]
// GET /api/company/detail/201
{
"id": 201,
"name": "字节跳动",
"industry": "互联网",
"location": "北京"
}
子应用:关于我们(child-react18-about)
功能:
- 关于我们 /api/about/info GET
mock 示例:
// GET /api/about/info
{
"company": "微前端平台",
"description": "一个多技术栈集成的企业级系统"
}
- Mock Server 工具推荐:
- 使用 Mock Service Worker (MSW)(适用于 Vue/React/Nuxt 等)
- 或者本地 node server + json-server/mockjs
- 接口统一管理:
- 每个子应用维护自己的接口配置
- 主应用统一配置公共接口(如认证相关)
- 前后端联调方式:
- 使用 axios 拦截器统一处理 token
- 子应用通过环境变量控制是否启用 mock
实现基座应用
目前使用 node(v18.20.6)
pnpm create vue@latest
# ts
# pina
# vue-router
cd micro-main-vue3
pnpm install
pnpm dev
# 安装相关依赖 router
# /router目录下放置路由
# /views目录下放视图
# /views/child目录下放子应用路由
# /views/main目录下放基座应用的视图
# 安装element plus
- 基座应用上集成 microapp
@micro-zoe/micro-app
- 项目的 main.js 中引入它并且初始它,具体实现看去 github 上看
import microApp from '@micro-zoe/micro-app';
microApp.start();
使用 nust2 实现首页应用(child-nuxt3-home)
它是基于 vue3 的服务端渲染项目(我学习的版本使用的是 vue3 我用的也是 nuxt3)
pnpm create nuxt-app child-nuxt3-home
功能开发完成后,集成到基座应用中,使用<micro-app name="child-app" url="http://localhost:3000/child-home"></micro-app>
, 但它会提示跨域问题,我们可以在 chrome 中添加了一个插件来测试允许跨域,插件名:Allow CORS: Access-Control-Allow-Origin
使用 vue2 构建一个子应用找工作(micro-child-vue2-job)
micro-child-vue2-job/
├── public/
│ └── index.html
├── src/
│ ├── assets/
│ ├── components/
│ ├── views/
│ ├── App.vue
│ ├── main.js
│ └── router.js
├── .gitignore
├── babel.config.js
├── package.json
└── README.md
vue3 构建一个子应用找企业(micro-child-vue3-enterprise)
2024-04-12
接口 mock 调整及列表数据展示
功能改动
- 添加接口代理配置
-
新增
.env.development
文件配置环境变量 - 在
vue.config.js
中添加代理配置,支持接口转发 -
配置 mock 接口地址:
http://127.0.0.1:4523/m1/6202454-5895755-default
- 调整职位列表样式
- 移除列表容器背景色
- 优化职位卡片样式
- 调整字体大小和颜色
- 添加卡片悬停效果
- 优化标签样式
- 完善列表数据展示
- 展示职位基本信息(标题、薪资)
- 展示公司信息(Logo、名称、行业)
- 展示职位标签(地点、经验、学历)
- 展示福利标签
- 展示技能标签
- 添加分页功能
文件变更
- 新增文件:
-
.env.development
:环境配置文件 - 修改文件:
src/api/findJobApi.js
:添加接口调试日志src/assets/scss/findjob.scss
:调整样式src/utils/request.js
:调整请求配置src/views/FindJob.vue
:完善列表展示vue.config.js
:添加代理配置
接口数据结构
{
"code": 200,
"message": "success",
"data": {
"list": [
{
"jobId": "job-100",
"jobTitle": "产品经理",
"enterpriseName": "腾讯科技",
"enterpriseLogo": "https://logo.clearbit.com/mi.com",
"industry": "人工智能",
"jobType": "全职",
"education": "本科",
"workCity": "北京市",
"workExperience": "1年",
"salaryMin": 10642,
"salaryMax": 17846,
"salaryRange": "10642-17846元/月",
"salaryUnit": "月",
"welfareTags": ["五险一金", "交通补贴", "加班补助"],
"skillTags": ["Python", "TensorFlow", "PyTorch"],
"refreshTimeStr": "2024年4月12日"
}
],
"total": 25
}
}
样式规范
- 主色调:#4e6ef2
- 标题文字:16px, #333
- 薪资文字:16px, #ff6b6b
- 普通文字:13px, #666
- 次要文字:12px, #999
- 标签样式:
- 背景色:#f8f9fc
- 文字颜色:#666
- 圆角:2px
- 卡片样式:
- 背景色:#fff
- 内边距:24px
- 圆角:4px
- 阴影:0 1px 3px rgba(0, 0, 0, 0.02)
创建 react 应用
npx create-react-app micro-child-react18-about
基于 React 18 的微前端子应用,支持独立运行和作为子应用运行。
项目结构
micro-child-react18-about/
├── public/ # 静态资源目录
├── src/ # 源代码目录
│ ├── components/ # 公共组件
│ │ └── Layout/ # 布局组件
│ ├── pages/ # 页面组件
│ │ └── About/ # 关于页面
│ ├── styles/ # 样式文件
│ │ └── global.scss # 全局样式
│ ├── utils/ # 工具函数
│ │ └── request.js # 请求封装
│ ├── App.js # 应用入口组件
│ ├── index.js # 应用入口文件
│ └── public-path.js # 微前端环境配置
├── .env # 环境变量
├── package.json # 项目依赖配置
└── README.md # 项目说明文档
micro 沙箱体(js 隔离)
new Fuction("return window")()
(0,eval)("window")
window.rawWindow
比如职位类型可能几个子应该都用到职位类型,我们可以将其提取到基座应该中,另外我们可以在子应用中通过window.__MICRO_APP_PUBLIC_PATH__
如果在主应用中显示 micro-app 不能识别的警告,但不影响使用我们需要在主应用中添加一些配置,在基座应用中 vite.config.js 中添加
//...
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => /^micro-app/.test(tag),
},
},
}),
];
// ..
使用基座应用数据的逻辑
mounted() {
if (window.__MICRO_APP_ENVIRONMENT__) {
// 微前端环境
this.jobTypeArr = window.rawWindow.jobTypeArr;
} else {
this.jobTypeArr = jobTypeArr;
}
// this.getAddressDict();
// this.searchJobList();
}
样式隔离(默认开启)
通过基座应该设置前缀来隔离,通过命名空间来
<el-config-provider namespace="ep">
<common-header v-if="route.name !== 'login'" />
<main-container />
<common-footer v-if="route.name !== 'login'" />
</el-config-provider>
创建一个 scss 文件将
// styles/element/index.scss
// we can add this to custom namespace, default is 'el'
@forward 'element-plus/theme-chalk/src/mixins/config.scss' with (
$namespace: 'ep'
);
// ...
注释掉 main 中的引入import 'element-plus/dist/index.css'
换成 scss 文件
在 vite.config 配置中声明
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "./src/styles/element/index.scss" as *;`,
},
},
},
元 s 隔离
shadow dom
元素可以重复,但又不会产生冲突,所有元素都不会逃离micro-app
这个边界
生命周期(micro-app)
可以 start 上配置lifeCycles
,也可以在每一个标准上使用@mounted
来为每一个子应该设置
主子通信
在micro-app
标签上添加数据
import microApp from '@micro-zoe/micro-app'
const globalData = microApp.getGlobalData()
<micro-app :data="globalData"></micro-app>
在子应用项目中
if (window.__MICRO_APP_ENVIROMENT__) {
const dataForchild = window.microApp.getData();
// 获取数据对象
const { token } = dataForChild;
axios.defaults.headers['x-client-token'] = token;
}
也可以使用microApp.setData('childEnterprise',data)
来设置,这个名字要和micro-app
的 name 保持一致
子应用向主应用发送数据
if (window.__MICRO_APP_ENVIRONMENT__) {
window.microApp.dispatch({
activeIndex: 'job',
});
}
// 在基座应用中获取数据
<script setup>
function handleDataChange(e) {
let { activeIndex } = e.detail.data;
localStorage.setItem('activeIndex', activeIndex);
}
</script>
<template>
<div>
<micro-app
@datachange="handleDataChange"
name="childHome"
url="http://localhost:3000/childHome/"
></micro-app>
</div>
</template>
<style></style>
子应用间通信
在首页应用点击搜索,把关键字带入到找工作子应用
- 判断是不是微前端环境
- 把数据发给基座
searchIt() {
let { homeSearchValue } = this;
if (window.__MICRO_APP_ENVIRONMENT__) {
window.microApp.setGlobalData({ homeSearchValue });
const baseRouter = window.microApp.router.getBaseAppRouter();
baseRouter.push('/main/childJob');
}
},
// 应用启动时要注册其路由信息
microApp.router.setBaseAppRouter(router) //router是基座路由信息
// 在找工作子应用的mounted中把数据拿出来添加到查询接口的参数中
const globalData = window.microApp.getGlobalData()
if(globalData){
}
主应用跳转子应用,我们在基座应用的路由中已经处理过了,如何从子应用跳转到主应用(刷新页面跳,不刷新页面跳)
// 1.
window.microApp.location.href = '/main/login';
// 2
// 获取基座应用的路由然后来设置
const baseRouter = window.microApp.router.getBaseAppRouter();
baseRouter.push('/main/login');
微前端应用的优化
预加载:requestIdleCallback
在浏览器不忙的时候把资源加载一下,在基座应用中在 start({})对象中添加属性prefetchApps
import microApp from '@micro-zoe/micro-app';
microApp.start({
prefetchApps: [
{
name: 'childHome',
url: 'http://localhost:3000/childHome/',
level: 3,
},
{
name: 'childJob',
url: 'http://localhost:8080',
level: 3,
},
{
name: 'childEnterprise',
url: 'http://localhost:3002/child/findEnterprise/',
level: 3,
iframe: true,
},
{
name: 'childAbout',
url: 'http://localhost:3003/',
level: 3,
},
],
});
要和标签的配置保持一致
配前端的资源共享,加载一次,其它子应用加载时从缓存中获取
globalAssets
项配置,但这个配置不是很好,可以在 index.html 中给 link 标签和 script 标签添加 global 属性来标识
<link global rel="stylesheet" href="xx.css" />
<script global src="xx.js"></script>
子应用缓存,
keep-alive
不会真正的被卸载,加载的生命周期是 afterHide beforeShow afterShow丝滑转场
页面加载动效(或者骨架屏),基座应用加载完之后它消失,注意几个点 基座应用加载完成前端的 loading, micro-app 加载前后的 loading,及子应用渲染完成前后,还有点是每个子应用的独立个体的性能优化
优雅上线
这个已经不是我的学习重点了,过一下得了。如需要了解移步其它课程的学习
每一个应用单独打包部署,且集成到基座应用中,生部署子系统应用,然后再部署基座应用
发布工具
- Putty
- Xftp
服务器环境依赖
- Nodejs 16.11.0
- Nginx
- Screen
线上域名
- localhost
- 线上域名
- 📘 统一管理本地和线上 url
发布线上
🚩 子应用部署
- Nuxt2 子应用
- Vue2 子应用
- Vue3 子应用
- React18 子应用
- Gzip 压缩
🟡 基座应用部署
- Vue3 主应用
多页应用
mpa 不同与我们的微前端架构(单一技术架构),不能为了技术而技术,vue2 实现一个多页面应用(也不是这个的重点)
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://joyjs.cn/archives/4451