1 简介
1.简单:任意js框架都可以使用;
2.完备:样式隔离,js沙箱,预加载等;
3.生成可用;
2 案例
1.创建4个应用,分别是:base项目(可以是任何框架搭建的),微前端的主应用,react创建的;my-react,一个子应用;my-vue,vue3创建的一个子应用;vue-lib,vue2创建的一个组件库。vue-lib是一个公共库,需要在react-project和my-vue中使用。
2.在base项目的App.js中,导入路由,可以实现my-react和my-vue两个子应用的却换;
3.在base项目中的src下新建一个resgisterApp.js的文件,用于注册子应用。用qiankun来注册。然后在main.ts中导入该文件。
import cons from "consolidate";
import { registerMicroApps, start } from "qiankun";
// qiankun底层是基于single-spa实现的
// 给m-vue应用传一个loader
const loader = (loading) => {
console.log(loading);
};
/**
* 注册应用 registerMicroApps(apps,lifeCycles?)
* apps Array<RegistrableApp> , 必填,微应用的一些注册信息
* lifeCycles 可选,全局的微应用生命周期钩子
*/
registerMicroApps(
[
{
name: "m-vue", // 注册应用名
entry: "//localhost:20000", // 访问资源的入口
container: "#container", // 挂载容器
activeRule: "/vue", // 激活条件,当选中/vue路由时才加载当前应用
loader, // 加载一个loading
},
{
name: "m-react",
entry: "//localhost:30000",
container: "#container",
activeRule: "/react",
loader,
},
],
{
beforeLoad: (app) => {
console.log("加载前");
},
beforeMount: (app) => {
console.log("挂载前");
},
afterMount: (app) => {
console.log("挂载后");
},
beforeUnmount: (app) => {
console.log("销毁前");
},
afterUnmount: (app) => {
console.log("销毁后");
},
}
);
// 开始预加载所有微应用静态资源
start();
4.进入my-vue项目,改造一下main.js;需要暴露接入协议,修改一下配置文件。
在vue.config.js文件中,具体代码如下:
module.exports = {
publicPath: "//localhost:20000", // 保证子应用静态资源都是像20000端口上发送的
devServer: {
port: 20000,
headers:{
"Access-Control-Allow-Origin": "*",
}
},
/**
* 别人怎样访问到?
* 别人会用到fetch请求资源
* fetch默认不允许跨域
*/
configureWebpack: {
// 需要获取我打包的内容,打包输出格式为umd格式
output:{
libraryTarget: "umd", // 打包格式
library: "m-vue", // 挂载名
}
},
};
// 3000 --> 20000基座会去找20000端口中的资源,publicPath /
5.修改my-vue中的main.js,直接在main.js后面追加如下代码:
// 不能直接挂载,需要切换的时候,调用mount方法时再去挂载
// createApp(App).use(router).mount("#app");
let history; // 每次退出时需要清空,所以用一个history记录
let router;
let app;
function render(props = {}) {
// 每次挂载和卸载都需要产生一个全新的实例
history = historyWebHistory("/vue"); // 传入主应用中的base地址
// 创建路由
router = createRouter({
history,
routes,
});
app = createApp(App);
let { container } = props;
app.use(router).mount(container ? container.querySelector("#app") : "#app");
}
// qiankun在渲染前给我们提供了一个变量window.__POWERED_BY_QIANKUN__
if (!window.__POWERED_BY_QIANKUN__) {
// 用于独立运行
render();
}
// 需要暴露接入协议
export async function bootstrap() {
console.log("vue3 app 启动成功 bootstrap");
}
export async function mount(props) {
console.log("vue3 app 启动成功 mount");
// props 传入需要挂载的容器过来,用于指定渲染时需要挂载的容器
render(props);
}
export async function unmount() {
console.log("vue3 app 启动成功 unmount");
history = null; // 卸载时就清空history
router = null;
app = null;
}
6.修改route/index.js文件:
删除里面的const router=createRouter({ ....})
7.修改my-react项目,想要修改配置文件,需要先下载@rescript/cli插件,表示重写react本身的配置文件,在项目根目录下新建一个.rescriptsrc.js文件,具体代码如下:
module.exports = {
webpack: (config) => {
config.output.library = "m-react";
// 为什么一定得用umd模式的,因为只有umd模式的才会把打包后的资源挂载在window上,挂载的属性名为library名
config.output.libraryTarget = "umd";
config.output.publicPath = "//localhost:30000/"; // 最后的/必须写,不写的话会找不到下面的资源
},
devServer: (config) => {
config.headers = {
"Access-Control-Allow-Origin": "*",
};
return config;
},
};
8.在my-react项目中,根目录下新建一个.env文件,具体代码如下:
PORT = 30000;
WDS_SOCKET_PORT = 30000;
9.想要my-react中的自定义配置文件生效,则需要修改package.json文件中的sxripts属性中的值,将之前的react-scripts替换为自定义的插件名rescripts;
10.my-react项目中导出生命周期函数,进去src下面的index.js文件,添加如下代码:
function render(props = {}) {
// 把之前的渲染挪在这个函数里面
let { container } = props;
ReactDom.render(
<App />,
container
? container.querySelector("#root")
: document.getElementById("#root")
);
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {}
export async function mount(props) {
render(props);
}
export async function unmount(props) {
let { container } = props;
// 卸载ReactDOM
ReactDom.unmountComponentAtNode(
container
? container.querySelector("#root")
: document.getElementById("#root")
);
}
11.样式隔离:
默认情况下切换应用,它会采用动态样式表,加载的时候添加样式,删除的时候卸载样式。
主要是隔离主应用和子应用如何隔离:
1.可以通过BEM规范来进行避免;
2.css-modules每次加载模块都动态生成一个前缀,并不是完全隔离。在主应用的registerApps.js中修改:
// ...
start({
// 添加沙箱
sandbox: {
experimentalStyleIsolation: true,
},
});
3.shadowDOM才是真正意义上的隔离,还有添加全局样式就会有问题:
// ...
start({
// 严格样式隔离
sandbox: {
strictStyleIsolation: true,
},
});
沙箱是使用案例代码如下:
<body>
<div>hello world</div>
<script>
// 通过fatch拿到内容
let appContent = `
<div id="qiankun">
<div>hellow aa</div>
<style>div{color:red;}</style>
</div>
`;
const containerElement = document.createElement("div");
containerElement.innerHTML = appContent;
const appElement = containerElement.firstChild;
let html = appElement.innerHTML;
appElement.innerHTML = "";
let shadow = appElement.attachShadow({ mode: "closed" });
shadow.innerHTML = html;
document.body.appendChild(appElement);
</script>
</body>