微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。
一:微前端架构的核心价值
- 技术栈无关
- 独立开发、独立部署
- 增量升级
- 独立运行时
二:快速上手
我们分为主应用跟微应用。之前的想法是想直接通过 vue-cli
脚手架生成两个项目的基本配置。但做到一半的时候,想用 webpack5
的模块联邦,vue-cli
配起来太费劲(一直有各种报错,猜测应该是 vue-cli4.x
里面的 webpack
版本是4.x,跟 webpack5
有的地方不兼容)。干脆直接使用 webpack5
自己搭建一套配置。
webpack 的配置
先用 webpack5
来搭建一个基本的配置让项目运行起来。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {VueLoaderPlugin} = require('vue-loader/dist/index');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
entry: path.resolve(__dirname, './src/main.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].js'
},
module: {
rules: [
{
test: /.vue$/,
use: [
'vue-loader'
]
},
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
devServer: {
// webpack4 的 contentBase 改为下面
static: {
directory: path.join(__dirname, './dist')
},
port: 8080
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './index.html'),
filename: 'index.html',
title: 'qiankun-root-app'
}),
new VueLoaderPlugin(),
new CleanWebpackPlugin()
]
};
复制代码
qiankun 配置
主应用:
- 安装
qiankun
yarn add qiankun # 或者 npm i qiankun -S
- 在主应用中注册微应用并启动
// main.js
import {registerMicroApps, start} from 'qiankun';
registerMicroApps([
{
// 微应用的名称
name: 'vue-app',
// 微应用的入口
entry: 'http://localhost:8081/',
// 微应用的容器节点的选择器
container: '#vue-app',
// 微应用的激活规则
activeRule: '/vue'
}
]);
start();
复制代码
在主应用中要有一个匹配激活规则的路由以及节点;
<template>
<div>
<router-link to="/">首页</router-link>
<router-link to="/vue">vue应用</router-link>
</div>
<router-view></router-view>
<div id="vue-app"></div>
</template>
复制代码
子应用:
- 导出相应的生命周期钩子(
bootstrap
、mount
、unmount
)
// main.js
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
render(props);
}
export async function unmount() {
instance.unmount();
instance._container.innerHTML = '';
instance = null;
router = null;
history.destroy();
}
复制代码
- 配置微应用的打包工具
// webpack.config.js
const packageName = require('./package.json').name;
module.exports = {
output: {
library: `${packageName}`,
libraryTarget: 'umd',
// webpack5 jsonpFunction 改成了 chunkLoadingGlobal
chunkLoadingGlobal: `webpackJsonp_${packageName}`,
},
};
复制代码
三:常见问题
1. 路由
- (1):主应用和微应用最好都是
history(createWebHistory)
模式;
主应用:
// router/index.js
import {createRouter, createWebHistory} from 'vue-router';
const Home = () => import('../src/components/Home.vue');
const routes = [
{path: '/', component: Home}
];
const router = createRouter({
history: createWebHistory('/'),
routes
});
export default router;
复制代码
微应用:
路由文件导出 routes
,在入口文件实例化路由。
// main.js
import {createRouter, createWebHistory} from 'vue-router';
import routes from '../router/index'
let instance = null;
let router = null;
let history = null;
function render(props = {}) {
const {container} = props;
// histroy模式的路由需要设置base
history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/vue' : '/');
router = createRouter({
history,
routes
});
instance = createApp(App);
instance.use(router);
instance.mount(container ? container.querySelector('#app1') : '#app1');
}
复制代码
- (2):跨域问题
子应用需要配置允许跨域:
// webpack.config.js
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
}
},
复制代码
2. 复用公用依赖库
使用 webpack
的 externals
实现。
主应用:
// webpack.config.js
externals: {
'vue': 'Vue',
'element-plus': 'ElementPlus'
}
复制代码
// index.html
<link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css">
<script src="//unpkg.com/vue@next"></script>
<script src="//unpkg.com/element-plus"></script>
复制代码
微应用: index.html
里面的地址要添加 ignore
,但是添加了 ignore
之后报错;
目前没找到解决方式;貌似是因为 vue-router4
有问题;
// webpack.config.js
externals: {
'vue': 'Vue',
'element-plus': 'ElementPlus'
}
复制代码
// index.html
<link ignore rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css">
<script ignore src="//unpkg.com/vue@next"></script>
<script ignore src="//unpkg.com/element-plus"></script>
复制代码
// main.js
import ElementPlus from 'element-plus';
instance.use(ElementPlus);
复制代码
3. 复用公共组件跟方法
- (1):注册微应用的时候直接使用
props
属性传过去;
主应用:
// main.js
import HeaderComponent from '../src/common/header.vue'
import FooterComponent from '../src/common/footer.vue'
window.commonComponents = {
HeaderComponent,
FooterComponent
};
let commonComponents = window.commonComponents || {};
registerMicroApps([
{
...
props: {
data: {
commonComponents
}
}
}
]);
// 共享组件必须开启多实例
start({ singular: false })
复制代码
微应用:
// main.js
export async function mount(props) {
window.commonComponents = props.data.commonComponents;
render(props);
}
复制代码
// Home.vue
components: {
HeaderComponent: window.__POWERED_BY_QIANKUN__ ? window.commonComponents.HeaderComponent : '',
FooterComponent: window.__POWERED_BY_QIANKUN__ ? window.commonComponents.FooterComponent : ''
}
复制代码
- (2):使用
webpack5
模块联邦
主应用: 把公用的组件跟方法放入一个独立的js并导出;
// commonExport.js
import HeaderComponent from './src/common/header.vue'
import FooterComponent from './src/common/footer.vue'
import utils from './src/utils/index.js'
export {
HeaderComponent,
FooterComponent,
utils
}
复制代码
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
new ModuleFederationPlugin({
name: 'root_app',
filename: "root_app.js",
exposes: {
'./commonExport': './commonExport'
}
})
复制代码
微应用:
// webpack.config.js
plugins: [
new ModuleFederationPlugin({
name: 'vue_app',
filename: "vue_app.js",
remotes: {
rootApp: 'root_app@http://localhost:8080/root_app.js'
}
})
]
复制代码
// Home.vue
import {onMounted} from 'vue';
// 使用公用组件跟方法
import {HeaderComponent, utils} from 'rootApp/commonExport'
export default {
name: 'Home',
components: {
HeaderComponent
},
setup() {
onMounted(() => {
utils.commonFn();
})
}
}
复制代码
4. 微应用加载的资源会 404
微应用新建 public-path.js
,在 main.js
中导入;
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
复制代码
import './public-path';
复制代码
5. 微应用如何独立运行?
// main.js
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
复制代码
6. 微应用路径下刷新后 404
通常是因为你使用的是 browser
模式的路由,这种路由模式的开启需要服务端配合才行。开发环境需要修改 devServer
// webpack.config.js
devServer: {
// 解决微应用刷新报错404
proxy: {
'/vue/': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/vue': ''
}
}
},
historyApiFallback: true
},
复制代码