本文介紹 Vue 項(xiàng)目如何實(shí)現(xiàn)前端微服務(wù)
一、前言
什么是微前端
Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. -- Micro Frontends
微前端是一種多個(gè)團(tuán)隊(duì)通過(guò)獨(dú)立發(fā)布功能的方式來(lái)共同構(gòu)建現(xiàn)代化 web 應(yīng)用的技術(shù)手段及方法策略。
更多關(guān)于微前端的相關(guān)介紹,推薦大家可以去看這幾篇文章:
qiankun
qiankun 是螞蟻金服開(kāi)源的一套完整的微前端解決方案。具體描述可查看 文檔 和 Github。
下面將通過(guò)一個(gè)微服務(wù)Demo 介紹 Vue 項(xiàng)目如何接入 qiankun,代碼地址:micro-front-vue
二、配置主應(yīng)用
- 使用 vue cli 快速創(chuàng)建主應(yīng)用;
- 安裝 qiankun
$ yarn add qiankun # 或者 npm i qiankun -S
- 調(diào)整主應(yīng)用
main.js
文件:具體如下:
import Vue from "vue"
import App from "./App.vue"
import router from "./router"
import { registerMicroApps, setDefaultMountApp, start } from "qiankun"
Vue.config.productionTip = false
let app = null;
/**
* 渲染函數(shù)
* appContent 子應(yīng)用html內(nèi)容
* loading 子應(yīng)用加載效果,可選
*/
function render({ appContent, loading } = {}) {
if (!app) {
app = new Vue({
el: "#container",
router,
data() {
return {
content: appContent,
loading
};
},
render(h) {
return h(App, {
props: {
content: this.content,
loading: this.loading
}
});
}
});
} else {
app.content = appContent;
app.loading = loading;
}
}
/**
* 路由監(jiān)聽(tīng)
* @param {*} routerPrefix 前綴
*/
function genActiveRule(routerPrefix) {
return location => location.pathname.startsWith(routerPrefix);
}
function initApp() {
render({ appContent: '', loading: true });
}
initApp();
// 傳入子應(yīng)用的數(shù)據(jù)
let msg = {
data: {
auth: false
},
fns: [
{
name: "_LOGIN",
_LOGIN(data) {
console.log(`父應(yīng)用返回信息${data}`);
}
}
]
};
// 注冊(cè)子應(yīng)用
registerMicroApps(
[
{
name: "sub-app-1",
entry: "http://localhost:8091",
render,
activeRule: genActiveRule("/app1"),
props: msg
},
{
name: "sub-app-2",
entry: "http://localhost:8092",
render,
activeRule: genActiveRule("/app2"),
}
],
{
beforeLoad: [
app => {
console.log("before load", app);
}
], // 掛載前回調(diào)
beforeMount: [
app => {
console.log("before mount", app);
}
], // 掛載后回調(diào)
afterUnmount: [
app => {
console.log("after unload", app);
}
] // 卸載后回調(diào)
}
);
// 設(shè)置默認(rèn)子應(yīng)用,與 genActiveRule中的參數(shù)保持一致
setDefaultMountApp("/app1");
// 啟動(dòng)
start();
- 修改主應(yīng)用 index.html 中綁定的
id
,需與el
綁定 dom 為一致; - 調(diào)整 App.vue 文件,增加渲染子應(yīng)用的盒子:
<template>
<div id="main-root">
<!-- loading -->
<div v-if="loading">loading</div>
<!-- 子應(yīng)用盒子 -->
<div id="root-view" class="app-view-box" v-html="content"></div>
</div>
</template>
<script>
export default {
name: "App",
props: {
loading: Boolean,
content: String
}
};
</script>
- 創(chuàng)建 vue.config.js 文件,設(shè)置
port
:
module.exports = {
devServer: {
port: 8090
}
}
三、配置子應(yīng)用
- 在主應(yīng)用同一級(jí)目錄下快速創(chuàng)建子應(yīng)用,子應(yīng)用無(wú)需安裝 qiankun
- 配置子應(yīng)用 main.js:
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
import './public-path';
Vue.config.productionTip = false;
let router = null;
let instance = null;
function render() {
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/app1' : '/',
mode: 'history',
routes,
});
instance = new Vue({
router,
render: h => h(App),
}).$mount('#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('vue app bootstraped');
}
export async function mount(props) {
console.log('props from main app', props);
render();
}
export async function unmount() {
instance.$destroy();
instance = null;
router = null;
}
- 配置 vue.config.js
const path = require('path');
const { name } = require('./package');
function resolve(dir) {
return path.join(__dirname, dir);
}
const port = 8091; // dev port
module.exports = {
/**
* You will need to set publicPath if you plan to deploy your site under a sub path,
* for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
* then publicPath should be set to "/bar/".
* In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath
*/
outputDir: 'dist',
assetsDir: 'static',
filenameHashing: true,
// tweak internal webpack configuration.
// see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
devServer: {
// host: '0.0.0.0',
hot: true,
disableHostCheck: true,
port,
overlay: {
warnings: false,
errors: true,
},
headers: {
'Access-Control-Allow-Origin': '*',
},
},
// 自定義webpack配置
configureWebpack: {
resolve: {
alias: {
'@': resolve('src'),
},
},
output: {
// 把子應(yīng)用打包成 umd 庫(kù)格式
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
其中有個(gè)需要注意的點(diǎn):
- 子應(yīng)用必須支持跨域:由于 qiankun 是通過(guò) fetch 去獲取子應(yīng)用的引入的靜態(tài)資源的,所以必須要求這些靜態(tài)資源支持跨域;
- 使用 webpack 靜態(tài) publicPath 配置:可以通過(guò)兩種方式設(shè)置,一種是直接在 mian.js 中引入 public-path.js 文件,一種是在開(kāi)發(fā)環(huán)境直接修改 vue.config.js:
{
output: {
publicPath: `//localhost:${port}`;
}
}
public-path.js 內(nèi)容如下:
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
至此,Vue 項(xiàng)目的前端微服務(wù)已經(jīng)簡(jiǎn)單完成了。
但是在實(shí)際的開(kāi)發(fā)過(guò)程中,并非如此簡(jiǎn)單,同時(shí)還存在應(yīng)用間跳轉(zhuǎn)、應(yīng)用間通信等問(wèn)題。