qiankun基本使用
1、qiankun框架基本介紹
qiankun 是螞蟻金服基于 single-spa 的一個微前端實現開源庫,旨在幫助大家能更簡單、無痛的構建一個生產可用微前端架構系統。qiankun 框架簡化了微應用的注冊方式,增加了微應用的沙箱管理(js、css隔離)與全局狀態共享機制,并且 qiankun 內部實現了一個解析 html 字符串獲取靜態資源地址的解析庫 import-html-entry,方便微應用接入與資源預加載。
qiankun 的核心設計理念:
-
簡單
由于主應用微應用都能做到技術棧無關,qiankun 對于用戶而言只是一個類似 jQuery 的庫,你需要調用幾個 qiankun 的 API 即可完成應用的微前端改造。同時由于 qiankun 的 HTML entry 及沙箱的設計,使得微應用的接入像使用 iframe 一樣簡單。
-
解耦/技術棧無關
微前端的核心目標是將巨石應用拆解成若干可以自治的松耦合微應用,而 qiankun 的諸多設計均是秉持這一原則,如 HTML entry、沙箱、應用間通信等。這樣才能確保微應用真正具備 獨立開發、獨立運行 的能力。
任意框架的前端項目均可使用qiankun框架,微應用接入像使用接入一個 iframe 系統一樣簡單,但實際不是 iframe。
1.1、qiankun框架特性
- ?? 基于 single-spa 封裝,提供了更加開箱即用的 API。
- ?? 技術棧無關,任意技術棧的應用均可 使用/接入,不論是 React/Vue/Angular/JQuery 還是其他等框架。
- ?? HTML Entry 接入方式,讓你接入微應用像使用 iframe 一樣簡單。
- ??? 樣式隔離,確保微應用之間樣式互相不干擾。
- ?? JS 沙箱,確保微應用之間 全局變量/事件 不沖突。
- ?? 資源預加載,在瀏覽器空閑時間預加載未打開的微應用資源,加速微應用打開速度。
- ?? umi 插件,提供了 @umijs/plugin-qiankun 供 umi 應用一鍵切換成微前端架構系統。
1.2、微前端的概念
微前端是一種多個團隊通過獨立發布功能的方式來共同構建現代化 web 應用的技術手段及方法策略。
微前端架構具備以下幾個核心價值:
-
技術棧無關
主框架不限制接入應用的技術棧,微應用具備完全自主權 -
獨立開發、獨立部署
微應用倉庫獨立,前后端可獨立開發,部署完成后主框架自動完成同步更新 -
增量升級
在面對各種復雜場景時,我們通常很難對一個已經存在的系統做全量的技術棧升級或重構,而微前端是一種非常好的實施漸進式重構的手段和策略
-
獨立運行時
每個微應用之間狀態隔離,運行時狀態不共享
微前端架構旨在解決單體應用在一個相對長的時間跨度下,由于參與的人員、團隊的增多、變遷,從一個普通應用演變成一個巨石應用(Frontend Monolith)后,隨之而來的應用不可維護的問題。
2、qiankun使用例子
2.1、主應用和子應用均為原生JS
如果使用原生 script 方式來引入 qiankun 框架,可以在 qiankun 依賴包里找到所需的打包后的 qiankun 結果文件,如下:

假設主應用和子應用都通過本地服務器打開,且子應用訪問URL為:http://127.0.0.1:5500/。
2.1.1、主應用代碼
主應用代碼示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>主應用</title> </head> <body> <div> 主應用 </div> <div id="qiankun-viewport"> </div> <button onclick="btnfn()">點擊跳轉</button> </body> <script src="./index.umd.js"></script> <script> function btnfn() { location.href = "#/react-app" } // microApps 微應用集合 const microApps = [ { name: "reactapp", entry: "//localhost:5500", activeRule: "#/react-app" } ]; const apps = microApps.map((item) => { return { ...item, container: "#qiankun-viewport", // 微應用掛載在主應用中的盒子的 id }; }); // 注冊微應用基礎信息 // 即 apps 微應用數組,后面對象為配置觸發鉤子,與vue的鉤子類似 window.qiankun.registerMicroApps(apps, { beforeLoad: (app) => { console.log("before load app.name====>>>>>", app.name); }, beforeMount: [ (app) => { console.log("before mount app.name====>>>>>", app.name); }, ], afterMount: [ (app) => { console.log("after mount app.name====>>>>>", app.name); }, ], afterUnmount: [ (app) => { console.log("after unmount app.name====>>>>>", app.name); }, ], }); // 添加全局的未捕獲異常處理器 window.qiankun.addGlobalUncaughtErrorHandler((event) => { console.err("微應用加載失敗!", event); }); window.qiankun.start(); </script> </html>
2.1.2、子應用代碼
子應用代碼示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>子應用</title> </head> <body> <div> 子應用 </div> </body> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <script> const render = ($) => { $("#qiankun-viewport").html("Hello, render with jQuery"); return Promise.resolve(); }; ((global) => { global["reactapp"] = { bootstrap: () => { console.log("reactapp bootstrap"); return Promise.resolve(); }, mount: () => { console.log("reactapp mount"); return render($); }, unmount: () => { console.log("reactapp unmount"); return Promise.resolve(); }, }; })(window); </script> </html>
打開主應用,點擊跳轉按鈕,即可看到子應用的內容將會掛載在主應用上。如下:

