微前端qiankun简易上手指南
theme: smartblue
前言
本文主要介绍了微前端 qiankun
环境的搭建,以及如何在主应用中挂载子应用,主应用和子应用之间通信,如何在子应用中接入路由。详细的整理,各种配置文件。分别介绍了 React
和 Vue
子应用的挂载方法。
如果,之前从未接触过微前端,这应该是个不错的上手项目。项目demo我已经放在 gitee
上面。
那么,先从什么是微前端 qiankun 说起。
关于qiankun
微前端
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
微前端借鉴了微服务的架构理念,将一个庞大的前端应用拆分为多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用联合为一个完整的应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。
qiankun
qiankun
是一个基于 single-spa
的微前端实现库。
应用场景
1.项目的迁移,老项目的改造,更新主要的技术栈
2.公司的小伙伴儿比较多,用啥的都有
搭建环境
我使用的node版本:14.8.0
分别创建三个应用,将 qiankun-base 作为主应用
npm create react-app qiankun-base --template typescript
npm create react-app qiankun-micro-app1 --template typescript
npm create react-app qiankun-micro-app2 --template typescript
相关配置,在每个应用的文件夹,根目录的中新建 .env 文件。
配置不同的端口号。
// qiankun-base应用
PORT=3010
// qiankun-micro-app1应用
PORT=3011
//qiankun-micro-app2应用
PORT=3012
快速上手之前,先看一下官网的 快速上手
在主应用 qiankun-base 安装 qiankun
npm i qiankun -S
在主应用 qiankun-base 的入口文件 index.ts 中注册微应用
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'micro-app1', // app name registered
entry: '//localhost:3011',
container: '#micro-app1',
activeRule: '/micro-app1',
},
{
name: 'micro-app2',
entry: '//localhost:3012',
container: '#micro-app2',
activeRule: '/micro-app2',
},
]);
start();
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑。
所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
然后,在主应用中,修改App.tsx,加入 container 容器。
import React from 'react';
import './App.css';
function App() {
return (
<div className="App">
<div id="micro-app1"></div>
<div id="micro-app2"></div>
</div>
);
}
export default App;
主应用中挂载子应用
微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。
React 微应用,相关配置:
1,在src文件夹下,新建 public-path.js 文件
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2.修改webpack配置
①安装 react-app-rewired
npm install react-app-rewired --save
②在 package.json 文件中,修改启动脚本。
"scripts": {
"start": "react-app-rewired start",
},
安装react-app-rewired 后,可以重写webpack的配置信息。
③在微应用根目录下,新建 config-overrides.js 文件。
const { name } = require('./package');
module.exports = {
webpack: (config) => {
config.output.library = `${name}-[name]`;
config.output.libraryTarget = 'umd';
config.output.jsonpFunction = `webpackJsonp_${name}`;
config.output.globalObject = 'window';
return config;
},
devServer: (_) => {
const config = _;
config.headers = {
'Access-Control-Allow-Origin': '*',
};
config.historyApiFallback = true;
config.hot = false;
config.watchContentBase = false;
config.liveReload = false;
return config;
},
}
④修改微应用的入口文件 index.tsx
// @ts-ignore
function render(props) {
const { container } = props;
// @ts-ignore
ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}
// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
// @ts-ignore
export async function bootstrap() {
console.log('[react16] react app bootstraped');
}
// @ts-ignore
export async function mount(props) {
console.log('[react16] props from main framework', props);
render(props);
}
// @ts-ignore
export async function unmount(props) {
const { container } = props;
// @ts-ignore
ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}
⑤为了方便区分,修改 qiankun-micro-app1 微应用中的 App.tsx 文件。
import React from "react";
import "./App.css";
function App() {
return <div className="App">qiankun-micro-app1</div>;
}
export default App;
⑥启动主应用,和两个子应用。
通过主应用配置的 activeRule 匹配到指定的 container 中,成功展示出子应用。
http://localhost:3010/micro-app1
页面展示:
qiankun-micro-app1
http://localhost:3010/micro-app2
页面有个图片没有展示出来,接下来就解决这个问题。
4.解决静态资源不显示的问题
在微应用src文件夹的 index.tsx 入口文件中,导入之前配置的 public-path.js 文件
import './public-path';
刷新页面,成功展示图片
http://localhost:3010/micro-app2
打开控制台在 Elements 元素,可以看到:
<img src="http://localhost:3012/static/media/logo.svg" class="App-logo" alt="logo">
导入 public-path.js 文件之后,图片路径变成了完整的url路径:
http://localhost:3012/static/media/logo.svg
主应用和子应用之间通信
主应用和子应用之间,可能公用一些参数,如何实现传参呢?
修改主应用 qiankun-base 的配置,在 index.tsx 文件中,加入 props 参数。
registerMicroApps([
{
name: "micro-app1", // app name registered
entry: "//localhost:3011",
container: "#micro-app1",
activeRule: "/micro-app1",
props: {
niceBody: "malena",
age: 32
}
},
{
name: "micro-app2",
entry: "//localhost:3012",
container: "#micro-app2",
activeRule: "/micro-app2",
props: {
niceBody: "malena",
age: 32
}
}
]);
子应用 qiankun-micro-app1 的入口文件 index.tsx 中,在 mount 方法中,可以获取的主应用传递的props 参数。
// @ts-ignore
export async function mount(props) {
console.log('[react16] props from main framework', props);
// render(props);
// @ts-ignore
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
// @ts-ignore
// props.setGlobalState(state);
}
如果主应用中参数,发生改变。微应用中,也能接收到改变。
譬如,设置一个定时器,2秒后将state数据中的age从32改为34
主应用 入口文件 index.tsx
import { initGlobalState, MicroAppStateActions } from 'qiankun';
const state = {
name: 'malena morgan'
}
// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
setTimeout(() => {
actions.setGlobalState({ ...state, age: 34});
}, 2000);
actions.offGlobalStateChange();
微应用 入口文件 index.tsx
// @ts-ignore
export async function mount(props) {
console.log('[react16] props from main framework', props);
// render(props);
// @ts-ignore
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
}
如果在微应用中,改变 state 的值,主应用中也能拿到
// @ts-ignore
export async function mount(props) {
console.log('[react16] props from main framework', props);
// render(props);
// @ts-ignore
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
// @ts-ignore
setTimeout(() => {
props.setGlobalState({...state, age: 36})
}, 2000);
});
}
qiankun接入vue3
首先,新建一个vue项目
安装脚手架
npm install -g @vue/cli
创建vue3项目
vue create qiankun-micro-vue3-app3
安装typescript
cd qiankun-micro-vue3-app3
vue add typescript
进行配置:
修改 vue.config.js 文件
// @ts-nocheck
const { name } = require('./package.json');
module.exports = {
devServer:{
port: 3013,
headers:{
'Access-Control-Allow-Origin': '*',
}
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
},
},
}
在 src 文件夹下,新建 public-path.js 文件
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
修改 main.ts 入口文件
// @ts-nocheck
import "./public-path";
import { createApp } from "vue";
import Vue from "vue";
import App from "./App.vue";
let instance = null;
function render(props = {}) {
const { container } = props;
instance = createApp(App);
instance.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('vue3-app')
console.log(props)
render(props);
instance.config.globalProperties.$onGlobalStateChange =
props.onGlobalStateChange;
instance.config.globalProperties.$setGlobalState = props.setGlobalState;
}
export async function unmount() {
instance.unmount();
instance._container.innerHTML = "";
instance = null;
}
在 qiankun-base 主应用中,加载微应用
registerMicroApps([
{
name: "micro-app1", // app name registered
entry: "//localhost:3011",
container: "#micro-app1",
activeRule: "/micro-app1",
props: {
niceBody: "malena",
age: 32
}
},
{
name: "micro-app2",
entry: "//localhost:3012",
container: "#micro-app2",
activeRule: "/micro-app2",
props: {
niceBody: "malena",
age: 32
}
},
{
name: "micro-vue3-app3",
entry: "//localhost:3013",
container: "#micro-vue3-app3",
activeRule: "/micro-vue3-app3",
props: {
niceBody: "malena",
age: 32
}
}
]);
如你所愿,如下所示vue3应用
到此为止,完成了各个子应用之间的切换,那么子应用中各个页面的切换,又该怎么做呢?这就是下面要讲到的。
react子应用接入路由
首先,当然要安装路由 react-router-dom
npm install react-router-dom --save
在子应用 index.tsx 入口文件中,引入路由
import { BrowserRouter } from "react-router-dom";
import "./public-path";
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<BrowserRouter>
<React.StrictMode>
<App />
</React.StrictMode>
</BrowserRouter>
);
// @ts-ignore
function render(props) {
const { container } = props;
// @ts-ignore
ReactDOM.render(
<BrowserRouter
basename={window.__POWERED_BY_QIANKUN__ ? "/micro-app2" : "/"}
>
<App />
</BrowserRouter>,
container
? container.querySelector("#root")
: document.querySelector("#root")
);
}
// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
// render({});
}
// @ts-ignore
export async function bootstrap() {
console.log("[react16] react app bootstraped");
}
// @ts-ignore
export async function mount(props) {
console.log("[react16] props from main framework", props);
render(props);
}
// @ts-ignore
export async function unmount(props) {
const { container } = props;
// @ts-ignore
ReactDOM.unmountComponentAtNode(
container
? container.querySelector("#root")
: document.querySelector("#root")
);
}
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
在 /src/pages 文件夹下,新建两个页面,命名随意。
修改 App.tsx 文件,配置这两个页面的路由。
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { Routes, Route, Link } from "react-router-dom";
import Mila from './pages/Mila';
import Malena from './pages/Malena';
function App() {
return (
<div className="App">
<Link to={"/"}>home</Link> |
<Link to={"/mila"}>micro-app2 mila</Link> |
<Link to={"/malena"}>micro-app2 malena</Link>
<Routes>
<Route path="/mila" element={<Mila/>} />
<Route path="/malena" element={<Malena/>} />
</Routes>
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
正如你所看到的,成功接入路由。
vue3子应用接入路由
第一步,当然也是安装路由
npm install vue-router --save
新建 src\router\index.ts 文件,新建两个页面,在路由文件中配置。
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Mila from '../pages/Mila.vue';
import Malena from '../pages/Malena.vue';
const routes: RouteRecordRaw[] = [
{
path: '/mila',
component: Mila
},
{
path: '/malena',
component: Malena
}
];
const router = createRouter({
history: createWebHistory(
window.__POWERED_BY_QIANKUN__ ? "/micro-vue3-app3" : "/"
),
routes
});
export default router;
然后,在 main.ts 文件中使用
import router from './router/index';
function render(props = {}) {
const { container } = props;
instance = createApp(App);
instance.use(router)
instance.mount(container ? container.querySelector("#app") : "#app");
}
入口页面 App.vue 中,加入 router-view
<template>
<router-link to="/mila">mila</router-link> |
<router-link to="/malena">malena</router-link>
<router-view />
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import HelloWorld from './components/HelloWorld.vue';
@Options({
components: {
HelloWorld,
},
})
export default class App extends Vue {}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
vue3子应用,也成功接入路由。
http://localhost:3010/micro-vue3-app3/malena
最后的话
以上,如果对你有用的话,不妨点赞收藏关注一下,谢谢
微信公众号: OrzR3
不定期更新一些技术类,生活类,读书类的文章。