概念
微前端:微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
qiankun存在一个主应用及一个或多个微应用,主应用和微应用不限技术栈
快速上手(官方文档)
- 主应用中安装
$ yarn add qiankun # or npm i qiankun -S
- 主应用中注册微应用并启动
注意:启动主应用的同时也需要启动微应用,否则点击跳转微应用会报错
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'reactApp',
entry: '//localhost:3000',
container: '#container',
activeRule: '/app-react',
props:{},//主应用需要传递给微应用的数据
},
{
name: 'vueApp',
entry: '//localhost:8080',
container: '#container',
activeRule: '/app-vue',
},
{
name: 'angularApp',
entry: '//localhost:4200',
container: '#container',
activeRule: '/app-angular',
},
]);
// 启动 qiankun
start();
//设置默认进入的微应用
setDefaultMountApp('/react')
应用间传值(参考文档)
1、qiankun内部提供了initGlobalState方法用于注册MIcroAppStateActions实例用于通信,存在以下三个方法:
setGlobalState:设置globalState,设置新值时,内部会做浅检查,如果监测到globalState的值发生变化,会通知所有的观察者
onGlobalState:注册观察者模式
offGlobalStateChange:取消观察者模式
- 利用url路由参数共享
主应用
<Link to='/vue?id=1'>vue3微应用</Link>
react子应用
window.location获取
vue3子应用
import { useRouter, useRoute } from 'vue-router';
通过useRoute获取
vue2子应用
可以通过window.location.search去获取
- 利用props传值
主应用中使用props进行传值
registerMicroApps([{
name: 'm-vue',
entry: '//localhost:6061/',
container: '#container',
activeRule: '/vue',
loader,
//增加props进行传值
props:{
something:'这是registerMicroApps传递的props'
}
}])
子应用中在src/main.js中将props挂载在根节点上面
function render(props) {
instance = new Vue({
router,
render: h => h(App),
data(){
return{
//挂载在根节点上面
parentRouter:props.something
}
}
}).$mount('#app');
}
应用之间跳转
利用history.pushState(null,'',url)不刷新页面,更改页面的url进行跳转
缺点:要求各个应用路由系统都是用history模式
利用NPM脚本运行多条命令,简化操作
- 在最外层文件夹下下载依赖
yarn add yarn-run-all
- 利用yarn-run-all下载所有项目依赖
1、修改package.json文件夹下的script配置
"scripts": {
"install": "npm-run-all -s install:*",
"install:my-react":"cd my-react && yarn",
"install:my-react1":"cd my-react1 && yarn",
"install:my-vue":"cd my-vue && yarn",
"install:my-vue2":"cd my-vue2 && yarn"
}
2、执行yarn install安装所有项目依赖
资源共享
利用import maps对import做一个映射处理
示例:
项目结构:主应用React + 微应用React1 + 微应用VUE2(vue版本2) + 微应用VUE3(vue版本3)+ 微应用HTML + 微应用vite项目
主应用修改
1、主应用src文件夹中创建registerApp.js
import { registerMicroApps,start} from 'qiankun';
const loader = (loading) =>{
console.log(loading);
}
registerMicroApps([{
name: 'm-vue',
entry: '//localhost:6061/',//端口号必须与对应项目的端口号一致
container: '#container',//对应页面上的容器
activeRule: '/vue',
loader
},{
name: 'm-vue2',
entry: '//localhost:6062/',
container: '#container',
activeRule: '/vue2',
loader
},{
name: 'm-react',
entry: '//localhost:6063/',
container: '#container',
activeRule: '/react',
loader
},{
name: 'm-html',
entry: '//localhost:7104/',
container: '#container',
activeRule: '/html',
loader
}],{
beforeLoad: () =>{
console.log('加载前');
},
beforeMount: () =>{
console.log('加载后');
}
})
start();
2、在src/index.js中引入registerApp.js
import './registerApp'
- 在src/App.js中加入容器
import './App.css';
import { BrowserRouter as Router, Link } from 'react-router-dom'
function App() {
return (
<div className="App">
<Router>
<Link to='/'>
<h1>
我是主应用
</h1>
</Link>
<Link to='/vue'>vue3微应用</Link>
<br />
<Link to='/vue2'>vue2微应用</Link>
<br />
<Link to='/react'>react微应用</Link>
</Router>
<div id='container'></div>//容器
</div>
);
}
export default App;
react微应用修改
修改src/index.js
import React from 'react';
import { createRoot } from 'react-dom/client'
import './index.css';
import App from './App';
function render(props = {}) {
const { container } = props;
const root = createRoot(container ? container.querySelector('#root') : document.querySelector('#root'))
root.render(<App />);
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('[react16] react app bootstraped');
}
export async function mount(props) {
console.log('[react16] props from main framework', props);
render(props);
}
export async function unmount(props) {
const { container } = props;
const root = createRoot(container ? container.querySelector('#root') : document.querySelector('#root'))
root.unmount(<App />);
}
- config-overrides.js配置
2.1引入react-app-rewired(作用:覆盖react脚手架配置)
npm install react-app-rewired -D
2.2修改package.json启动命令
"scripts": {
"start": "set PORT=6063 && react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
2.3修改dev以及打包配置,在根目录下创建config-overrides.js,文件配置如下:
module.exports = {
webpack: config => {
config.output.library = "m-react";
config.output.libraryTarget = "umd";
config.output.publicPath = "http://localhost:6063/"; // 此应用自己的端口号
return config;
},
devServer: configFunction => {
return function(proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
config.port = "6063";
config.headers = {
"Access-Control-Allow-Origin": "*"
};
return config;
};
}
}
vue3微应用修改
- 在src目录中新增public-path.js(判断当前环境是否是在微前端的环境下)
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
- 在src/router.js文件中的顶部引入public-path.js
import './public-path'
import HelloWorld from './components/HelloWorld.vue'
import Home from './Home.vue'
const routes = [
{
path: '/home',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: HelloWorld
},
]
export default routes
- 入口文件main.js修改,为了避免根id #app与其他的DOM冲突,需要限制查找范围
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue'
import routes from './router'
let history;
let router;
let app;
function render(props = {}) {
history = createWebHistory('/vue');
router = createRouter({
history,
routes
});
app = createApp(App);
let { container } = props;
app.use(router).mount(container ? container.querySelector('#app') : "#app")
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
console.log('[vue] props from main framework', props);
render(props);
}
export async function unmount() {
console.log('[vue] vue app unmount');
history = null;
app = null;
router = null;
}
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false,
devServer:{
port: 6061,
headers:{
// 允许跨域
"Access-Control-Allow-Origin": "*"
}
},
configureWebpack:{
output: {
library: 'm-vue',
libraryTarget:'umd'
}
}
})
vue2微应用修改
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false,
devServer:{
port: 6061,
headers:{
// 允许跨域
"Access-Control-Allow-Origin": "*"
}
},
configureWebpack:{
output: {
library: 'm-vue2',
libraryTarget:'umd'
}
}
})
- 修改main.js (这里与vue3不同)
import Vue from 'vue'
import App from './App.vue'
import router from './router'
let instance = null
function render(props) {
instance = new Vue({
router,
render: h => h(App)
}).$mount('#app'); // 这里是挂载到自己的html中 基座会拿到这个挂载后的html 将其插入进去
}
if (window.__POWERED_BY_QIANKUN__) { // 动态添加publicPath
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
if (!window.__POWERED_BY_QIANKUN__) { // 默认独立运行
render();
}
// 父应用加载子应用,子应用必须暴露三个接口:bootstrap、mount、unmount
// 子组件的协议就ok了
export async function bootstrap(props) {
};
export async function mount(props) {
render(props)
}
export async function unmount(props) {
instance.$destroy();
}
HTML微应用修改
- 新增入口文件 entry.js
const render = ($) => {
// 渲染前do something
return Promise.resolve();
};
((global) => {
// 对应微应用的名称
global['m-html'] = {
bootstrap: () => {
console.log('purehtml bootstrap');
return Promise.resolve();
},
mount: () => {
console.log('purehtml mount');
return render($);
},
unmount: () => {
console.log('purehtml unmount');
return Promise.resolve();
},
};
})(window);
- 在html中引入entry.js
注意:
这里js需要放在最后加载,放在最后引入或者将入口js标记为entry
entry.js基于jq,需要在entry前引入jq
<script src="./entry.js"></script>//放最后引入
或者
<script src="./entry.js" entry></script>
- 新建package.json文件
{
"name": "my-html",
"version": "1.0.0",
"description": "",
"main": "index.html",
"scripts": {
"start": "cross-env PORT=7104 http-server . --cors",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"devDependencies": {
"cross-env": "^7.0.2",
"http-server": "^0.12.1"
}
}
- 运行
yarn //下载依赖
yarn start //运行