2.2、主應用為vue2,子應用為原生JS
2.2.1、主應用代碼
我們用 vue-cli 初始化一個 vue項目,并且不使用 vue-router。(實際上用不用vue-router原理都一樣,就是qiankun監聽路由變化然后往容器里面加載子應用內容)
初始化完項目后,使用 npm i qiankun -S 來引入 qiankun 框架,并且修改 main.js 代碼如下:
import Vue from "vue"; import App from "./App"; import { registerMicroApps, addGlobalUncaughtErrorHandler, start } from "qiankun"; Vue.config.productionTip = false; /* eslint-disable no-new */ new Vue({ el: "#app", components: { App }, template: "<App/>" }); // microApps 微應用集合 const microApps = [ { name: "reactapp", entry: "http://localhost:5500", activeRule: "#/react-app" } ]; const apps = microApps.map(item => { return { ...item, container: "#qiankun-viewport" // 微應用掛載在主應用中的盒子的 id }; }); // 注冊微應用基礎信息 // 即 apps 微應用數組,后面對象為配置觸發鉤子,與vue的鉤子類似 registerMicroApps(apps, { beforeLoad: app => { console.log("before load app.name====>>>>>", app.name); }, beforeMount: [ app => { console.log("before mount app.name====>>>>>", app.name); } ], afterMount: [ app => { console.log("after mount app.name====>>>>>", app.name); } ], afterUnmount: [ app => { console.log("after unmount app.name====>>>>>", app.name); } ] }); // 添加全局的未捕獲異常處理器 addGlobalUncaughtErrorHandler(event => { console.err("微應用加載失敗!", event); }); start();
在 App.vue 文件中添加子應用容器,并且通過按鈕跳轉到指定路由,qiankun 監聽到路由變化即會加載子應用內容。代碼如下:
<template>
<div id="app">
<div id="qiankun-viewport"> </div>
<button @click="btnfn">點擊跳轉</button>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld'
export default {
name: 'App',
components: {
HelloWorld
},
methods: {
btnfn() {
location.href = "#/react-app"
}
}
}
</script>
2.2.2、子應用代碼
子應用代碼可參考上面 2.1 中子應用代碼。
2.3、主應用和子應用均為vue2
2.3.1、主應用代碼
我們用 vue-cli 初始化一個 vue項目,并且不使用 vue-router。(實際上用不用vue-router原理都一樣,就是qiankun監聽路由變化然后往容器里面加載子應用內容)
初始化完項目后,使用 npm i qiankun -S 來引入 qiankun 框架,并且修改 main.js 代碼如下:
import Vue from "vue"; import App from "./App"; import { registerMicroApps, addGlobalUncaughtErrorHandler, start } from "qiankun"; Vue.config.productionTip = false; /* eslint-disable no-new */ new Vue({ el: "#app", components: { App }, template: "<App/>" }); // microApps 微應用集合 const microApps = [ { name: "reactapp", entry: "http://localhost:8081", activeRule: "#/react-app" } ]; const apps = microApps.map(item => { return { ...item, container: "#qiankun-viewport" // 微應用掛載在主應用中的盒子的 id }; }); // 注冊微應用基礎信息 // 即 apps 微應用數組,后面對象為配置觸發鉤子,與vue的鉤子類似 registerMicroApps(apps, { beforeLoad: app => { console.log("before load app.name====>>>>>", app.name); }, beforeMount: [ app => { console.log("before mount app.name====>>>>>", app.name); } ], afterMount: [ app => { console.log("after mount app.name====>>>>>", app.name); } ], afterUnmount: [ app => { console.log("after unmount app.name====>>>>>", app.name); } ] }); // 添加全局的未捕獲異常處理器 addGlobalUncaughtErrorHandler(event => { console.err("微應用加載失敗!", event); }); start();
在 App.vue 文件中添加子應用容器,并且通過按鈕跳轉到指定路由,qiankun 監聽到路由變化即會加載子應用內容。代碼如下:
<template> <div id="app"> <img src="./assets/logo.png"> <div id="qiankun-viewport"> </div> <button @click="btnfn">點擊跳轉</button> </div> </template> <script> import HelloWorld from './components/HelloWorld' export default { name: 'App', components: { HelloWorld }, methods: { btnfn() { location.href = "#/react-app" } } } </script>
2.3.2、子應用代碼
如果是以 vue-cli 3+ 生成的 vue 2.x (即有vue.config.js文件)項目,則子應用代碼可參考官網實例:https://qiankun.umijs.org/zh/guide/tutorial#vue-微應用
如果是以 vue-cli2 搭建的 vue2.x 子應用,則配置可參考:http://www.rzrgm.cn/fengsaoke/p/14628997.html
下面示例沒有引用vue-router 的 vue-cli2 搭建的 vue2 應用配置。
第一步跟官網保持一致,在 src 目錄新增 public-path.js,內容如下:
if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
然后修改 main.js 文件,最終文件內容如下:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import "./public-path"; import Vue from "vue"; import App from "./App"; Vue.config.productionTip = false; /* eslint-disable no-new */ // new Vue({ // el: "#app", // components: { App }, // template: "<App/>" // }); let router = null; let instance = null; function render(props = {}) { const { container } = props; // router = new VueRouter({ // base: window.__POWERED_BY_QIANKUN__ ? "/app-vue/" : "/", // mode: "history", // routes // }); instance = new Vue({ // router, // store, render: h => h(App) }).$mount(container ? container.querySelector("#app") : "#app"); } // 獨立運行時 if (!window.__POWERED_BY_QIANKUN__) { render(); } export async function bootstrap() { console.log("[vue] vue app bootstraped"); } export async function mount(props) { console.log("[vue] props from main framework", props); render(props); } export async function unmount() { instance.$destroy(); instance.$el.innerHTML = ""; instance = null; router = null; }
最后修改 webpack.base.conf.js 文件,修改允許跨域和打包格式,內容跟官網差不多,不過有點小修改。如下:
const { name } = require("../package");
module.exports = {
devServer: {
headers: {
"Access-Control-Allow-Origin": "*"
}
},
output: {
library: `${name}-[name]`,
libraryTarget: "umd", // 把微應用打包成 umd 庫格式
jsonpFunction: `webpackJsonp_${name}`,
......
},
}
最終父子應用運行起來,即可通過父應用加載子應用內容,效果如下:

