首先,官網(wǎng)祭天保佑~
微前端特性:
- 子應(yīng)用可自主選擇技術(shù)棧
- 各應(yīng)用單獨(dú)部署舷丹、互不依賴
然后像鸡,開始整。
主應(yīng)用
- 創(chuàng)建微應(yīng)用容器
<!-- app.vue -->
<template>
<div id="appBase">
<el-menu :router="true" mode="horizontal">
<!--主應(yīng)用內(nèi)容-->
<el-menu-item index="/">Home</el-menu-item>
<el-menu-item index="/about">About</el-menu-item>
<!--子應(yīng)用內(nèi)容-->
<el-menu-item index="/vue">vue應(yīng)用</el-menu-item>
<el-menu-item index="/react">react應(yīng)用</el-menu-item>
</el-menu>
<router-view></router-view>
<!-- 與 main.js 里面配置的 container 對應(yīng)-->
<div id="vue"></div>
<div id="react"></div>
</div>
</template>
<script>
export default {
name: 'appBase',
components: {
}
}
</script>
注冊微應(yīng)用
啟動
qiankun
席爽;
import { registerMicroApps, start } from 'qiankun';
const apps = [
{
name: 'vueApp', // 應(yīng)用的名字
entry: '//localhost:8081', // 默認(rèn)會加載這個html 解析里面的js 動態(tài)的執(zhí)行 (子應(yīng)用必須支持跨域)fetch
container: '#vue', // 容器名(主應(yīng)用頁面中定義的容器id,用于把對應(yīng)的子應(yīng)用放到此容器中)
activeRule: '/vue', // 激活的路徑
props: { name: 'vueApp' } // 傳遞的值(可選)
},
{
name: 'reactApp',
entry: '//localhost:3000',
container: '#react',
activeRule: '/react',
}
]
registerMicroApps(apps); // 注冊應(yīng)用
start({
prefetch: false // 取消預(yù)加載
});
當(dāng)微應(yīng)用信息注冊完之后,一旦瀏覽器的 url
發(fā)生變化卵惦,便會自動觸發(fā)qiankun
的匹配邏輯,所有 activeRule
規(guī)則匹配上的微應(yīng)用就會被插入到指定的 container
中瓦戚,同時依次調(diào)用微應(yīng)用暴露出的生命周期鉤子
子應(yīng)用
- 建議使用
history
模式的路由沮尿,需要設(shè)置路由base
,值和它的activeRule
一樣,當(dāng)然也可以選擇hash
畜疾,詳細(xì)區(qū)別見入門教程 - 路由模式如何選擇 - 在入口文件
main.js
中動態(tài)添加運(yùn)行時的publicPath
赴邻,修改并導(dǎo)出三個生命周期函數(shù)。
注:運(yùn)行時的publicPath
和構(gòu)建時的publicPath
不同啡捶。 - 修改
webpack
打包姥敛,允許開發(fā)環(huán)境跨域和umd
打包。
vueApp
// main.js
import Vue from 'vue'
import VueRouter from "vue-router";
import App from './App.vue'
import routes from './routes'
Vue.use(VueRouter);
Vue.config.productionTip = false;
let instance = null;
let router = null;
/**
* 渲染函數(shù)
* 兩種情況:主應(yīng)用生命周期鉤子中運(yùn)行 / 微應(yīng)用單獨(dú)啟動時運(yùn)行
*/
function render(props) {
// 在 render 中創(chuàng)建 VueRouter瞎暑,可以保證在卸載微應(yīng)用時彤敛,移除 location 事件監(jiān)聽,防止事件污染
router = new VueRouter({
// 運(yùn)行在主應(yīng)用中時了赌,添加路由命名空間 /vue
base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/",
mode: "history",
routes,
});
const el = props?.containter || '#appVue'
// 掛載應(yīng)用
instance = new Vue({
router,
render: (h) => h(App),
}).$mount(el);
}
if (window.__POWERED_BY_QIANKUN__) { // 動態(tài)添加publicPath
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
if (!window.__POWERED_BY_QIANKUN__) { // 默認(rèn)獨(dú)立運(yùn)行
render();
}
// 父應(yīng)用加載子應(yīng)用墨榄,子應(yīng)用必須暴露三個接口:bootstrap、mount勿她、unmount
// 子組件的協(xié)議就ok了
export async function bootstrap(props) {
console.log('bootstrap', props)
}
export async function mount(props) {
render(props)
}
export async function unmount(props) {
console.log('unmount', props)
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
router = null;
}
此處踩了好幾個坑渠概,最大坑是這行代碼:
const el = props?.containter || '#appVue'
// 然后掛載
原因是子應(yīng)用加載不出頁面,并且 qiankun
拋出微應(yīng)用加載后容器 DOM 節(jié)點(diǎn)不存在了嫂拴,然后檢查元素發(fā)現(xiàn)該子應(yīng)用的 id
是另一串:<div id="__qiankun_microapp_wrapper_for_vue_app__" data-name="vueApp" data-version="2.6.3">
播揪,由此可推出是掛載時發(fā)生的報(bào)錯。最坑的是當(dāng)自己傻傻花了好久解決的報(bào)錯筒狠,在我事后查看官網(wǎng)時發(fā)現(xiàn)了常見問題中已經(jīng)給出了答案...
ps: 常常都是解決了問題才發(fā)現(xiàn)官網(wǎng)上有寫猪狈,只有我這樣的鐵憨憨才有這樣的苦惱嗎...
// vue.config.js
module.exports = {
devServer: {
port: 8081, //這里的端口是必須和主應(yīng)用配置的子應(yīng)用端口一致
headers: {
//因?yàn)閝iankun內(nèi)部請求都是fetch來請求資源,所以子應(yīng)用必須允許跨域
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
output: {
//資源打包路徑
library: 'vueApp',
libraryTarget: 'umd'
}
}
}
reactApp
-
index.js
文件
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
function render(props) {
console.log('render', props)
const container = props?.container;
ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}
if (window.__POWERED_BY_QIANKUN__) {
console.log('QIANKUN', window['reactApp'])
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
if(!window.__POWERED_BY_QIANKUN__){
console.log('N_QIANKUN')
render();
}
export async function bootstrap(){
}
export async function mount(props) {
console.log('mount', props)
render(props)
}
export async function unmount(props) {
const container = props?.container;
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
- 修改
webpack
配置
官網(wǎng)例子中安裝插件@rescripts/cli
辩恼,還可以選擇其他的插件雇庙,于是這里安裝的是react-app-rewired
npm install react-app-rewired
// 修改package.json
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
- "eject": "react-scripts eject"
+ "eject": "react-app-rewired eject",
- 創(chuàng)建打包配置
config-overrides.js
module.exports = {
webpack: (config) => {
config.output.library = 'reactApp';
config.output.libraryTarget = 'umd';
config.output.publicPath = 'http://localhost:3000/'; // 此應(yīng)用自己的端口號
return config;
},
devServer: (configFunction) => {
return function (proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
config.headers = {
"Access-Control-Allow-Origin": '*'
}
return config
}
}
}
react
的相關(guān)配置還算順利
代碼:demo