一.qiankun
1.主应用(以react为例)
- 在src目录下建registerApps.js文件
安装qiankun yarn add qiankun
import { registerMicroApps, start } from "qiankun" //底层基于single-spa
const loader = (loading) => {
//加载状态
console.log(loading,'loading')
}
registerMicroApps([
{
name: "m-vue",
entry: '//localhost:20000',
container: "#container",
activeRule: '/vue',
props:{a:1},//传值到vue微应用
loader
},
{
name: "m-react",
entry: '//localhost:30000',
container: "#container",
activeRule: '/react',
loader
}
], {
beforeLoad: () => {
console.log('加载前')
},
beforeMount: () => {
console.log('挂载前')
},
afterMount: () => {
console.log('挂载后')
},
beforeUnmount: ()=>{
console.log('销毁前')
},
afterUnmount: () => {
console.log('销毁后')
}
})
start({
sandbox: {
//沙箱解决样式隔离
experimentalStyleIsolation:true
}
})
- 在index.js中
引入registerApps.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './registerApps'//引入
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
- 在App.js中
注意:路由时react要手动安装react-router-dom
import {BrowserRouter as Router,Link } from 'react-router-dom'
function App() {
return (
<div className="App">
<Router>
<Link to="/vue">加载vue应用</Link>
<Link to="/react">加载react应用</Link>
</Router>
{/* 切换导航,将微应用渲染到container里面 */}
<div id="container"></div>
</div>
);
}
export default App;
2.微应用(vue3)
- 在main.js中
import { createApp } from "vue";
import App from "./App.vue";
import { createRouter, createWebHistory } from "vue-router";
import routes from "./router";
let history = null;
let router = null;
let app;
//不能直接挂载 需要切换 再调mount方法再去挂载
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__){
//如果在qiankun中运行 动态添加publicPath
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
//乾坤在渲染前 提供了个变量 window.__POWERED_BY_QIANKUN__
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
//需要暴露接入协议 需是 promise
export async function bootstrap() {
console.log("vue3 bootstrap");
}
export async function mount(props) {
console.log("vue3 mount", props);
render(props);
}
export async function unmount() {
console.log("vue3 unmount");
history = null;
router = null;
app = null;
}
- 在根目录下建 vue.config.js
module.exports = {
//!20000后面的 / 注意别掉
publicPath: "//localhost:20000/", //保证子应用静态资源都是向20000端口发送的
devServer: {
port: 20000, //fatch
headers: {
"Access-Control-Allow-Origin": "*", //允许跨域
},
},
configureWebpack: {
//获取我打包的内容 systemjs => umd格式
output: {
library: "m-vue", //${name}-[name] window['m-vue']
libraryTarget: "umd", // 把微应用打包成 umd 库格式
},
},
};
//3000 -> 20000 基座去找20000端口的资源,publicPath '/' 以3000找资源
- 在router中的index.js里 直接导出路由配置 routes数组
在main.js 的render方法里面有创建路由
import Home from "../views/Home.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
];
export default routes;
3.微应用为react
- 首先安装插件@rescripts/cli
yarn add @rescripts/cli -D
- 根目录新增
.rescriptsrc.js
//.rescriptsrc.js
module.exports = {
webpack: (config) => {
config.output.library = `m-react`;//${name}-[name]
config.output.libraryTarget = 'umd';
config.output.publicPath="//localhost:30000/"
// config.output.jsonpFunction = `webpackJsonp_${name}`;
// config.output.globalObject = 'window';
return config;
},
devServer: (config) => {
config.headers = {
'Access-Control-Allow-Origin': '*',
};
return config;
},
};
- 修改
package.json
"scripts": {
"start": "rescripts start",
"build": "rescripts build",
"test": "rescripts test",
"eject": "rescripts eject"
}
- 在根目录建.env文件(react修改端口号需要在.env文件中配置)
PORT=30000
WDS_SOCKET_PORT=30000
- 在src下index.js中
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
reportWebVitals();
function render(props={}) {
let { container } = props;
ReactDOM.render(
<App />,
container?container.querySelector('#root'):document.getElementById('root')
)
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
//需要暴露接入协议 需是 promise
export async function bootstrap() {
}
export async function mount(props) {
render(props);
}
export async function unmount(props) {
let { container } = props;
ReactDOM.unmountComponentAtNode(container?container.querySelector('#root'):document.getElementById('root'))
}
//qiankun 中处理样式 如何处理
//默认情况下切换应用 他会采用动态样式表 加载的时候添加样式,删除的时候卸载样式
//主应用和子应用 样式如何隔离
//1.根据BEM规范
//2.动态生成一个前缀(css-modules)
//3.shadowDom(影子dom) 类似video标签 快进 放大功能 增加全局样式会有问题
微前端续
1.概念
2.single-spa
缺陷
1.不够灵活 不能动态加载js
2.样式不隔离 没有js沙箱机制
1.子应用中
安装
//vue项目中
npm i single-spa-vue
//react项目中
npm i single-spa-react
在main.js中
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import singleSpaVue from "single-spa-vue";
Vue.config.productionTip = false;
const appOptions = {
el: "#vue",//挂载到父应用 id为vue的标签中
router,
render: (h) => h(App),
};
const vueLifeCycle = singleSpaVue({
Vue,
appOptions,
});
//作为子应用执行
if (window.singleSpaNavigate) {
//配置publicPath 请求资源时都会把这个路径拼接在前面 生成绝对路径
__webpack_public_path__ = "http://localhost:10000/";
}
if (!window.singleSpaNavigate) {
//不是子应用时执行
delete appOptions.el;
new Vue(appOptions).$mount("#app");
}
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
配置vue.config.js将子应用打包成一个类库供父应用使用
module.exports = {
configureWebpack: {
output: {
library: "singleVue",//类库的名字叫singleVue
libraryTarget: "umd",//打包成umd模块
},
devServer: {
port: 10000,
},
},
};
2.主应用中
安装
npm i single-spa
在main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import { registerApplication, start } from "single-spa";
Vue.config.productionTip = false;
//拿到 app.js和chunk-vendors.js后通过script标签 放在head里面加载
async function loadScript(url) {
return new Promise((resolve, reject) => {
let script = document.createElement("script");
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
// 1.参数1 myVueApp为子应用的名称
// 2.参数2 applicationOrLoadingFn 启动应用的函数 必须为promise
registerApplication(
"myVueApp",
async () => {
//加载模块
await loadScript("http://localhost:10000/js/chunk-vendors.js");//拿到打包的类库 第三方模块打包后存放位置chunk-vendors.js
await loadScript("http://localhost:10000/js/app.js");//拿到打包的类库 app.js
//1.singleVue为子应用中 vue.config.js类库的名字
//2.通过 window.singleVue 拿到子应用导出的 bootstrap/mount/unmount
return window.singleVue;
},
(location) => location.pathname.startsWith("/vue"),//用户切换到 /vue路由时会加载 上面的async 加载模块
{ a: 1, b: 2 }//传到子应用的参数
);
start();//开启应用
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
在App.vue中
<template>
<div id="app">
<router-link to="/vue">加载vue应用</router-link>
<!-- 子应用加载的位置 -->
<div id="vue">sssssss</div>
</div>
</template>
3.样式隔离
- 子应用与子应用间样式隔离
加载另一个子应用时把上一个子应用的样式移除
- 子应用与夫应用的样式隔离
4.shadow Dom的实现
5.JS沙箱(有以下两种方式实现)
(1)快照沙箱(仅支持单应用)
//如果应用 加载 刚开始我加载A应用 window.a 再加载B应用时(可以访问上个A应用的属性 window.a造成污染)
//单应用切换 沙箱 创造一个干净的环境给这个子应用使用,当切换时 可以选择丢弃属性和恢复属性
//实现方式有 js沙箱 和 proxy
//快照沙箱 1年前拍一张 再拍一张 (将区别保存起来) 再回到一年前
class SnapshotSandbox {
constructor (){
this.proxy = window //window属性
this.modifyPropsMap = {} //记录window的修改
this.active()//初始时激活沙箱
}
active (){
//沙箱激活时状态
this.windowSnapshot = {}//给window拍个照 记录window上的属性
for(const prop in window){
if(window.hasOwnProperty(prop)){
//这个属性如果在window上就保存起来
this.windowSnapshot[prop] = window[prop]
}
}
//当激活时就把上次修改进行一个应用
Object.keys(this.modifyPropsMap).forEach(p=>{
window[p] = this.modifyPropsMap[p]
})
}
inactive (){
//沙箱失效时状态
for(const prop in window){
if(window.hasOwnProperty(prop)){
//判断如果window属性和以前比是否有变化
if(window[prop] !== this.windowSnapshot[prop]){
//有变化后放在 this.modifyPropsMap这个记录属性中
this.modifyPropsMap[prop] = window[prop]
//失活再把window变回以前初始状态
window[prop] = this.windowSnapshot[prop]
}
}
}
}
}
let sandbox = new SnapshotSandbox()
((window)=>{
//测试下
window.a = 1
window.b = 2
console.log(window.a,window.b)
sandbox.inactive();//先失活
console.log(window.a,window.b)
sandbox.active();//再激活
console.log(window.a,window.b)
})(sandbox.proxy) //sandbox.proxy就是window
(2)代理沙箱(可以支持多应用)
把多个应用 用多个代理来实现