基于 Qiankun 微前端實踐- 從零到一篇
簡短的概括:微前端痛點與解決問題
1?使用背景
1)在同一個頁面可以使用多個前端框架(React, AngularJS, Vue 等);
2)用新框架編寫新代碼,無需重寫已有的 app;
3)代碼的延遲加載可以縮減初次加載時長;
2?主要解決的問題:
1)在一個 app 中不同的模塊由不同的團隊維護,而每個團隊所用的技術棧可能不同,而且發版周期不同。
2)所使用的前端框架升級負擔,新版本可能存在不兼容的更新,升級后可能對已有的業務產生 bug,造成難以升級。限制了前端框架新版本的使用。
一、為什么需要微前端
「~ 微前端導圖 ~」
我們通過 3w (what,why,how) 的方式來講解微前端
1、what? 什么是微前端?
微前端就是將不同的功能按照不同的維度拆分成多個子應用。通過主應用來加載這些子應用。
微前端的核心在于拆,拆完后再合!
微前端架構具備以下幾個核心價值:(重要)(摘自 qiankun官方文檔)
1)技術棧無關
主框架不限制接入應用的技術棧,微應用具備完全自主權;
2)獨立開發、獨立部署
微應用倉庫獨立,前后端可獨立開發,部署完成后主框架自動完成同步更新;
3)增量升級
在面對各種復雜場景時,我們通常很難對一個已經存在的系統做全量的技術棧升級或重構,而微前端是一種非常好的實施漸進式重構的手段和策略;
4)獨立運行時
每個微應用之間狀態隔離,運行時狀態不共享;
2、why? 為什么去使用他?
1)不同團隊間開發同一個應用技術棧不同怎么破?
2)希望每個團隊都可以獨立開發,獨立部署怎么破?
3)項目中還需要老的應用代碼怎么破?
我們是不是可以將一個應用劃分成若干個子應用,將子應用打包成一個個的lib。當路徑切換時加載不同的子應用。這樣每個子應用都是獨立的,技術棧也不用做限制了!從而解決了前端協同開發問題。
3、How? 怎么落地微前端?
2018年 Single-SPA 誕生了,single-spa 是一個用于前端微服務化的 JavaScript 前端解決方案(本身沒有處理樣式隔離,js 執行隔離)實現了路由劫持和應用加載。
說明:single-spa 解決了以應用為維度的路由,應用的注冊,監聽,最重要的是賦予了應用生命周期和生命周期相關事件。
*Single-SPA 缺陷:不夠靈活,不能動態加載js文件;樣式不隔離,沒有js沙箱的機制。
2019年 qiankun 是微前端框架,提供了更加開箱即用的 API (single-spa + sandbox + import-html-entry),它基于 single-spa,具備 js 沙箱、樣式隔離、HTML Loader、預加載 等微前端系統所需的能力。qiakun 升級 2.0 后,支持多個微應用的同時加載,有了這個特性,我們基本可以像接入 iframe 一樣方便的接入微應用。
*總結:子應用可以獨立構建,運行時動態加載,主子應用完全解耦,技術棧無關,靠的是協議接入(子應用必須導出 bootstrap,mount,unmount方法)
擴展:
1)Single-SPA 官網地址:
https://zh-hans.single-spa.js.org/docs/getting-started-overview
2)qiankun官網地址:
https://qiankun.umijs.org/zh
二、解決隔離的方案
1、css 隔離方案
子應用之間樣式隔離:
Dynamic Stylesheet 動態樣式表,當應用切換時移除老應用樣式,添加新應用樣式;
主應用和子應用之間的樣式隔離:
1)BEM(Block Element Modifier ) 約定項目前綴;
2)css-Modules 打包時生成不沖突的選擇器名;
3)Shadow DOM 真正意義上的隔離;
4)css-in-js
2、沙箱 shaowDom
*css 解決方法:
// dom的api
// 外界無法訪問 shadow dom
let shadowDOM = document.getElementById('x').attachShadow({mode: 'closed'});
let pElm = document.createElement('p');
pElm.innerHTML = 'hello';
let styleElm = document.createElement('style');
styleElm.textContent = `
p{color: red}
`
shadowDOM.appendchild(styleElm);
shadowDOM.appendchild(pElm);
*JS 沙箱 proxy
快照沙箱簡單理解:1年前拍一張 在拍一張 (將區別保存起來) 在回到一年前
源碼實踐
let sandbox = new SnapshotSandbox();
class SnapshotSandbox{
constructor(){
this.proxy = window; // window屬性
this.modifyPropsmap = {}; // 記錄在window上的修改
this.active();
}
active() { // 激活
this.windowSnapshot = {}; //拍照
for(const prop in window) {
if(window.hasOwnProperty(prop)){
this.windowsnapshot[prop] = window[prop];
}
}
object.keys(this.modifyPropsMap).forEach(p=>{
window[p] = this.modifyPropsMap[p];
})
}
inactive(){ // 失活
for(const prop in window){
if(window.hasOwnProperty(prop)){
if(window[prop] !== this.windowsnapshot[prop]){
this.modifyPropsMap[prop] = window[prop];
window[prop] = this.windowsnapshot[prop]
}
}
}
}
}
// 應用的運行 從開始到結束, 切換后不會影響全局
((window)=> {
window.a = 1;
window.b = 2;
console.log(window.a,window.b);
sandbox.inactive();
console.log(window.a,window.b);
sandbox.active();
console.log(window.a,window.b);
})(sandbox.proxy); // sandbox.proxy 就是window
// 如果是多個子應用就不能使用這種方式了,es6的proxy
// 代理沙箱可以實現多應用沙箱。把不同的應用用不同的代理來處理
三、qiankun (乾坤) 項目實踐
*將普通的項目改造成 qiankun 主應用基座,需要進行三步操作:
1、創建微應用容器 - 用于承載微應用,渲染顯示微應用
2、注冊微應用 - 設置微應用激活條件,微應用地址等等;
3、啟動 qiankun;
擴展:主應用不限技術棧,只需要提供一個容器 DOM,然后注冊微應用并 start 即可。
*微前端 qiankun 項目實踐:主應用(基座)配置 react 17.0.2,子應用配置 vue 2.6.10
詳細配置如下:
1. 主應用(基座)配置 react 17.0.2
1.1 主應用為子應用準備的 展示元素 (文件:src/App.js )
import {BrowserRouter as Router,Link} from 'react-router-dom'
function App() {
return (
<div className="App">
<Router>
<Link to="/vue">vue應用</Link>
</Router>
{/* 切換導航, 將微應用渲染container容器中 */}
<div id="container"></div>
</div>
);
}
export default App;
1.2 引入react 渲染,注冊 registerApps (文件:src/index.js )
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './registerApps'
ReactDOM.render(
<App />,
document.getElementById('root')
);
1.3 在主應用中注冊微應用(文件:src/registerApps.js)
1?安裝 qiankun (建議安裝:qiankun 2.X以上,支持多個微應用的同時加載)
yarn add qiankun 或者 npm i qiankun
相關配置信息:
// ------ Step1 引入 qiankun
import { registerMicroApps, start } from 'qiankun'; // 底層是基于single-spa
// ----- Step2 注冊子應用
registerMicroApps([{
name: 'm-vue',
entry: '//localhost:20000',
container: '#container',
activeRule: '/vue',
},
], {
beforeLoad: () => {
console.log('加載前')
},
beforeMount: () => {
console.log('掛在前')
},
afterMount: () => {
console.log('掛載后')
},
beforeUnmount: () => {
console.log('銷毀前')
},
afterUnmount: () => {
console.log('銷毀后')
},
})
// ----- Step3 啟動應用
start();
2. 子應用配置 vue 2.6.10
主應用基座只有一個主頁,現在我們需要接入微應用。
qiankun 內部通過 import-entry-html 加載微應用,要求微應用需要導出生命周期鉤子函數(見下圖)。
從上圖可以看出,qiankun 內部會校驗微應用的生命周期鉤子函數,如果微應用沒有導出這三個生命周期鉤子函數,則微應用會加載失敗。
如果我們使用了腳手架搭建微應用的話,我們可以通過 webpack 配置在入口文件處導出這三個生命周期鉤子函數。如果沒有使用腳手架的話,也可以直接在微應用的 window 上掛載這三個生命周期鉤子函數。
2.1 調整子應用 main.js 文件:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// Vue.config.productionTip = false
let instance = null
function render(props) {
instance = new Vue({
router,
render: h => h(App)
}).$mount('#app'); // 這里是掛載到自己的html中 基座會拿到這個掛載后的html 將其插入進去
}
if (window.__POWERED_BY_QIANKUN__) { // 動態添加publicPath
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
if (!window.__POWERED_BY_QIANKUN__) { // 默認獨立運行
render();
}
// 需要暴露接入協議
export async function bootstrap(props) {
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(props) {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
router = null;
}
說明:導出相應的生命周期鉤子函數。
微應用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 導出 bootstrap、mount、unmount 三個生命周期鉤子,以供主應用在適當的時機調用。
*擴展資源:
/**
* bootstrap 只會在微應用初始化的時候調用一次,下次微應用重新進入時會直接調用 mount 鉤子,不會再重復觸發 bootstrap。
* 通常我們可以在這里做一些全局變量的初始化,比如不會在 unmount 階段被銷毀的應用級別的緩存等。
*/
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
/**
* (重要)應用每次進入都會調用 mount 方法,通常我們在這里觸發應用的渲染方法
*/
export async function mount(props) {
console.log('[vue] props from main framework', props);
storeTest(props);
render(props);
}
/**
* 應用每次 切出/卸載 會調用的方法,通常在這里我們會卸載微應用的應用實例
*/
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
router = null;
}
/**
* 可選生命周期鉤子,僅使用 loadMicroApp 方式加載微應用時生效
*/
export async function update(props) {
console.log('update props', props);
}
2.2 新建 vue.config.js,配置如下
module.exports = {
devServer:{
port:10000,
headers:{
// 解決跨域
'Access-Control-Allow-Origin':'*'
}
},
configureWebpack:{
output:{
// 把子應用打包成 umd 庫格式
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
}
}
}
*基于 qiankun 微前端項目 (實踐代碼庫)
https://github.com/jiasx/mic-front-vue2.0
https://github.com/jiasx/mic-front-react