一、什么是微前端
微前端就是將不同的功能按照不同的維度拆分成多個子應(yīng)用。通過主應(yīng)用來加載這些子應(yīng)用愿伴。微前端的核心在于拆,拆完后在合电湘!
二公般、為什么使用微前端
1.不同團隊間開發(fā)同一個應(yīng)用技術(shù)棧不同
2.希望每個團隊都可以獨立開發(fā),獨立部署
3.項目中還需要老的應(yīng)用代碼
我們可以將一個應(yīng)用劃分成若干個子應(yīng)用胡桨,將子應(yīng)用打包成一個個的 lib 。當路徑切換 時加載不同的子應(yīng)用瞬雹。這樣每個子應(yīng)用都是獨立的昧谊,技術(shù)棧也不用做限制了!從而解決了前端協(xié)同開發(fā)問題酗捌。
三呢诬、qiankun框架
文檔地址:https://qiankun.umijs.org/zh
2018 年 Single-SPA 誕生了, single-spa 是一個用于前端微服務(wù)化的 JavaScript 前端解決方案 ( 本身沒有處理樣式隔離胖缤, js 執(zhí)行隔離 ) 實現(xiàn)了路由劫持和應(yīng)用加載尚镰。
2019 年 qiankun 基于 Single-SPA, 提供了更加開箱即用的 API ( single-spa + sandbox + import-html-entry ) 做到了,技術(shù)棧無關(guān)哪廓、并且接入簡單(像 i frame 一樣簡單)狗唉。
四、qiankun框架實例
這里我們打算建立三個項目進行實操涡真,一個Vue項目充當主應(yīng)用分俯,另一個Vue和React應(yīng)用充當子應(yīng)用
1、創(chuàng)建三個應(yīng)用
1)創(chuàng)建基座
vue create qiankun-base
2)創(chuàng)建子應(yīng)用1
vue create qiankun-vue
3)創(chuàng)建子應(yīng)用2
cnpm install -g create-react-app
create-react-app qiankun-react
三個項目
基座:qiankun-base 子應(yīng)用:qiankun-vue哆料、qiankun-react
2缸剪、項目配置(主要)
1)基座qiankun-base配置
項目創(chuàng)建好后我們首先進行主應(yīng)用qiankun-base的配置,進入man.js文件進行配置东亦, 在main.js中加入以下代碼,要注意的是杏节,entry這項配置是我們兩個子項目的域名和端口,我們必須確保兩字子項目運行在這兩個端口上面典阵,container就是我們的容器名奋渔,就是我們子應(yīng)用掛載的節(jié)點,相當于Vue項目里面的app節(jié)點萄喳,activeRule就是我們的激活路徑卒稳,根據(jù)路徑來顯示不同的子應(yīng)用。
1.引入qiankun插件
yarn add qiankun 或者 npm i qiankun -S
2.main.js配置
// 引入qiankun
import { registerMicroApps, start } from 'qiankun';
const apps = [
{
name: 'vueApp', // 應(yīng)用的名字
entry: '//localhost:8081', // 默認會加載這個html 解析里面的js 動態(tài)的執(zhí)行 (子應(yīng)用必須支持跨域)fetch
container: '#vue', // 容器名(此項目頁面中定義的容器id他巨,用于把對應(yīng)的子應(yīng)用放到此容器中)
activeRule: '/vue', // 激活的路徑
props: { a: 1 } // 傳遞的值(可選)
},
{
name: 'reactApp',
entry: '//localhost:20000', // 默認會加載這個html 解析里面的js 動態(tài)的執(zhí)行 (子應(yīng)用必須支持跨域)fetch
container: '#react',
activeRule: '/react',
}
]
registerMicroApps(apps); // 注冊應(yīng)用
start({
prefetch: false // 取消預(yù)加載
});// 開啟
3.配置完之后我們?nèi)サ絨iankun-base的app.vue文件進行主應(yīng)用的頁面編寫充坑,這里我安裝了element-ui來進行頁面美化
npm i element-ui -S
在main.js中引入element-ui
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
4.修改app.vue的組件代碼
<template>
<div id="app">
<el-menu :router="true" mode="horizontal">
<!--基座中可以放自己的路由-->
<el-menu-item index="/">Home</el-menu-item>
<el-menu-item index="/about">About</el-menu-item>
<!--引用其他子應(yīng)用-->
<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>
<div id="vue"></div>
<div id="react"></div>
</div>
</template>
5.router.js代碼
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
// base: process.env.BASE_URL,
base: '',
routes
})
export default router
2)子應(yīng)用qiankun-vue配置
1.main.js配置
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// Vue.config.productionTip = false
let instance = null
function render(props) {
instance = new Vue({
router,
render: h => h(App)
}).$mount('#qkApp'); // 這里是掛載到自己的html中 基座會拿到這個掛載后的html 將其插入進去
}
if (window.__POWERED_BY_QIANKUN__) { // 動態(tài)添加publicPath
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
if (!window.__POWERED_BY_QIANKUN__) { // 默認獨立運行
render();
}
// 父應(yīng)用加載子應(yīng)用减江,子應(yīng)用必須暴露三個接口:bootstrap、mount捻爷、unmount
// 子組件的協(xié)議就ok了
export async function bootstrap(props) {
};
export async function mount(props) {
render(props)
}
export async function unmount(props) {
instance.$destroy();
}
2.router.js配置
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: '/vue',
routes
})
export default router
3.Vue.config.js配置
在子應(yīng)用的根目錄下面新建一個Vue.config.js文件
module.exports = {
lintOnSave: false, // 關(guān)閉eslint檢測
devServer: {
port: 8080,//這里的端口是必須和父應(yīng)用配置的子應(yīng)用端口一致
headers: {
//因為qiankun內(nèi)部請求都是fetch來請求資源辈灼,所以子應(yīng)用必須允許跨域
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
output: {
//資源打包路徑
library: 'vueApp',
libraryTarget: 'umd'
}
}
}
3)子應(yīng)用qiankun-react配置
1.src目錄下index.js文件
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__){
render();
}
export async function bootstrap(){
}
export async function mount() {
render()
}
export async function unmount(){
ReactDOM.unmountComponentAtNode( document.getElementById('root')); // 卸載節(jié)點
}
2.config-overrides.js配置
先引入react-app-rewired,在修改package.json啟動命令
npm install react-app-rewired
修改package.json啟動命令
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
再進行dev以及打包的配置也榄,根目錄下創(chuàng)建config-overrides.js
module.exports = {
webpack: (config) => {
config.output.library = 'reactApp';
config.output.libraryTarget = 'umd';
config.output.publicPath = 'http://localhost:20000/'; // 此應(yīng)用自己的端口號
return config;
},
devServer: (configFunction) => {
return function (proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
config.headers = {
"Access-Control-Allow-Origin": '*'
}
return config
}
}
}
3巡莹、注意點
1)如何在主應(yīng)用的某個路由頁面加載微應(yīng)用
react + react-router 技術(shù)棧的主應(yīng)用:只需要讓子應(yīng)用的 activeRule 包含主應(yīng)用的這個路由即可。
vue + vue-router 技術(shù)棧的主應(yīng)用:
例如:主應(yīng)用需要在login頁面登錄甜紫,登錄成功后跳轉(zhuǎn)到main后臺管理界面降宅,在main管理界面下可以顯示子應(yīng)用。
修改主應(yīng)用router.js:
// 如果這個路由有其他子路由囚霸,需要另外注冊一個路由腰根,任然使用這個組件即可。
// 本案例就是有子路由拓型,所以需要才后面重新定義main頁面的路由
const routes = [
{
path: '/',
name: 'Login',
component: Login
},
{
path: '/main',
name: 'Main',
component: Main,
children: [
{
path: '/home',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
},
{
path: '/main/*',
name: 'Main',
component: Main,
}
]
修改主應(yīng)用main.js的文件:
// 子應(yīng)用的 activeRule 需要包含主應(yīng)用的這個路由 path
const apps = [
{
name: 'vueApp', // 應(yīng)用的名字
entry: '//localhost:8081', // 默認會加載這個html 解析里面的js 動態(tài)的執(zhí)行 (子應(yīng)用必須支持跨域)fetch
container: '#vue', // 容器名
activeRule: '/main/vue', // 激活的路徑
props: { a: 1 }
},
{
name: 'reactApp',
entry: '//localhost:20000', // 默認會加載這個html 解析里面的js 動態(tài)的執(zhí)行 (子應(yīng)用必須支持跨域)fetch
container: '#react',
activeRule: '/main/react',
}
]
registerMicroApps(apps); // 注冊應(yīng)用
修改主應(yīng)用main.vue頁面代碼:
// 在 Main.vue 這個組件的 mounted 周期調(diào)用 start 函數(shù)额嘿,注意不要重復(fù)調(diào)用。
<template>
<div class="main-content">
<el-menu :router="true" mode="horizontal">
<!--基座中可以放自己的路由-->
<el-menu-item index="/home">Home</el-menu-item>
<el-menu-item index="/about">About</el-menu-item>
<!--引用其他子應(yīng)用-->
<el-menu-item index="/main/vue">vue應(yīng)用</el-menu-item>
<el-menu-item index="/main/react">react應(yīng)用</el-menu-item>
</el-menu>
<router-view></router-view>
<div id="vue"></div>
<div id="react"></div>
</div>
</template>
<script>
import { start } from "qiankun";
export default {
name: "Main",
mounted() {
if (!window.qiankunStarted) {
window.qiankunStarted = true;
start();
}
},
};
</script>