微前端(Micro-Frontends)是一種類似于微服務(wù)的架構(gòu),它將微服務(wù)的理念應(yīng)用于瀏覽器端,即將 Web 應(yīng)用分解成一些更小、更簡(jiǎn)單的小模塊,而在用戶側(cè)看起來還是一個(gè)內(nèi)聚的產(chǎn)品。各個(gè)前端應(yīng)用還可以獨(dú)立運(yùn)行、獨(dú)立開發(fā)、獨(dú)立部署。微前端不是單純的前端框架或者工具,而是一套架構(gòu)體系。
qiankun是一個(gè)基于 single-spa 的微前端實(shí)現(xiàn)庫(kù),旨在幫助大家能更簡(jiǎn)單、無(wú)痛的構(gòu)建一個(gè)生產(chǎn)可用微前端架構(gòu)系統(tǒng)。
安裝
$ yarn add qiankun # 或者 npm i qiankun -S
創(chuàng)建基座項(xiàng)目,并在基座項(xiàng)目中引入qiankun
這里我們基座項(xiàng)目使用的是vue項(xiàng)目,版本為vue@3.0.0,具體創(chuàng)建項(xiàng)目教程請(qǐng)參考創(chuàng)建vue,項(xiàng)目目錄結(jié)構(gòu)如下:
|-- qiankun
? ? |-- .DS_Store
? ? |-- .browserslistrc
? ? |-- .eslintrc.js
? ? |-- .gitignore
? ? |-- README.md
? ? |-- babel.config.js
? ? |-- package-lock.json
? ? |-- package.json
? ? |-- public
? ? |? |-- favicon.ico
? ? |? |-- index.html
? ? |-- src
? ? ? ? |-- .DS_Store
? ? ? ? |-- App.vue
? ? ? ? |-- main.js
? ? ? ? |-- assets
? ? ? ? |? |-- logo.png
? ? ? ? |-- components
? ? ? ? |? |-- HelloWorld.vue
? ? ? ? |-- router
? ? ? ? |? |-- index.js
? ? ? ? |-- views
? ? ? ? ? ? |-- About.vue
? ? ? ? ? ? |-- Home.vue
創(chuàng)建子應(yīng)用
創(chuàng)建兩個(gè)子應(yīng)用,分別為Vue項(xiàng)目和React項(xiàng)目:
vue目錄結(jié)構(gòu):
|-- .DS_Store
? ? |-- .browserslistrc
? ? |-- .eslintrc.js
? ? |-- .gitignore
? ? |-- README.md
? ? |-- babel.config.js
? ? |-- package-lock.json
? ? |-- package.json
? ? |-- vue.config.js
? ? |-- public
? ? |? |-- favicon.ico
? ? |? |-- index.html
? ? |-- src
? ? ? ? |-- App.vue
? ? ? ? |-- main.js
? ? ? ? |-- assets
? ? ? ? |? |-- logo.png
? ? ? ? |-- components
? ? ? ? |? |-- HelloWorld.vue
? ? ? ? |-- router
? ? ? ? |? |-- index.js
? ? ? ? |-- views
? ? ? ? ? ? |-- About.vue
? ? ? ? ? ? |-- Home.vue
react目錄結(jié)構(gòu):
react項(xiàng)目創(chuàng)建請(qǐng)參考創(chuàng)建 react
|-- .DS_Store
? ? |-- .eslintrc.js
? ? |-- .gitignore
? ? |-- README.md
? ? |-- config-overrides.js
? ? |-- package-lock.json
? ? |-- package.json
? ? |-- yarn.lock
? ? |-- public
? ? |? |-- favicon.ico
? ? |? |-- index.html
? ? |? |-- logo192.png
? ? |? |-- logo512.png
? ? |? |-- manifest.json
? ? |? |-- robots.txt
? ? |-- src
? ? ? ? |-- App.css
? ? ? ? |-- App.js
? ? ? ? |-- App.test.js
? ? ? ? |-- index.css
? ? ? ? |-- index.js
? ? ? ? |-- logo.svg
? ? ? ? |-- reportWebVitals.js
? ? ? ? |-- setupTests.js
基座中引入
在基座項(xiàng)目中引入qiankun,修改main.js文件
import { registerMicroApps, start } from "qiankun";
const apps = [
? {
? ? name: 'vueApp',
? ? entry: '//localhost:8081', // 默認(rèn)會(huì)加載這個(gè)html,解析里面的js,動(dòng)態(tài)執(zhí)行(子應(yīng)用必須支持跨域)
? ? container: '#vue',
? ? activeRule: '/vue',
? ? props: { a: 1 }
? },
? {
? ? name: 'reactApp',
? ? entry: "http://localhost:3000",
? ? container: "#react",
? ? activeRule: '/react'
? }
]
registerMicroApps(apps);
start({
? // sandbox: { experimentalStyleIsolation: true } //沙箱默認(rèn)開啟
})
createApp(App).use(ElementPlus).use(router).mount('#appBase')
registerMicroApps的相關(guān)入?yún)⒄?qǐng)參考入?yún)?,其中?/p>
container是針對(duì)微應(yīng)用掛載的節(jié)點(diǎn),
entry微應(yīng)用的入口,微應(yīng)用的訪問地址;
activeRule微應(yīng)用的激活規(guī)則,當(dāng)配置為字符串時(shí)會(huì)直接跟 url 中的路徑部分做前綴匹配,匹配成功表明當(dāng)前應(yīng)用會(huì)被激活。所以我們需要在基座項(xiàng)目中修改App.vue,在該項(xiàng)目中增加微應(yīng)用的掛載節(jié)點(diǎn),修改如下:
<template>
? <div id="nav">
? ? <el-menu class="el-menu-demo" :router="true" mode="horizontal">
? ? ? <el-menu-item index="/">Home</el-menu-item>
? ? ? <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 />
? ? <div id="vue"></div>
? ? <div id="react"></div>
? </div>
</template>
子應(yīng)用文件修改
我們需要根據(jù)協(xié)議,在子應(yīng)用的入口文件中導(dǎo)出三個(gè)生命周期函數(shù),以供主應(yīng)用在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用:
/**
* 只會(huì)在微應(yīng)用初始化的時(shí)候調(diào)用一次,下次微應(yīng)用重新進(jìn)入時(shí)會(huì)直接調(diào)用 mount 鉤子,不會(huì)再重復(fù)觸發(fā) bootstrap
*/
export async function bootstrap() { }
/**
* 應(yīng)用每次進(jìn)入都會(huì)調(diào)用 mount 方法,通常我們?cè)谶@里觸發(fā)應(yīng)用的渲染方法
* @param {*} props
*/
export async function mount(props) {
? console.log(props);
}
/**
* 應(yīng)用每次 切出/卸載 會(huì)調(diào)用的方法,通常在這里我們會(huì)卸載微應(yīng)用的應(yīng)用實(shí)例
* @param {*} props
*/
export async function unmount(props) {
? console.log(app);
}
為了能讓子應(yīng)用也可獨(dú)立運(yùn)行,我們可以根據(jù)qiankun在全局注冊(cè)一個(gè)變量window.__POWERED_BY_QIANKUN__,來區(qū)分子應(yīng)用是否運(yùn)行在qiankun中:
vue
// vue
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
let app = null
function render(props) {
? ? app = createApp(App).use(router) //掛載到自己的html.基座會(huì)拿到掛載后的html,直接插入
? ? app.mount('#app')
}
if (window.__POWERED_BY_QIANKUN__) { // 動(dòng)態(tài)添加publicPath
? ? __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
if (!window.__POWERED_BY_QIANKUN__) { //
? ? render()
}
// 子組件的協(xié)議
/**
* 只會(huì)在微應(yīng)用初始化的時(shí)候調(diào)用一次,下次微應(yīng)用重新進(jìn)入時(shí)會(huì)直接調(diào)用 mount 鉤子,不會(huì)再重復(fù)觸發(fā) bootstrap
*/
export async function bootstrap() { }
/**
* 應(yīng)用每次進(jìn)入都會(huì)調(diào)用 mount 方法,通常我們?cè)谶@里觸發(fā)應(yīng)用的渲染方法
* @param {*} props
*/
export async function mount(props) {
? ? console.log(props);
? ? render(props)
}
/**
* 應(yīng)用每次 切出/卸載 會(huì)調(diào)用的方法,通常在這里我們會(huì)卸載微應(yīng)用的應(yīng)用實(shí)例
* @param {*} props
*/
export async function unmount(props) {
? ? console.log(app);
? ? app.unmount() // 卸載
}
react
// react
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__) { // 動(dòng)態(tài)添加publicPath
? __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
if (!window.__POWERED_BY_QIANKUN__) {
? render()
}
export async function bootstrap() { };
export async function mount() {
? render()
};
export async function unmount() {
? ReactDOM.unmountComponentAtNode(document.getElementById('root'))
};
子應(yīng)用配置修改
vue
//vue vue.config.js
module.exports = {
? ? devServer: {
? ? ? ? headers: {
? ? ? ? ? ? 'Access-Control-Allow-Origin': '*' //支持跨域
? ? ? ? }
? ? },
? ? configureWebpack: {
? ? ? ? output: {
? ? ? ? ? ? library: 'vueApp',
? ? ? ? ? ? libraryTarget: 'umd',
? ? ? ? }
? ? },
? ? publicPath: 'http://localhost:8081/'
}
react
module.exports = {
? ? webpack: (config) => {
? ? ? ? config.output.library = 'reactApp';
? ? ? ? config.output.libraryTarget = 'umd';
? ? ? ? config.output.publicPath = 'http://localhost:3000/' // 靜態(tài)資源路徑
? ? ? ? return config
? ? },
? ? devServer: (configFunction) => {
? ? ? ? return function (proxy, allowedHost) {
? ? ? ? ? ? const config = configFunction(proxy, allowedHost);
? ? ? ? ? ? config.headers = {
? ? ? ? ? ? ? ? "Access-Control-Allow-Origin": "*"
? ? ? ? ? ? }
? ? ? ? ? ? return config
? ? ? ? }
? ? }
}
運(yùn)行結(jié)果
子應(yīng)用獨(dú)立運(yùn)行結(jié)果展示

基座運(yùn)行結(jié)果


