微前端
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://walker-learn.xyz/archives/4451
