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