微前端qiankun項目實踐
導語
最近在做微前端的項目 , 過程中真是踩了不少坑 , 在有限的資料中不斷試錯 , 默默無語兩行淚 哈哈. 在此次將踩坑部分都記錄下來, 讓更多的人少走點彎路 , 此項目使用 螞蟻金服qiankun 為基礎作為開發 . 話不多說 開講 !!!
那什么是 qiankun 呢
qiankun 是一個基于 single-spa 的微前端實現庫,旨在幫助大家能更簡單、無痛的構建一個生產可用微前端架構系統。
什么是微前端
微前端架構具備以下幾個核心價值:
- 技術棧無關
主框架不限制接入應用的技術棧,微應用具備完全自主權
- 獨立開發、獨立部署
微應用倉庫獨立,前后端可獨立開發,部署完成后主框架自動完成同步更新
- 增量升級
在面對各種復雜場景時,我們通常很難對一個已經存在的系統做全量的技術棧升級或重構,而微前端是一種非常好的實施漸進式重構的手段和策略
- 獨立運行時
每個微應用之間狀態隔離,運行時狀態不共享
摘自 qiankun官方文檔
主應用配置
此次項目 主應用與 子應用均為 vue ,
下載 qiankun
- npm install qiankun
在主應用中注冊微應用
// 導入乾坤函數
- import {
- registerMicroApps,
- setDefaultMountApp,
- start
- } from "qiankun";
封裝 render 方法
此方法在main.js 中要初始調用一次, 主要用來掛載主應用 , 之后子應用分別依次調用 ,所以故作判斷. 傳入的參數分別為 子應用 的 HTML 和 加載狀態 content 字段 我們用 vuex 存儲 起來,方便使用
- let app = null;
- function render({ appContent, loading }) {
- if (!app) {
- app = new Vue({
- router,
- store,
- render: h => h(App),
- }).$mount('#app');
- } else {
- store.commit('microApp/changeCenter', appContent);
- store.commit('microApp/changeLoading', loading);
- }
- }
微應用注冊
下文中的apps 可以為獲取后數據 , 注冊微應用 本文案例比較簡單,方便大家理解 ,
在注冊自應用的參數 ** container 與 render** 踩坑比較多,下邊會著重講解.
- function genActiveRule(routerPrefix) {
- return location => location.pathname.startsWith(routerPrefix);
- }
- //傳遞給子應用的數據
- let msg = {
- 
- data:'修煉愛情的辛酸,學會放好以前的渴望'
- }
- let apps = [
- {
- name: 'linjunjie',
- entry: '//localhost:215', // 改成自己子應用的端口號
- container:'#subView', //節點 id // 沙盒模式
- // render:render, // 普通模式
- activeRule: genActiveRule('/star'),
- props:msg
- }
- ]
- //注冊的子應用 參數為數組
- registerMicroApps(apps,{
- beforeLoad: [
- app => {
- console.log(app)
- console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
- },
- ],
- beforeMount: [
- app => {
- console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
- },
- ],
- afterUnmount: [
- app => {
- console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
- },
- ],
- });
- setDefaultMountApp('/star/linjunjie')
- //開啟沙盒模式
- start({
- sandbox :{strictStyleIsolation: true}
- })
當微應用信息注冊完之后,一旦瀏覽器的 url 發生變化,便會自動觸發 qiankun 的匹配邏輯,所有 activeRule 規則匹配上的微應用就會被插入到指定的 container 中,同時依次調用微應用暴露出的生命周期鉤子。
主應用為子應用準備的 展示元素
- <template>
- <div id="app">
- <div id="nav">
- <!--//主應用 為子應用的跳轉dom-->
- <div @click="onChangePage('/star/linjunjie')" >林俊杰</div>
- <div @click="onChangePage('/star/zhangyixin')" >張藝興</div>
- </div>
- <!--//用來展子應用的 內容區-->
- <div id="subView" class="sub-content-wrap" v-html="content"></div>
- </div>
- </template>
- <script>
- import { mapState } from 'vuex';
- export default{
- data(){
- return {
- }
- },
- computed:{
- //獲取子應用HTML 數據
- ...mapState('microApp', ['content']),
- ...mapState('microApp', ['mircoAppLoading']),
- },
- methods:{
- //定義跳轉方法
- onChangePage(url){
- console.log(url)
- this.routerGo(url, '我喜愛的男明星')
- },
- routerGo(href = '/', title = null, stateObj = {}) {
- window.history.pushState(stateObj, title, href);
- },
- }
- }
- </script>
子應用配置
關于子應用的配置相對較簡單 , 不需要額外下載qiankun 主要將生命鉤子 導出即可
導出響應的生命鉤子
導出 bootstrap、mount、unmount 三個生命周期鉤子,以供主應用在適當的時機調用。注意,實例化路由時,判斷當運行在qiankun環境時,路由要添加前綴,前綴與主應用注冊子應用函數genActiveRule("/subdemo")內的參數一致
'star' 值需要與主應用的值對應 genActiveRule("/star") 中的值需要商定好 主應用與微應用都要使用
如果 new VueRouter 不在main.js 中 配置 ,請將此配置移動到 main.js 方便管理
- import routes from './router' //將路由信息導出方便使用
- let router = null;
- let instance = null;
- function render(props = {}) {
- const { container } = props;
- router = new VueRouter({
- base: window.__POWERED_BY_QIANKUN__ ? '/star' : '/',
- 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) {
- //props 包含主應用傳遞的參數 也包括為子應用 創建的節點信息
- console.log(props)
- render(props);
- }
- export async function unmount() {
- instance.$destroy();
- instance = null;
- router = null;
- }
配置微應用的打包工具
除了代碼中暴露出相應的生命周期鉤子之外,為了讓主應用能正確識別微應用暴露出來的一些信息,微應用的打包工具需要在vue.config.js 中 增加如下配置:
- const packageName = require('./package.json').name;
- module.exports = {
- output: {
- library: `${packageName}-[name]`,
- libraryTarget: 'umd',
- jsonpFunction: `webpackJsonp_${packageName}`,
- },
- };
子應用判斷
子應用中新建 publicPath.js 在main.js 引入
- if (window.__POWERED_BY_QIANKUN__) {
- //處理資源
- __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
- }
處理 資源加載問題
配置 vue.config.js
- module.exports = {
- publicPath:`//localhost:${port}`,
- }
vue.config.js 完整配置
- const path = require('path');
- const packageName = require('./package').name;
- function resolve(dir) {
- return path.join(__dirname, dir);
- }
- const port = 7101; // dev port
- module.exports = {
- publicPath:`//localhost:${port}`,
- outputDir: 'dist',
- assetsDir: 'static',
- filenameHashing: true,
- devServer: {
- // host: '0.0.0.0',
- hot: true,
- historyApiFallback: true,//添加 重點
- port,
- overlay: {
- warnings: false,
- errors: true,
- },
- headers: {
- 'Access-Control-Allow-Origin': '*',
- },
- },
- configureWebpack: {
- resolve: {
- alias: {
- '@': resolve('src'),
- },
- },
- output: {
- library: `${packageName}-[name]`,
- libraryTarget: 'umd',
- jsonpFunction: `webpackJsonp_${packageName}`,
- },
- },
- };
踩坑記錄
當前頁面為子應用時, 刷新頁面404
以下方式均為主應用配置
- 方式一 刪除 mode 配置項
- mode: 'history', // 將此配置代碼刪除
- 方式二 配置404 頁面
如果沒有注釋掉mode: 'history' 此參數 將404 頁面重新導向 home首頁
- {
- path: '*',
- name: 'indexNotFound',
- component: resolve => require(['@/components/home'], resolve),
- children: HomeChild,
- },
子應用 樣式隔離 開始沙箱模式 遇到的問題
- 主應用配置sandbox :{strictStyleIsolation: true}渲染模式由 render 模式 改為 containercontainer:'#subView', 此時 子應用的 掛載 dom 為 <div id="subView"> </div> 謹記主 container :#+id
- 子應用配置 上文有提到 主要代碼 截取
- instance = new Vue({
- router,
- store,
- render: h => h(App),
- }).$mount(container ? container.querySelector('#app') : '#app'); //重點
遇到的問題: 開啟沙箱模式,如果是 采用 render 模式會報錯 ,故選擇container 模式
效果圖
寫到這里,項目已經構建完成了 讓我們來看看效果吧
這里是完整代碼 方便大家學習 代碼github地址:https://github.com/zxh1307/qiankun-vue
項目問題
- 為啥我項目啟動后看不到子應用的效果
將master 主應用 main.js 中 注冊的 子應用的端口號 改成自己項目的端口號即可
結語
開發中還有其他坑 忘記記錄了, 千萬記得項目部署子應用資源跨域的問題 , 需要Nginx配置跨域問題