寫在開頭
微前端系列文章:
- 基於 qiankun 的微前端最佳實踐(萬字長文) - 從 0 到 1 篇
- 基於 qiankun 的微前端最佳實踐(圖文並茂) - 應用間通訊篇
- 萬字長文+圖文並茂+全面解析微前端框架 qiankun 原始碼 - qiankun 篇
本系列其他文章計劃一到兩個月內完成,點個 關注
不迷路。
計劃如下:
- 生命週期篇;
- IE 相容篇;
- 生產環境部署篇;
- 效能優化、快取方案篇;
引言
大家好~
本文是基於 qiankun
的微前端最佳實踐系列文章之 從 0 到 1 篇
,本文將分享如何使用 qiankun
如何搭建主應用基座,然後接入不同技術棧的微應用,完成微前端架構的從 0 到 1。
本教程採用 Vue
作為主應用基座,接入不同技術棧的微應用。如果你不懂 Vue
也沒關係,我們在搭建主應用基座的教程儘量不涉及 Vue
的 API
,涉及到 API
的地方都會給出解釋。
注意:qiankun
屬於無侵入性的微前端框架,對主應用基座和微應用的技術棧都沒有要求。
我們在本教程中,接入了多技術棧 微應用
的 主應用
最終效果圖如下:
構建主應用基座
我們以 實戰案例 - feature-inject-sub-apps 分支 (案例是以 Vue
為基座的主應用,接入多個微應用) 為例,來介紹一下如何在 qiankun
中如何接入不同技術棧的微應用。
我們先使用 vue-cli
生成一個 Vue
的專案,初始化主應用。
vue-cli 是Vue
官方提供的腳手架工具,用於快速搭建一個Vue
專案。如果你想跳過這一步,可以直接clone
實戰案例 - feature-inject-sub-apps 分支 的程式碼。
將普通的專案改造成 qiankun
主應用基座,需要進行三步操作:
- 建立微應用容器 - 用於承載微應用,渲染顯示微應用;
- 註冊微應用 - 設定微應用啟用條件,微應用地址等等;
- 啟動
qiankun
;
建立微應用容器
我們先在主應用中建立微應用的承載容器,這個容器規定了微應用的顯示區域,微應用將在該容器內渲染並顯示。
我們先設定路由,路由檔案規定了主應用自身的路由匹配規則,程式碼實現如下:
// micro-app-main/src/routes/index.ts
import Home from "@/pages/home/index.vue";
const routes = [
{
/**
* path: 路徑為 / 時觸發該路由規則
* name: 路由的 name 為 Home
* component: 觸發路由時載入 `Home` 元件
*/
path: "/",
name: "Home",
component: Home,
},
];
export default routes;
// micro-app-main/src/main.ts
//...
import Vue from "vue";
import VueRouter from "vue-router";
import routes from "./routes";
/**
* 註冊路由例項
* 即將開始監聽 location 變化,觸發路由規則
*/
const router = new VueRouter({
mode: "history",
routes,
});
// 建立 Vue 例項
// 該例項將掛載/渲染在 id 為 main-app 的節點上
new Vue({
router,
render: (h) => h(App),
}).$mount("#main-app");
從上面程式碼可以看出,我們設定了主應用的路由規則,設定了 Home
主頁的路由匹配規則。
我們現在來設定主應用的佈局,我們會有一個選單和顯示區域,程式碼實現如下:
// micro-app-main/src/App.vue
//...
export default class App extends Vue {
/**
* 選單列表
* key: 唯一 Key 值
* title: 選單標題
* path: 選單對應的路徑
*/
menus = [
{
key: "Home",
title: "主頁",
path: "/",
},
];
}
上面的程式碼是我們對選單配置的實現,我們還需要實現基座和微應用的顯示區域(如下圖)
我們來分析一下上面的程式碼:
-
第 5 行
:主應用選單,用於渲染選單; -
第 9 行
:主應用渲染區。在觸發主應用路由規則時(由路由配置表的$route.name
判斷),將渲染主應用的元件; -
第 10 行
:微應用渲染區。在未觸發主應用路由規則時(由路由配置表的$route.name
判斷),將渲染微應用節點;
從上面的分析可以看出,我們使用了在路由表配置的 name
欄位進行判斷,判斷當前路由是否為主應用路由,最後決定渲染主應用元件或是微應用節點。
由於篇幅原因,樣式實現程式碼就不貼出來了,最後主應用的實現效果如下圖所示:
從上圖可以看出,我們主應用的元件和微應用是顯示在同一片內容區域,根據路由規則決定渲染規則。
註冊微應用
在構建好了主框架後,我們需要使用 qiankun
的 registerMicroApps
方法註冊微應用,程式碼實現如下:
// micro-app-main/src/micro/apps.ts
// 此時我們還沒有微應用,所以 apps 為空
const apps = [];
export default apps;
// micro-app-main/src/micro/index.ts
// 一個進度條外掛
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { message } from "ant-design-vue";
import {
registerMicroApps,
addGlobalUncaughtErrorHandler,
start,
} from "qiankun";
// 微應用註冊資訊
import apps from "./apps";
/**
* 註冊微應用
* 第一個引數 - 微應用的註冊資訊
* 第二個引數 - 全域性生命週期鉤子
*/
registerMicroApps(apps, {
// qiankun 生命週期鉤子 - 微應用載入前
beforeLoad: (app: any) => {
// 載入微應用前,載入進度條
NProgress.start();
console.log("before load", app.name);
return Promise.resolve();
},
// qiankun 生命週期鉤子 - 微應用掛載後
afterMount: (app: any) => {
// 載入微應用前,進度條載入完成
NProgress.done();
console.log("after mount", app.name);
return Promise.resolve();
},
});
/**
* 新增全域性的未捕獲異常處理器
*/
addGlobalUncaughtErrorHandler((event: Event | string) => {
console.error(event);
const { message: msg } = event as any;
// 載入失敗時提示
if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
message.error("微應用載入失敗,請檢查應用是否可執行");
}
});
// 匯出 qiankun 的啟動函式
export default start;
從上面可以看出,我們的微應用註冊資訊在 apps
陣列中(此時為空,我們在後面接入微應用時會新增微應用註冊資訊),然後使用 qiankun
的 registerMicroApps
方法註冊微應用,最後匯出了 start
函式,註冊微應用的工作就完成啦!
啟動主應用
我們在註冊好了微應用,匯出 start
函式後,我們需要在合適的地方呼叫 start
啟動主應用。
我們一般是在入口檔案啟動 qiankun
主應用,程式碼實現如下:
// micro-app-main/src/main.ts
//...
import startQiankun from "./micro";
startQiankun();
最後,啟動我們的主應用,效果圖如下:
因為我們還沒有註冊任何微應用,所以這裡的效果圖和上面的效果圖是一樣的。
到這一步,我們的主應用基座就建立好啦!
接入微應用
我們現在的主應用基座只有一個主頁,現在我們需要接入微應用。
qiankun
內部通過 import-entry-html
載入微應用,要求微應用需要匯出生命週期鉤子函式(見下圖)。
從上圖可以看出,qiankun
內部會校驗微應用的生命週期鉤子函式,如果微應用沒有匯出這三個生命週期鉤子函式,則微應用會載入失敗。
如果我們使用了腳手架搭建微應用的話,我們可以通過 webpack
配置在入口檔案處匯出這三個生命週期鉤子函式。如果沒有使用腳手架的話,也可以直接在微應用的 window
上掛載這三個生命週期鉤子函式。
現在我們來接入我們的各個技術棧微應用吧!
注意,下面的內容對相關技術棧 API
不會再有過多介紹啦,如果你要接入不同技術棧的微應用,最好要對該技術棧有一些基礎瞭解。
接入 Vue
微應用
我們以 實戰案例 - feature-inject-sub-apps 分支 為例,我們在主應用的同級目錄(micro-app-main
同級目錄),使用 vue-cli
先建立一個 Vue
的專案,在命令列執行如下命令:
vue create micro-app-vue
本文的 vue-cli
選項如下圖所示,你也可以根據自己的喜好選擇配置。
在新建專案完成後,我們建立幾個路由頁面再加上一些樣式,最後效果如下:
註冊微應用
在建立好了 Vue
微應用後,我們可以開始我們的接入工作了。首先我們需要在主應用中註冊該微應用的資訊,程式碼實現如下:
// micro-app-main/src/micro/apps.ts
const apps = [
/**
* name: 微應用名稱 - 具有唯一性
* entry: 微應用入口 - 通過該地址載入微應用
* container: 微應用掛載節點 - 微應用載入完成後將掛載在該節點上
* activeRule: 微應用觸發的路由規則 - 觸發路由規則後將載入該微應用
*/
{
name: "VueMicroApp",
entry: "//localhost:10200",
container: "#frame",
activeRule: "/vue",
},
];
export default apps;
通過上面的程式碼,我們就在主應用中註冊了我們的 Vue
微應用,進入 /vue
路由時將載入我們的 Vue
微應用。
我們在選單配置處也加入 Vue
微應用的快捷入口,程式碼實現如下:
// micro-app-main/src/App.vue
//...
export default class App extends Vue {
/**
* 選單列表
* key: 唯一 Key 值
* title: 選單標題
* path: 選單對應的路徑
*/
menus = [
{
key: "Home",
title: "主頁",
path: "/",
},
{
key: "VueMicroApp",
title: "Vue 主頁",
path: "/vue",
},
{
key: "VueMicroAppList",
title: "Vue 列表頁",
path: "/vue/list",
},
];
}
選單配置完成後,我們的主應用基座效果圖如下
配置微應用
在主應用註冊好了微應用後,我們還需要對微應用進行一系列的配置。首先,我們在 Vue
的入口檔案 main.js
中,匯出 qiankun
主應用所需要的三個生命週期鉤子函式,程式碼實現如下:
從上圖來分析:
-
第 6 行
:webpack
預設的publicPath
為""
空字串,會基於當前路徑來載入資源。我們在主應用中載入微應用時需要重新設定publicPath
,這樣才能正確載入微應用的相關資源。(public-path.js
具體實現在後面) -
第 21 行
:微應用的掛載函式,在主應用中執行時將在mount
生命週期鉤子函式中呼叫,可以保證在沙箱內執行。 -
第 38 行
:微應用獨立執行時,直接執行render
函式掛載微應用。 -
第 46 行
:微應用匯出的生命週期鉤子函式 -bootstrap
。 -
第 53 行
:微應用匯出的生命週期鉤子函式 -mount
。 -
第 61 行
:微應用匯出的生命週期鉤子函式 -unmount
。
完整程式碼實現如下:
// micro-app-vue/src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// 動態設定 webpack publicPath,防止資源載入出錯
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// micro-app-vue/src/main.js
import Vue from "vue";
import VueRouter from "vue-router";
import Antd from "ant-design-vue";
import "ant-design-vue/dist/antd.css";
import "./public-path";
import App from "./App.vue";
import routes from "./routes";
Vue.use(VueRouter);
Vue.use(Antd);
Vue.config.productionTip = false;
let instance = null;
let router = null;
/**
* 渲染函式
* 兩種情況:主應用生命週期鉤子中執行 / 微應用單獨啟動時執行
*/
function render() {
// 在 render 中建立 VueRouter,可以保證在解除安裝微應用時,移除 location 事件監聽,防止事件汙染
router = new VueRouter({
// 執行在主應用中時,新增路由名稱空間 /vue
base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/",
mode: "history",
routes,
});
// 掛載應用
instance = new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
}
// 獨立執行時,直接掛載應用
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
/**
* bootstrap 只會在微應用初始化的時候呼叫一次,下次微應用重新進入時會直接呼叫 mount 鉤子,不會再重複觸發 bootstrap。
* 通常我們可以在這裡做一些全域性變數的初始化,比如不會在 unmount 階段被銷燬的應用級別的快取等。
*/
export async function bootstrap() {
console.log("VueMicroApp bootstraped");
}
/**
* 應用每次進入都會呼叫 mount 方法,通常我們在這裡觸發應用的渲染方法
*/
export async function mount(props) {
console.log("VueMicroApp mount", props);
render(props);
}
/**
* 應用每次 切出/解除安裝 會呼叫的方法,通常在這裡我們會解除安裝微應用的應用例項
*/
export async function unmount() {
console.log("VueMicroApp unmount");
instance.$destroy();
instance = null;
router = null;
}
在配置好了入口檔案 main.js
後,我們還需要配置 webpack
,使 main.js
匯出的生命週期鉤子函式可以被 qiankun
識別獲取。
我們直接配置 vue.config.js
即可,程式碼實現如下:
// micro-app-vue/vue.config.js
const path = require("path");
module.exports = {
devServer: {
// 監聽埠
port: 10200,
// 關閉主機檢查,使微應用可以被 fetch
disableHostCheck: true,
// 配置跨域請求頭,解決開發環境的跨域問題
headers: {
"Access-Control-Allow-Origin": "*",
},
},
configureWebpack: {
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
output: {
// 微應用的包名,這裡與主應用中註冊的微應用名稱一致
library: "VueMicroApp",
// 將你的 library 暴露為所有的模組定義下都可執行的方式
libraryTarget: "umd",
// 按需載入相關,設定為 webpackJsonp_VueMicroApp 即可
jsonpFunction: `webpackJsonp_VueMicroApp`,
},
},
};
我們需要重點關注一下 output
選項,當我們把 libraryTarget
設定為 umd
後,我們的 library
就暴露為所有的模組定義下都可執行的方式了,主應用就可以獲取到微應用的生命週期鉤子函式了。
在 vue.config.js
修改完成後,我們重新啟動 Vue
微應用,然後開啟主應用基座 http://localhost:9999
。我們點選左側選單切換到微應用,此時我們的 Vue
微應用被正確載入啦!(見下圖)
此時我們開啟控制檯,可以看到我們所執行的生命週期鉤子函式(見下圖)
到這裡,Vue
微應用就接入成功了!
接入 React
微應用
我們以 實戰案例 - feature-inject-sub-apps 分支 為例,我們在主應用的同級目錄(micro-app-main
同級目錄),使用 react-create-app
先建立一個 React
的專案,在命令列執行如下命令:
npx create-react-app micro-app-react
在專案建立完成後,我們在根目錄下新增 .env
檔案,設定專案監聽的埠,程式碼實現如下:
# micro-app-react/.env
PORT=10100
BROWSER=none
然後,我們建立幾個路由頁面再加上一些樣式,最後效果如下:
註冊微應用
在建立好了 React
微應用後,我們可以開始我們的接入工作了。首先我們需要在主應用中註冊該微應用的資訊,程式碼實現如下:
// micro-app-main/src/micro/apps.ts
const apps = [
/**
* name: 微應用名稱 - 具有唯一性
* entry: 微應用入口 - 通過該地址載入微應用
* container: 微應用掛載節點 - 微應用載入完成後將掛載在該節點上
* activeRule: 微應用觸發的路由規則 - 觸發路由規則後將載入該微應用
*/
{
name: "ReactMicroApp",
entry: "//localhost:10100",
container: "#frame",
activeRule: "/react",
},
];
export default apps;
通過上面的程式碼,我們就在主應用中註冊了我們的 React
微應用,進入 /react
路由時將載入我們的 React
微應用。
我們在選單配置處也加入 React
微應用的快捷入口,程式碼實現如下:
// micro-app-main/src/App.vue
//...
export default class App extends Vue {
/**
* 選單列表
* key: 唯一 Key 值
* title: 選單標題
* path: 選單對應的路徑
*/
menus = [
{
key: "Home",
title: "主頁",
path: "/",
},
{
key: "ReactMicroApp",
title: "React 主頁",
path: "/react",
},
{
key: "ReactMicroAppList",
title: "React 列表頁",
path: "/react/list",
},
];
}
選單配置完成後,我們的主應用基座效果圖如下
配置微應用
在主應用註冊好了微應用後,我們還需要對微應用進行一系列的配置。首先,我們在 React
的入口檔案 index.js
中,匯出 qiankun
主應用所需要的三個生命週期鉤子函式,程式碼實現如下:
從上圖來分析:
-
第 5 行
:webpack
預設的publicPath
為""
空字串,會基於當前路徑來載入資源。我們在主應用中載入微應用時需要重新設定publicPath
,這樣才能正確載入微應用的相關資源。(public-path.js
具體實現在後面) -
第 12 行
:微應用的掛載函式,在主應用中執行時將在mount
生命週期鉤子函式中呼叫,可以保證在沙箱內執行。 -
第 17 行
:微應用獨立執行時,直接執行render
函式掛載微應用。 -
第 25 行
:微應用匯出的生命週期鉤子函式 -bootstrap
。 -
第 32 行
:微應用匯出的生命週期鉤子函式 -mount
。 -
第 40 行
:微應用匯出的生命週期鉤子函式 -unmount
。
完整程式碼實現如下:
// micro-app-react/src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// 動態設定 webpack publicPath,防止資源載入出錯
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// micro-app-react/src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./public-path";
import App from "./App.jsx";
/**
* 渲染函式
* 兩種情況:主應用生命週期鉤子中執行 / 微應用單獨啟動時執行
*/
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
}
// 獨立執行時,直接掛載應用
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
/**
* bootstrap 只會在微應用初始化的時候呼叫一次,下次微應用重新進入時會直接呼叫 mount 鉤子,不會再重複觸發 bootstrap。
* 通常我們可以在這裡做一些全域性變數的初始化,比如不會在 unmount 階段被銷燬的應用級別的快取等。
*/
export async function bootstrap() {
console.log("ReactMicroApp bootstraped");
}
/**
* 應用每次進入都會呼叫 mount 方法,通常我們在這裡觸發應用的渲染方法
*/
export async function mount(props) {
console.log("ReactMicroApp mount", props);
render(props);
}
/**
* 應用每次 切出/解除安裝 會呼叫的方法,通常在這裡我們會解除安裝微應用的應用例項
*/
export async function unmount() {
console.log("ReactMicroApp unmount");
ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}
在配置好了入口檔案 index.js
後,我們還需要配置路由名稱空間,以確保主應用可以正確載入微應用,程式碼實現如下:
// micro-app-react/src/App.jsx
const BASE_NAME = window.__POWERED_BY_QIANKUN__ ? "/react" : "";
const App = () => {
//...
return (
// 設定路由名稱空間
<Router basename={BASE_NAME}>{/* ... */}</Router>
);
};
接下來,我們還需要配置 webpack
,使 index.js
匯出的生命週期鉤子函式可以被 qiankun
識別獲取。
我們需要藉助 react-app-rewired
來幫助我們修改 webpack
的配置,我們直接安裝該外掛:
npm install react-app-rewired -D
在 react-app-rewired
安裝完成後,我們還需要修改 package.json
的 scripts
選項,修改為由 react-app-rewired
啟動應用,就像下面這樣
// micro-app-react/package.json
//...
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
}
在 react-app-rewired
配置完成後,我們新建 config-overrides.js
檔案來配置 webpack
,程式碼實現如下:
const path = require("path");
module.exports = {
webpack: (config) => {
// 微應用的包名,這裡與主應用中註冊的微應用名稱一致
config.output.library = `ReactMicroApp`;
// 將你的 library 暴露為所有的模組定義下都可執行的方式
config.output.libraryTarget = "umd";
// 按需載入相關,設定為 webpackJsonp_VueMicroApp 即可
config.output.jsonpFunction = `webpackJsonp_ReactMicroApp`;
config.resolve.alias = {
...config.resolve.alias,
"@": path.resolve(__dirname, "src"),
};
return config;
},
devServer: function (configFunction) {
return function (proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
// 關閉主機檢查,使微應用可以被 fetch
config.disableHostCheck = true;
// 配置跨域請求頭,解決開發環境的跨域問題
config.headers = {
"Access-Control-Allow-Origin": "*",
};
// 配置 history 模式
config.historyApiFallback = true;
return config;
};
},
};
我們需要重點關注一下 output
選項,當我們把 libraryTarget
設定為 umd
後,我們的 library
就暴露為所有的模組定義下都可執行的方式了,主應用就可以獲取到微應用的生命週期鉤子函式了。
在 config-overrides.js
修改完成後,我們重新啟動 React
微應用,然後開啟主應用基座 http://localhost:9999
。我們點選左側選單切換到微應用,此時我們的 React
微應用被正確載入啦!(見下圖)
此時我們開啟控制檯,可以看到我們所執行的生命週期鉤子函式(見下圖)
到這裡,React
微應用就接入成功了!
接入 Angular
微應用
Angular
與 qiankun
目前的相容性並不太好,接入 Angular
微應用需要一定的耐心與技巧。
對於選擇 Angular
技術棧的前端開發來說,對這類情況應該駕輕就熟(沒有辦法)。
我們以 實戰案例 - feature-inject-sub-apps 分支 為例,我們在主應用的同級目錄(micro-app-main
同級目錄),使用 @angular/cli
先建立一個 Angular
的專案,在命令列執行如下命令:
ng new micro-app-angular
本文的 @angular/cli
選項如下圖所示,你也可以根據自己的喜好選擇配置。
然後,我們建立幾個路由頁面再加上一些樣式,最後效果如下:
註冊微應用
在建立好了 Angular
微應用後,我們可以開始我們的接入工作了。首先我們需要在主應用中註冊該微應用的資訊,程式碼實現如下:
// micro-app-main/src/micro/apps.ts
const apps = [
/**
* name: 微應用名稱 - 具有唯一性
* entry: 微應用入口 - 通過該地址載入微應用
* container: 微應用掛載節點 - 微應用載入完成後將掛載在該節點上
* activeRule: 微應用觸發的路由規則 - 觸發路由規則後將載入該微應用
*/
{
name: "AngularMicroApp",
entry: "//localhost:10300",
container: "#frame",
activeRule: "/angular",
},
];
export default apps;
通過上面的程式碼,我們就在主應用中註冊了我們的 Angular
微應用,進入 /angular
路由時將載入我們的 Angular
微應用。
我們在選單配置處也加入 Angular
微應用的快捷入口,程式碼實現如下:
// micro-app-main/src/App.vue
//...
export default class App extends Vue {
/**
* 選單列表
* key: 唯一 Key 值
* title: 選單標題
* path: 選單對應的路徑
*/
menus = [
{
key: "Home",
title: "主頁",
path: "/",
},
{
key: "AngularMicroApp",
title: "Angular 主頁",
path: "/angular",
},
{
key: "AngularMicroAppList",
title: "Angular 列表頁",
path: "/angular/list",
},
];
}
選單配置完成後,我們的主應用基座效果圖如下
最後我們在主應用的入口檔案,引入 zone.js
,程式碼實現如下:
Angular
執行依賴於zone.js
。
qiankun
基於single-spa
實現,single-spa
明確指出一個專案的zone.js
只能存在一份例項,所以我們在主應用注入zone.js
。
// micro-app-main/src/main.js
// 為 Angular 微應用所做的 zone 包注入
import "zone.js/dist/zone";
配置微應用
在主應用的工作完成後,我們還需要對微應用進行一系列的配置。首先,我們使用 single-spa-angular
生成一套配置,在命令列執行以下命令:
# 安裝 single-spa
yarn add single-spa -S
# 新增 single-spa-angular
ng add single-spa-angular
執行命令時,根據自己的需求選擇配置即可,本文配置如下:
在生成 single-spa
配置後,我們需要進行一些 qiankun
的接入配置。我們在 Angular
微應用的入口檔案 main.single-spa.ts
中,匯出 qiankun
主應用所需要的三個生命週期鉤子函式,程式碼實現如下:
從上圖來分析:
-
第 21 行
:微應用獨立執行時,直接執行掛載函式掛載微應用。 -
第 46 行
:微應用匯出的生命週期鉤子函式 -bootstrap
。 -
第 50 行
:微應用匯出的生命週期鉤子函式 -mount
。 -
第 54 行
:微應用匯出的生命週期鉤子函式 -unmount
。
完整程式碼實現如下:
// micro-app-angular/src/main.single-spa.ts
import { enableProdMode, NgZone } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { Router } from "@angular/router";
import { ɵAnimationEngine as AnimationEngine } from "@angular/animations/browser";
import {
singleSpaAngular,
getSingleSpaExtraProviders,
} from "single-spa-angular";
import { AppModule } from "./app/app.module";
import { environment } from "./environments/environment";
import { singleSpaPropsSubject } from "./single-spa/single-spa-props";
if (environment.production) {
enableProdMode();
}
// 微應用單獨啟動時執行
if (!(window as any).__POWERED_BY_QIANKUN__) {
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
}
const { bootstrap, mount, unmount } = singleSpaAngular({
bootstrapFunction: (singleSpaProps) => {
singleSpaPropsSubject.next(singleSpaProps);
return platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(
AppModule
);
},
template: "<app-root />",
Router,
NgZone,
AnimationEngine,
});
/** 主應用生命週期鉤子中執行 */
export {
/**
* bootstrap 只會在微應用初始化的時候呼叫一次,下次微應用重新進入時會直接呼叫 mount 鉤子,不會再重複觸發 bootstrap。
* 通常我們可以在這裡做一些全域性變數的初始化,比如不會在 unmount 階段被銷燬的應用級別的快取等。
*/
bootstrap,
/**
* 應用每次進入都會呼叫 mount 方法,通常我們在這裡觸發應用的渲染方法
*/
mount,
/**
* 應用每次 切出/解除安裝 會呼叫的方法,通常在這裡我們會解除安裝微應用的應用例項
*/
unmount,
};
在配置好了入口檔案 main.single-spa.ts
後,我們還需要配置 webpack
,使 main.single-spa.ts
匯出的生命週期鉤子函式可以被 qiankun
識別獲取。
我們直接配置 extra-webpack.config.js
即可,程式碼實現如下:
// micro-app-angular/extra-webpack.config.js
const singleSpaAngularWebpack = require("single-spa-angular/lib/webpack")
.default;
const webpackMerge = require("webpack-merge");
module.exports = (angularWebpackConfig, options) => {
const singleSpaWebpackConfig = singleSpaAngularWebpack(
angularWebpackConfig,
options
);
const singleSpaConfig = {
output: {
// 微應用的包名,這裡與主應用中註冊的微應用名稱一致
library: "AngularMicroApp",
// 將你的 library 暴露為所有的模組定義下都可執行的方式
libraryTarget: "umd",
},
};
const mergedConfig = webpackMerge.smart(
singleSpaWebpackConfig,
singleSpaConfig
);
return mergedConfig;
};
我們需要重點關注一下 output
選項,當我們把 libraryTarget
設定為 umd
後,我們的 library
就暴露為所有的模組定義下都可執行的方式了,主應用就可以獲取到微應用的生命週期鉤子函式了。
在 extra-webpack.config.js
修改完成後,我們還需要修改一下 package.json
中的啟動命令,修改如下:
// micro-app-angular/package.json
{
//...
"script": {
//...
// --disable-host-check: 關閉主機檢查,使微應用可以被 fetch
// --port: 監聽埠
// --base-href: 站點的起始路徑,與主應用中配置的一致
"start": "ng serve --disable-host-check --port 10300 --base-href /angular"
}
}
修改完成後,我們重新啟動 Angular
微應用,然後開啟主應用基座 http://localhost:9999
。我們點選左側選單切換到微應用,此時我們的 Angular
微應用被正確載入啦!(見下圖)
到這裡,Angular
微應用就接入成功了!
接入 Jquery、xxx...
微應用
這裡的Jquery、xxx...
微應用指的是沒有使用腳手架,直接採用html + css + js
三劍客開發的應用。本案例使用了一些高階
ES
語法,請使用谷歌瀏覽器執行檢視效果。
我們以 實戰案例 - feature-inject-sub-apps 分支 為例,我們在主應用的同級目錄(micro-app-main
同級目錄),手動建立目錄 micro-app-static
。
我們使用 express
作為伺服器載入靜態 html
,我們先編輯 package.json
,設定啟動命令和相關依賴。
// micro-app-static/package.json
{
"name": "micro-app-jquery",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"cors": "^2.8.5"
},
"devDependencies": {
"nodemon": "^2.0.2"
}
}
然後新增入口檔案 index.js
,程式碼實現如下:
// micro-app-static/index.js
const express = require("express");
const cors = require("cors");
const app = express();
// 解決跨域問題
app.use(cors());
app.use('/', express.static('static'));
// 監聽埠
app.listen(10400, () => {
console.log("server is listening in http://localhost:10400")
});
使用 npm install
安裝相關依賴後,我們使用 npm start
啟動應用。
我們新建 static
資料夾,在資料夾內新增一個靜態頁面 index.html
(程式碼在後面會貼出),加上一些樣式後,開啟瀏覽器,最後效果如下:
註冊微應用
在建立好了 Static
微應用後,我們可以開始我們的接入工作了。首先我們需要在主應用中註冊該微應用的資訊,程式碼實現如下:
// micro-app-main/src/micro/apps.ts
const apps = [
/**
* name: 微應用名稱 - 具有唯一性
* entry: 微應用入口 - 通過該地址載入微應用
* container: 微應用掛載節點 - 微應用載入完成後將掛載在該節點上
* activeRule: 微應用觸發的路由規則 - 觸發路由規則後將載入該微應用
*/
{
name: "StaticMicroApp",
entry: "//localhost:10400",
container: "#frame",
activeRule: "/static"
},
];
export default apps;
通過上面的程式碼,我們就在主應用中註冊了我們的 Static
微應用,進入 /static
路由時將載入我們的 Static
微應用。
我們在選單配置處也加入 Static
微應用的快捷入口,程式碼實現如下:
// micro-app-main/src/App.vue
//...
export default class App extends Vue {
/**
* 選單列表
* key: 唯一 Key 值
* title: 選單標題
* path: 選單對應的路徑
*/
menus = [
{
key: "Home",
title: "主頁",
path: "/"
},
{
key: "StaticMicroApp",
title: "Static 微應用",
path: "/static"
}
];
}
選單配置完成後,我們的主應用基座效果圖如下
配置微應用
在主應用註冊好了微應用後,我們還需要直接寫微應用 index.html
的程式碼即可,程式碼實現如下:
從上圖來分析:
-
第 70 行
:微應用的掛載函式,在主應用中執行時將在mount
生命週期鉤子函式中呼叫,可以保證在沙箱內執行。 -
第 77 行
:微應用獨立執行時,直接執行render
函式掛載微應用。 -
第 88 行
:微應用註冊的生命週期鉤子函式 -bootstrap
。 -
第 95 行
:微應用註冊的生命週期鉤子函式 -mount
。 -
第 102 行
:微應用註冊的生命週期鉤子函式 -unmount
。
完整程式碼實現如下:
<!-- micro-app-static/static/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<!-- 引入 bootstrap -->
<link
href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css"
rel="stylesheet"
/>
<title>Jquery App</title>
</head>
<body>
<section
id="jquery-app-container"
style="padding: 20px; color: blue;"
></section>
</body>
<!-- 引入 jquery -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
/**
* 請求介面資料,構建 HTML
*/
async function buildHTML() {
const result = await fetch("http://dev-api.jt-gmall.com/mall", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
// graphql 的查詢風格
body: JSON.stringify({
query: `{ vegetableList (page: 1, pageSize: 20) { page, pageSize, total, items { _id, name, poster, price } } }`,
}),
}).then((res) => res.json());
const list = result.data.vegetableList.items;
const html = `<table class="table">
<thead>
<tr>
<th scope="col">菜名</th>
<th scope="col">圖片</th>
<th scope="col">報價</th>
</tr>
</thead>
<tbody>
${list
.map(
(item) => `
<tr>
<td>
<img style="width: 40px; height: 40px; border-radius: 100%;" src="${item.poster}"></img>
</td>
<td>${item.name}</td>
<td>¥ ${item.price}</td>
</tr>
`
)
.join("")}
</tbody>
</table>`;
return html;
}
/**
* 渲染函式
* 兩種情況:主應用生命週期鉤子中執行 / 微應用單獨啟動時執行
*/
const render = async ($) => {
const html = await buildHTML();
$("#jquery-app-container").html(html);
return Promise.resolve();
};
// 獨立執行時,直接掛載應用
if (!window.__POWERED_BY_QIANKUN__) {
render($);
}
((global) => {
/**
* 註冊微應用生命週期鉤子函式
* global[appName] 中的 appName 與主應用中註冊的微應用名稱一致
*/
global["StaticMicroApp"] = {
/**
* bootstrap 只會在微應用初始化的時候呼叫一次,下次微應用重新進入時會直接呼叫 mount 鉤子,不會再重複觸發 bootstrap。
* 通常我們可以在這裡做一些全域性變數的初始化,比如不會在 unmount 階段被銷燬的應用級別的快取等。
*/
bootstrap: () => {
console.log("MicroJqueryApp bootstraped");
return Promise.resolve();
},
/**
* 應用每次進入都會呼叫 mount 方法,通常我們在這裡觸發應用的渲染方法
*/
mount: () => {
console.log("MicroJqueryApp mount");
return render($);
},
/**
* 應用每次 切出/解除安裝 會呼叫的方法,通常在這裡我們會解除安裝微應用的應用例項
*/
unmount: () => {
console.log("MicroJqueryApp unmount");
return Promise.resolve();
},
};
})(window);
</script>
</html>
在構建好了 Static
微應用後,我們開啟主應用基座 http://localhost:9999
。我們點選左側選單切換到微應用,此時可以看到,我們的 Static
微應用被正確載入啦!(見下圖)
此時我們開啟控制檯,可以看到我們所執行的生命週期鉤子函式(見下圖)
到這裡,Static
微應用就接入成功了!
擴充套件閱讀
如果在 Static
微應用的 html
中注入 SPA
路由功能的話,將演變成單頁應用,只需要在主應用中註冊一次。
如果是多個 html
的多頁應用 - MPA
,則需要在伺服器(或反向代理伺服器)中通過 referer
頭返回對應的 html
檔案,或者在主應用中註冊多個微應用(不推薦)。
小結
最後,我們所有微應用都註冊在主應用和主應用的選單中,效果圖如下:
從上圖可以看出,我們把不同技術棧 Vue、React、Angular、Jquery...
的微應用都已經接入到主應用基座中啦!
最後一件事
如果您已經看到這裡了,希望您還是點個 贊
再走吧~
您的 點贊
是對作者的最大鼓勵,也可以讓更多人看到本篇文章!
如果感興趣的話,請關注 部落格 或者關注作者即可獲取最新動態!