qiankun 是什么
qiankun 是基于 single-spa 的微前端實(shí)現(xiàn)庫湃鹊,可以幫助大家更快速的構(gòu)建一個(gè)前端微架構(gòu)體系桃移。
微前端是什么
官方給出的概念是:微前端是一種多個(gè)團(tuán)隊(duì)通過獨(dú)立發(fā)布功能的方式來共同構(gòu)建現(xiàn)代化 web 應(yīng)用的技術(shù)手段及方法策略君旦。簡(jiǎn)單理解就是一個(gè)大型項(xiàng)目中,有一個(gè)主應(yīng)用土榴,N 個(gè)子應(yīng)用家坎,各個(gè)應(yīng)用之間可以獨(dú)立開發(fā)疮茄,獨(dú)立運(yùn)行,獨(dú)立部署员咽。
為什么要用微前端
我們應(yīng)該都有這樣的經(jīng)歷毒涧,一個(gè)項(xiàng)目一直在更新迭代,參與的人也越來越多骏融,代碼也越來越多链嘀,導(dǎo)致項(xiàng)目越來越不好管理,打包速度越來越慢档玻,打包體積越來越大怀泊,部署不方便。尤其是在有一個(gè)很小的需求要更新時(shí)误趴,我們需要重新打包部署整個(gè)項(xiàng)目霹琼,就很頭大。所以我們就需要微前端凉当,把一個(gè)單一的應(yīng)用枣申,轉(zhuǎn)換為多個(gè)可以獨(dú)立開發(fā)、測(cè)試看杭、部署的小應(yīng)用忠藤。但用戶是無感知的,依舊認(rèn)為是一個(gè)產(chǎn)品楼雹。
開始搭建微前端的主應(yīng)用(基座)
安裝 qiankun
yarn add qiankun # 或者 npm i qiankun -S
在主應(yīng)用中注冊(cè)微應(yīng)用
我們?cè)?src 文件夾下新建一個(gè) qiankun/index.ts 模孩,用來放我們的 qiankun 的配置信息
// src/qiankun/index.ts
import {registerMicroApps, start} from 'qiankun';
registerMicroApps([
{
name: 'qiankun-demo-b', // 子應(yīng)用的注冊(cè)名稱,即子應(yīng)用 package.json 中的 name
entry: '//localhost:5174', // 子應(yīng)用的啟動(dòng)地址
container: '#app-b', // 承載子應(yīng)用的容器贮缅,在主應(yīng)用中榨咐,有一個(gè) ID 為 app-b 的 div,用來承載子應(yīng)用
activeRule: '/about', // 點(diǎn)擊哪個(gè)路由會(huì)進(jìn)入子應(yīng)用
},
/*
* 比如谴供,我在 app.vue 文件中注冊(cè)了兩個(gè)路由
* <template>
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
<RouterView />
</template>
* 那么這個(gè) activeRule: '/about' 對(duì)應(yīng)的就是 <RouterLink to="/about">About</RouterLink> 這個(gè)路由
* 之后块茁,我在 about.vue 這個(gè)文件中,添加一個(gè)承載子應(yīng)用的 div,那么這個(gè) container: '#app-b' 對(duì)應(yīng)的就是 <div id="app-b"></div> 這個(gè) div
* <template>
<div class="about">
<h1>This is an about page 111</h1>
<div id="app-b"></div>
</div>
</template>
* 有多個(gè)子應(yīng)用数焊,可以按這種方式來配置多個(gè)
* */
{
name: 'qiankun-demo-d',
entry: '//localhost:8080',
container: '#app-d',
activeRule: '/car',
}
]);
start();
在 main.ts 中引入
// 引入qiankun
import './qiankun/index'
微應(yīng)用
Vue3 + Vite
我們先來看 Vue 微應(yīng)用永淌,Vue 微應(yīng)用有一個(gè)點(diǎn)需要注意的就是 Vite , qiankun 與 Vite 不能一起使用佩耳,所以需要額外安裝一個(gè)插件仰禀,以下是使用 Vite 時(shí) qiankun 的配置
npm install vite-plugin-qiankun
微應(yīng)用需要在自己的入口 js 導(dǎo)出 bootstrap
、mount
蚕愤、unmount
三個(gè)生命周期鉤子答恶,以供主應(yīng)用在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用。
我們來修改子應(yīng)用里的 main.ts 文件
// main.ts
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import './assets/main.css'
import {renderWithQiankun, qiankunWindow} from 'vite-plugin-qiankun/dist/helper'
let instance: any = null
function render(props: any = {}) {
const { container } = props
instance = createApp(App)
instance.use(router)
instance?.mount(container ? container.querySelector('#app') : '#app')
console.log('開始加載相關(guān)內(nèi)容')
}
/*
* bootstrap :
* 只會(huì)在微應(yīng)用初始化的時(shí)候調(diào)用一次萍诱,下次微應(yīng)用重新進(jìn)入時(shí)會(huì)直接調(diào)用 mount 鉤子悬嗓,不會(huì)再重復(fù)觸發(fā) bootstrap。
* 通常我們可以在這里做一些全局變量的初始化裕坊,比如不會(huì)在 unmount 階段被銷毀的應(yīng)用級(jí)別的緩存等包竹。
* mount :
* 應(yīng)用每次進(jìn)入都會(huì)調(diào)用 mount 方法,通常我們?cè)谶@里觸發(fā)應(yīng)用的渲染方法
* unmount :
* 應(yīng)用每次 切出/卸載 會(huì)調(diào)用的方法籍凝,通常在這里我們會(huì)卸載微應(yīng)用的應(yīng)用實(shí)例
* update :
* 可選生命周期鉤子周瞎,僅使用 loadMicroApp 方式加載微應(yīng)用時(shí)生效
* */
renderWithQiankun({
mount(props: any) {
// 應(yīng)用每次進(jìn)入都會(huì)調(diào)用 mount 方法,所以我們?cè)谶@里初始化一些內(nèi)容
render(props)
},
bootstrap() {
console.log('微應(yīng)用初始化的時(shí)候調(diào)用一次')
},
update() {
console.log('update')
},
unmount(props: any) {
console.log('unmount:應(yīng)用每次 切出/卸載 會(huì)調(diào)用的方法', props)
instance.unmount()
instance._container.innerHTML = ''
instance = null
}
})
/*
* 通過 qiankunWindow.__POWERED_BY_QIANKUN__ 判斷是不是 qiankun 渲染的饵蒂,如果不是 qiankun 渲染的声诸,需要調(diào)用以下 render 方法來初始化一些內(nèi)容
* */
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
console.log('并不是qiankun渲染')
render()
}
打包配置修改,需要配置微應(yīng)用的名字與端口號(hào)退盯,使其與主應(yīng)用配置的微應(yīng)用保持一致
// vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'
export default defineConfig({
plugins: [
vue(),
qiankun('vue-app', { // 微應(yīng)用名字彼乌,與主應(yīng)用注冊(cè)的微應(yīng)用名字保持一致
useDevMode: true
})
],
server: {
port: 5174 // 微應(yīng)用端口號(hào),與主應(yīng)用注冊(cè)的微應(yīng)用保持一致
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
Vue3 不使用 Vite
這里我們就不需要安裝任何插件了渊迁,只需在微應(yīng)用的入口文件導(dǎo)出 bootstrap
慰照、mount
、unmount
三個(gè)生命周期鉤子琉朽,以供主應(yīng)用在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用就可以了毒租。
同樣還是修改 main.ts 文件,這里的思路和 Vue3 + Vite 的是一樣的箱叁,只是寫法略微不同而已
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
let app = null;
function render(props = {}) {
const { container } = props;
app = createApp(App);
app
.use(store)
.use(router)
.mount(container ? container.querySelector("#app") : "#app");
}
// 獨(dú)立運(yùn)行時(shí)
// 這里和 Vue3 + Vite 不太一樣墅垮,是通過 window.__POWERED_BY_QIANKUN__ 來判斷是否是 qiankun 渲染的
if (!window.__POWERED_BY_QIANKUN__) {
console.log('獨(dú)立運(yùn)行')
render();
}
export async function bootstrap() {
console.log('微應(yīng)用初始化的時(shí)候調(diào)用一次');
}
export async function mount(props) {
console.log("mount", props);
render(props);
}
export async function unmount() {
app.unmount();
}
打包配置修改,需要配置微應(yīng)用的名字與端口號(hào)蝌蹂,使其與主應(yīng)用配置的保持一致
// vue.config.ts
const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package');
module.exports = defineConfig({
transpileDependencies: true,
publicPath: "http://localhost:8080/",
devServer: {
port: 8080, // 微應(yīng)用端口號(hào)噩斟,與主應(yīng)用注冊(cè)的微應(yīng)用保持一致
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微應(yīng)用打包成 umd 庫格式
},
},
})
Vue2
Vue2 和 Vue3 中不使用 Vite 是一樣的曹锨,唯一的區(qū)別就是 Vue2 和 Vue3 的語法不太一樣孤个,直接上代碼
// main.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
import store from './store';
Vue.config.productionTip = false;
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');
}
// 獨(dú)立運(yùn)行時(shí)
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;
}
打包配置修改
// vue.config.js
const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package');
module.exports = defineConfig({
transpileDependencies: true,
publicPath: "http://localhost:8080/",
devServer: {
port: 8080,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微應(yīng)用打包成 umd 庫格式
},
},
})
這里只介紹了子應(yīng)用為 Vue 的情況,當(dāng)然我們也可以創(chuàng)建一個(gè) React 或者 Angular 的子應(yīng)用沛简,其配置方法 qiankun 中都有示例