微前端概述
微前端就是将应用拆分成多个子应用,主应用再加载这些子应用。核心就是先拆后合。
- 微前端解决的问题
- 技术栈不同
- 独立开发,独立部署
- 微前端方案
- iframe:通过iframe嵌入子应用,通过postMessage进行通信,完美的沙箱机制。缺点:弹窗只能在iframe中,url不能体现在地址栏。
- single-spa:通过SystemJS加载子应用,没有沙箱机制
- qiankun:对single-spa封装,提供了沙箱机制
- 模块联邦:将组件打包导出,需要使用webpack5
- 其它:EMP(百度),MicroApp (京东),wujie(腾讯)
qiankun 实战
主应用
- 创建主应用:
yarn create react-app - 添加路由:
yarn add react-router-dom
import { BrowserRouter, Link } from "react-router-dom";
function App() {
return (
<div className="App">
<BrowserRouter>
<Link to="/react">react</Link>
<span> </span>
<Link to="/static">static</Link>
</BrowserRouter>
<div id="subapp"></div>
</div>
);
}
export default App;
- 安装qiankun:
yarn add qiankun - 注册微应用并启动
import { registerMicroApps, start } from "qiankun";
registerMicroApps([
{
name: "react",
entry: "//localhost:8000",
container: "#subapp",
activeRule: "/react",
},
{
name: "static",
entry: "//localhost:9000",
container: "#subapp",
activeRule: "/static",
},
]);
start();
静态微应用
- 创建静态页面
<!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>Document</title>
</head>
<body>
<div id="root"></div>
<script>
const rootEle = document.getElementById("root");
rootEle.innerHTML = `<div>this is static page</div>`;
</script>
</body>
</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>Document</title>
</head>
<body>
<div id="root"></div>
<script>
function render(props) {
const { container } = props;
const rootEle = container
? container.querySelector("#root")
: document.getElementById("root");
rootEle.innerHTML = `<div>this is static page</div>`;
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
window.subStatic = {
bootstrap: async () => {
console.log("bootstrap");
},
mount: async (props) => {
console.log("mount");
render(props);
},
unmount: async () => {
console.log("mount");
},
};
</script>
</body>
</html>
切到微应用路由后流程:
- 主应用加载入口文件内容
- 加工html字符串,(去掉外链css, 外链js)
- 调用bootstrap方法
- 将加工后的html字符串嵌入挂载点
- 执行微应用js代码
- 调用mount方法
从微应用路由切到其它路由流程:
- 将挂载点的DOM移除
- 调用unmount方法
- 启动:支持跨域
serve -C
react 微应用
- 修改端口号:添加
.env文件,修改端口号PORT=8000 - 打包成
umd格式:安装修改配置的第三方工具@rescripts/cli,修改配置
packaga.json
"scripts": {
"start": "rescripts start",
"build": "rescripts build",
"test": "rescripts test",
"eject": "rescripts eject"
}
.rescriptsrc.js
module.exports = {
webpack: (config) => {
config.output.library = "subReact";
config.output.libraryTarget = "umd";
return config;
},
devServer: (config) => {
config.headers = {
"Access-Control-Allow-Origin": "*",
};
return config;
},
};
- 修改入口文件,导出生命周期钩子
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
function render(props) {
const { container } = props;
const root = ReactDOM.createRoot(
container
? container.querySelector("#root")
: document.getElementById("root")
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
export async function bootstrap(props) {
console.log("bootstrap", props);
}
export async function mount(props) {
console.log("mount", props);
render(props);
}
export async function unmount(props) {
console.log("unmount", props);
}
- 修改
__webpack_public_path__
新建public-path.js并在入口文件首行导入
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
- css沙箱
start({ sandbox: { experimentalStyleIsolation: true } });
微应用的样式添加了唯一的父选择器div[data-qiankun="react"]用这种方式隔离。
- 通信
通过props通信
registerMicroApps([
{
name: "react",
entry: "//localhost:8000",
container: "#subapp",
activeRule: "/react",
+ props: { key: 1, key2: 2 },
},
{
name: "static",
entry: "//localhost:5000",
container: "#subapp",
activeRule: "/static",
},
]);