微前端學習(qiankun、singleSpa)
一、微前端的優勢
-
什么是微前端
-
微前端是一種多個團隊通過獨立發布功能的方式來共同構建現代化 web 應用的技術手段及方法策略
幾個核心價值:技術堆疊無關,獨立開發、獨立部署,增量升級,獨立運行時
-
-
特點
- 基于single-spa封裝,提供了更加開箱即用的 API,
- HTML Entry 接入方式,讓你接入微應用像使用 iframe 一樣簡單,
- 樣式隔離,確保微應用之間樣式互相不干擾,
- JS 沙箱,確保微應用之間 全域變數/事件 不沖突,
- 資源預加載,在瀏覽器空閑時間預加載未打開的微應用資源,加速微應用打開速度,
- umi 插件,提供了 @umijs/plugin-qiankun 供 umi 應用一鍵切換成微前端架構系統,
二、知識點擴展
-
樣式的隔離:
-
實作原理:影子根attachShadow() 開辟一個封閉的陰影盒子,避免其他樣式的參考
-
attachShadow學習:https://developer.mozilla.org/zh-CN/docs/Web/API/Element/attachShadow
-
<!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> <p>fur</p> <div id="shadow"></div> </div> <script> let shadow = document.getElementById('shadow') //可以省略 let shadowDOM = shadow.attachShadow({ mode: 'closed' }); //外界無法訪間closed let pElm = document.createElement('p'); pElm.innerHTML = "hello fur" let styleElm = document.createElement("style"); styleElm.textContent = `p{color:red}` // 添加樣式 shadowDOM.appendChild(styleElm) shadowDOM.appendChild(pElm) document.body.appendChild(pElm) // 添加到陰影外面,就無法控制其樣式 </script> </body> </html>
-
-
快照沙箱
-
作用:隔離js,防止全域污染
-
實作原理:默認初始化先回圈遍歷保存一份以前的資料,切換inactive(失活狀態下),先保存不同的資料(方便以后的資料恢復),然后恢復為以前的資料;切換active(激活狀態),把資料恢復為剛剛保存的不同的資料,
-
j簡單代碼示例:(淺拷貝記錄資料)
<!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> </body> <script> //快照沙箱dome class SnapshotSandbox { constructor() { this.proxy = window; // window屬性 this.modifyPropsMap = {}; //記錄在window上的修改 this.active(); } active() { //激活 this.windowSnapshot = {}; //拍照:保存全部window屬性 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 (this.windowSnapshot[prop] !== window[prop]) { this.modifyPropsMap[prop] = window[prop]; ///保存變化 window[prop] = this.windowSnapshot[prop] //失活:變回原來 } } } } } let sandbox = new SnapshotSandbox(); ((window) => { // 1、實體化sandbox時默認激活了一次 window.a = 1 window.b = 2 console.log(window.a) //1 sandbox.inactive() //失活 console.log(window.a) //undefined sandbox.active() //激活 console.log(window.a) //1 })(sandbox.proxy); //sandbox.proxy就是window </script> </html>
-
-
js沙箱
-
作用:隔離js,防止全域污染
-
實作原理:利用**new Proxy()**包裝代理不同的windon物件,代理物件之間會不干擾
-
-
Proxy()學習Script/Reference/Global_Objects/Proxy
-
代碼示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>proxy多應用沙箱實作示例(js沙箱)</title> </head> <body> </body> <script> //proxy的demo class ProxySandbox { constructor() { const rawWindow = window; const fakeWindow = {} const proxy = new Proxy(fakeWindow, { set(target, p, value) { target[p] = value; return true }, get(target, p) { return target[p] || rawWindow[p]; } }) this.proxy = proxy } } /** * 原理: * 1、利用Proxy實體化不同的window物件 * 2、不同的對之間互不干預, * */ let sandbox1 = new ProxySandbox(); let sandbox2 = new ProxySandbox(); window.a = 1; ((window) => { window.a = 'hello'; console.log(window.a) })(sandbox1.proxy); ((window) => { window.a = 'world'; console.log(window.a) })(sandbox2.proxy); </script> </html>
三、實作微前端的方法
-
iframe
- 特點:iframe 最大的特性就是提供了瀏覽器原生的硬隔離方案,不論是樣式隔離、js 隔離這類問題統統都能被完美解決,但他的最大問題也在于他的隔離性無法被突破,導致應用間背景關系無法被共享,
- 缺點:
- url 不同步,瀏覽器重繪 iframe url 狀態丟失、后退前進按鈕無法使用,
- UI 不同步,DOM 結構不共享,想象一下螢屏右下角 1/4 的 iframe 里來一個帶遮罩層的彈框,同時我們要求這個彈框要瀏覽器居中顯示,還要瀏覽器 resize 時自動居中…
- 全域背景關系完全隔離,記憶體變數不共享,iframe 內外系統的通信、資料同步等需求,主應用的 cookie 要透傳到根域名都不同的子應用中實作免登效果,
- 慢,每次子應用進入都是一次瀏覽器背景關系重建、資源重新加載的程序,
-
singleSpa
-
配置實作:
-
主應用配置
-
下載依賴:
npm i single-spa -savejs -
main.js 中配置
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import {registerApplication,start} from "single-spa" Vue.config.productionTip = false // 封裝:script標簽動態加載 async function loadScript(url){ return new Promise((resolve,reject)=>{ let script = document.createElement("script"); script.src = url; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }) } /** * singlespa 缺陷:不夠靈活,不能動態加載js檔案 * 樣式不隔離,沒有js沙箱機制 */ registerApplication("myApp", async ()=>{ console.log("加載子應用中..."); await loadScript("http://localhost:10000/js/chunk-vendors.js"); await loadScript("http://localhost:10000/js/app.js"); return window.sigleVue }, location=>location.pathname.startsWith("/vue"), // 匹配加載路由 ); // 開啟應用 start(); new Vue({ router, store, render: h => h(App) }).$mount('#app') -
掛載應用
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hI6y7kKl-1603706137263)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201026165342047.png)]
-
-
子應用配置(這里的子應用是vue,需要下載對應的依賴)
-
下載依賴:
npm i single-spa-vue -save -
main.js 中配置
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import singleSpaVue from 'single-spa-vue'; Vue.config.productionTip = false // 子應用配置 const appOptions = { el:"#vue", // 子應用掛載到父應用中id為vue的標簽中 router, render:h=>h(App) } // 分條件動態運行專案 // 作為子應用運行 if(window.singleSpaNavigate){ __webpack_public_path__ = "http://localhost:10000/"; } // 作為獨立專案運行 if(!window.singleSpaNavigate){ delete appOptions.el; new Vue(appOptions).$mount("#app") } // 創建劫持物件 const vueLifeCycle = singleSpaVue( { Vue, appOptions } ) /** * 協議接入 我定好了協議 父應用會呼叫這些方法 * 匯出變數:umd型別掛載windows * */ export const bootstrap = vueLifeCycle.bootstrap; export const mount = vueLifeCycle.mount; export const unmount = vueLifeCycle.unmount; // new Vue({ // router, // store, // render: h => h(App) // }).$mount('#app')
-
-
-
缺點:
-
不夠靈活,不能動態加載js檔案
-
樣式不隔離,沒有js沙箱機制
-
-
四、qiankun 微服務實作
-
什么是qiankun:
- 官網:https://qiankun.umijs.org/zh
- 優點:
- 幾乎包含所有構建微前端系統時所需要的基本能力,如 樣式隔離、js 沙箱、預加載等,
- 任意 js 框架均可使用,微應用接入像使用接入一個 iframe 系統一樣簡單,但實際不是 iframe,
-
依賴安裝:
$ yarn add qiankun # or npm i qiankun -S -
基本使用
import { loadMicroApp } from 'qiankun'; // 單個應用注冊加載 loadMicroApp({ name: 'reactApp', entry: '//localhost:7100', container: '#container', props: { slogan: 'Hello Qiankun' }, }); -
專案代碼示例:
- 說明:
- 主應用是:vue+element-ui
- 2個子級應用:vue、react
- 說明:
-
主應用vue+element-ui配置:
- 下載依賴:
npm i qiankun -S-
main.js 配置
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import {registerMicroApps,start} from 'qiankun' Vue.use(ElementUI); Vue.config.productionTip = false // 多個應用配置 const apps = [ { name: 'vueApp', //應用名 entry:'//localhost:10000',//默認會加載這個html決議里面的js動態的執行(子應用必須支持跨域)fetch container: '#vue', //容器名 activeRule: '/vue', //激活路徑 // props: {a:1} //父傳子參 },{ name: 'reactApp', entry:'//localhost:20000', container: '#react', activeRule: '/react' }] registerMicroApps(apps) //注冊應用,可以在此加入生命周期鉤子 start({ prefetch:false//取消預加載 }) new Vue({ router, store, render: h => h(App) }).$mount('#app') -
App.vue配置
<template> <div> <el-menu :router="true" mode="horizontal"> <!--基座中可以放自己的路由--> <el-menu-item index="/">Home</el-menu-item> <!--參考其他子應用--> <el-menu-item index="/vue">vue應用</el-menu-item> <el-menu-item index="/react">react應用</el-menu-item> </el-menu> <router-view></router-view> <!-- 掛載vue應用 --> <div id="vue"></div> <!-- 掛載react應用 --> <div id="react"></div> </div> </template> <style lang="less"></style> -
vue子級應用配置
-
main.js 配置
import Vue from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; Vue.config.productionTip = false; let instance = null; function render(props) { new Vue({ router, render: (h) => h(App), }).$mount("#app"); } //這里是掛載到自己的html中,基座會拿到這個掛載后的html將其插入進去 // 父級應用加載 if (window.__POWERED_BY_QIANKUN__) { //動態添加路徑 __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } // 獨立運行時 if (!window.__POWERED_BY_QIANKUN__) { //獨立運行 render(); } //子組件協議 export async function bootstrap(props) {} export async function mount(props) { render(props); //裝載 } export async function unmount(props) { if(instance){ instance.$destroy(); //卸載 } } new Vue({ router, store, render: (h) => h(App), }).$mount("#app"); -
vue.config.js 配置
module.exports = { configureWebpack: { output: { library: "vueApp", // 打包輸出檔案名 libraryTarget: "umd", // 打包模塊型別是umd }, devServer: { port: 10000, // 父應用是通過瀏覽器fetch方式加載打包的lib的,需要配置請求頭跨域 headers: { "Access-Control-Allow-Origin": "*", }, }, }, }; /** * umd 模塊型別:掛載變數到windows中 * eg:windows.vueApp.bootstrap\mount\unmount * */ -
react子級應用配置
- src/index.js 配置
import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; function render() { ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); } if (window.__POWERED_BY_QIANKUN__) { //動態添加路徑 __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } if (!window.__POWERED_BY_QIANKUN__) { //獨立運行 render(); } render(); //子組件協議 export async function bootstrap(props) {} export async function mount(props) { render(props); //裝載 } export async function unmount(props) { ReactDOM.unmountComponentAtNode(document.getElementById("root")); //卸載 }-
根目錄創建組態檔config-overrides.js
module.exports = { webpack:(config)=>{ config.output.library = 'reactApp'; config.output.libraryTarget = 'umd'; config.output.publicPath = "http://localhost:20000/"; return config; }, devServer: (configFunction)=>{ return function (proxy,allowedHost){ const config = configFunction(proxy,allowedHost); // config.port = "20000"; config.headers ={ "Access-Control-Allow-Origin":"*" // 配置跨域 } return config } } } -
根目錄創建組態檔.env
PORT=20000 WDS_SOCKET_PORT=20000 -
配置package.json
-
下載變線依賴:
npm i -save react-app-rewired// 修改package.json檔案(注意json檔案這行注釋要去掉) "scripts":{ "start":"react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" }
-
-
-
注意:__webpack_public_path__有可能會報錯
-
效果圖:

五、qiankun 微前端通訊方式
- 未完成,敬請期待,,,,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/194601.html
標籤:其他
上一篇:博主聯系方式匯總(非誠勿擾)
