qiankun使用
single-spa的缺点:需要借助systemjs加载应用,没有预加载功能
创建基座项目
bash
npx create-react-app substrate
npm install react-router-dom qiankun
npm run start
substrate/src/index.js
diff
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
+import './registerApps.js'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
substrate/src/App.js
javascript
import React from 'react'
import {BrowserRouter, Link} from 'react-router-dom'
import { useEffect } from 'react';
import { loadMicroApp} from 'qiankun'
function App() {
const containerRef = React.createRef();
useEffect(()=>{
loadMicroApp({
name:'m-static',
entry: 'http://localhost:30000',
container:containerRef.current
})
})
// keep-alive 可以实现动态的加载
return (
<div className="App">
<BrowserRouter>
<Link to="/react">React应用</Link>
<Link to="/vue">Vue应用</Link>
</BrowserRouter>
<div ref={containerRef}></div>
<div id='container'></div>
</div>
);
}
export default App;
substrate/src/registerApps.js
javascript
import { registerMicroApps, start,initGlobalState } from 'qiankun';
const loader = (loading) => {
console.log('加载状态', loading)
}
const actions = initGlobalState({
name:'wtm',
age:30
})
actions.onGlobalStateChange((newVal,oldVal)=>{
console.log('parent',newVal,oldVal)
})
registerMicroApps([
{
name: 'reactApp',
entry: '//localhost:40000', // 默认react启动的入口是10000端口
activeRule: '/react', // 当路径是 /react的时候启动
container: '#container', // 应用挂载的位置
loader,
props: { a: 1, util: {} }
},
{
name: 'vueApp',
entry: '//localhost:20000', // 默认react启动的入口是10000端口
activeRule: '/vue', // 当路径是 /react的时候启动
container: '#container', // 应用挂载的位置
loader,
props: { a: 1, util: {} }
}
], {
beforeLoad() {
console.log('before load')
},
beforeMount() {
console.log('before mount')
},
afterMount() {
console.log('after mount')
},
beforeUnmount() {
console.log('before unmount')
},
afterUnmount() {
console.log('after unmount')
}
})
start({
sandbox:{
// 实现了动态样式表
// css-module,scoped 可以再打包的时候生成一个选择器的名字 增加属性 来进行隔离
// BEM
// CSS in js
// shadowDOM 严格的隔离
// strictStyleIsolation:true,
//experimentalStyleIsolation:true // 缺点 就是子应用中的dom元素如果挂在到了外层,会导致样式不生效
}
})
substrate/package.json
json
{
...
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
...
}
创建vue子项目
bash
vue create m-vue
Vue CLI v5.0.8
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router
? Choose a version of Vue.js that you want to start the project with 3.x
? Use history mode for router? (Requires proper server setup for index fallback in production)
Yes
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
m-vue/vue.config.js
javascript
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
devServer:{
port:20000,
headers:{
'Access-Control-Allow-Origin':"*"
}
},
configureWebpack:{
output:{
libraryTarget:'umd',
library:'m-vue'
}
}
})
m-vue/src/main.js
javascript
import './public-path.js'
import { createApp } from 'vue'
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
import App from './App.vue'
import routes from './router'
let app;
let history;
let router;
function render(props) {
app = createApp(App)
history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/vue' : '/')
router = createRouter({
history,
routes
})
app.use(router)
const container = props.container
app.mount(container ? container.querySelector('#app'):document.getElementById('app'))
}
if(!window.__POWERED_BY_QIANKUN__){
render({})
}
export async function bootstrap() {
console.log('vue bootsrap')
}
export async function mount(props) {
render(props)
}
export async function unmount() {
app.unmount()
history.destroy();
app = null;
router = null
}
m-vue/src/router/index.js
javascript
import HomeView from '../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
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/AboutView.vue')
}
]
export default routes
m-vue/src/public-path.js
javascript
if(window.__POWERED_BY_QIANKUN__){
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
创建react子项目
bash
npx create-react-app m-react
npm install @rescripts/cli --force 重写webpack配置
m-react/src/index.js
javascript
import './public-path.js'
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
let root;
function render(props) {
const container = props.container
root = ReactDOM.createRoot(container ? container.querySelector('#root') : document.getElementById('root'));
root.render(
<App />
);
}
// qiankun 提供了一些标识,用于表示当前应用是否在父应用中被引入过
if (!window.__POWERED_BY_QIANKUN__) {
render({}); // 独立运行调用render方法
}
// qiankun 要求应用暴露的方式需要时umd格式
export async function bootstrap(props) {
console.log(props)
}
export async function mount(props) {
props.onGlobalStateChange((newVal, oldVal) => {
console.log('child', newVal, oldVal)
})
props.setGlobalState({ name: 'wtm2' })
// 外层基座的容器叫container容器
render(props); // 父应用挂在的时候会传递props, props 有挂载点
}
export async function unmount(props) {
root.unmount();
}
m-react/src/public-path.js
javascript
if(window.__POWERED_BY_QIANKUN__){
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
m-react/.env
bash
PORT=40000
WDS_SOCKET_PORT=40000
m-react/.rescriptsrc.js
javascript
module.exports = {
webpack:(config)=>{
config.output.libraryTarget = 'umd';
config.output.library = 'm-react'; // 打包的格式是umd格式
return config
},
devServer:(config)=>{
config.headers = {
'Access-control-Allow-Origin':"*"
}
return config
}
}
m-react/package.json
json
{
...
"scripts": {
"start": "rescripts start",
"build": "rescripts build",
"test": "rescripts test",
"eject": "rescripts eject"
},
...
}
支持静态服务
bash
http-server --port 30000 --cors
m-static/index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>静态应用</title>
</head>
<body>
<div id="static"></div>
<script >
const app = document.getElementById('static');
// 最终导出接入协议即可
function render() {
app.innerHTML = 'static'
}
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
window['m-static'] = {
bootstrap: async () => {
console.log('static bootstrap')
},
mount: async () => {
render()
},
unmount: async () => {
app.innerHTML = ''
}
}
</script>
</body>
</html>
统一登录问题
https://github.com/umijs/qiankun/issues/178 美团 https://tech.meituan.com/2020/02/27/meituan-waimai-micro-frontends-practice.html