9、qiankun框架原理
9.2、為什么不是使用iframe來實現
為什么不用 iframe,這幾乎是所有微前端方案第一個會被 challenge 的問題。但是大部分微前端方案又不約而同放棄了 iframe 方案,自然是有原因的,并不是為了 "炫技" 或者刻意追求 "特立獨行"。
如果不考慮體驗問題,iframe 幾乎是最完美的微前端解決方案了。iframe 最大的特性就是提供了瀏覽器原生的硬隔離方案,不論是樣式隔離、js 隔離這類問題統統都能被完美解決。但他的最大問題也在于他的隔離性無法被突破,導致應用間上下文無法被共享,隨之帶來的開發體驗、產品體驗的問題。
- url 不同步。瀏覽器刷新 iframe url 狀態丟失、后退前進按鈕無法使用。
- UI 不同步,DOM 結構不共享。想象一下屏幕右下角 1/4 的 iframe 里來一個帶遮罩層的彈框,同時我們要求這個彈框要瀏覽器居中顯示,還要瀏覽器 resize 時自動居中..
- 全局上下文完全隔離,內存變量不共享。iframe 內外系統的通信、數據同步等需求,主應用的 cookie 要透傳到根域名都不同的子應用中實現免登效果。
- 慢。每次子應用進入都是一次瀏覽器上下文重建、資源重新加載的過程。
其中有的問題比較好解決(問題1),有的問題我們可以睜一只眼閉一只眼(問題4),但有的問題我們則很難解決(問題3)甚至無法解決(問題2),而這些無法解決的問題恰恰又會給產品帶來非常嚴重的體驗問題, 最終導致我們舍棄了 iframe 方案。

浙公網安備 33010602011771號