微前端乾坤
qiankun 是一个基于 single-spa 的微前端实现库,拥有的特点:JS沙箱,样式隔离,元素隔离,数据通信,预加载,HTML Entry。
qiankun与single-spa的区别有:
qiankun 是HTML Entry,single-spa是 JS Entry
qiankun 有JS沙箱隔离(js沙箱先检测window.Proxy,否则降级为window快照),CSS样式隔离用Shawdom;single-spa不带JS和CSS沙箱隔离,需要用户自己设计
2018年,single-spa诞生了,single-spa是一个小于5kb(gzip)npm包,用于协调微前端的挂载和卸载。只做两件事: 提供生命周期,并负责调度子应用的生命周期。挟持 url 变化,url 变化时匹配对应子应用,并执行生命周期流程。
用于前端微服务化的JavaScript前端解决方案 (本身没有处理样式隔离、js执行隔离) ,实现了路由劫持和应用加载。
特点:
(1)在同一页面上使用多个框架而无需刷新页面
(2)独立部署
(3)使用新框架编写代码,无需重写现有应用程序
(4)延迟加载代码以改善初始加载时间
(5)本身没有处理样式隔离、js执行隔离,共用同一个window
主应用
-
全新的界面设计 ,将会带来全新的写作体验;
- 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
子应用
-
vue2适配
shadow dom自动实现样式隔离(类似于原生的video标签,它有默认的样式)
主应用配置
src/main.js 修改
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { registerMicroApps, start } from "qiankun";
//注册子应用
// 微前端运行原理和spa非常相似
// 当匹配到activeRule的时候,请求获取entry资源,渲染到container中
registerMicroApps([
{
name: "vueapp1",
entry: "//localhost:8031",// 子应用的html入口
container: "#container",// 渲染到哪里
activeRule: "/mypage1",// 路由匹配原则
},
{
name: "vueapp2",
entry: "//localhost:8032",
container: "#container",
activeRule: "/mypage2",
},
]);
createApp(App).use(router).mount("#app");
start();
src/router 修改
import { createRouter, createWebHistory } from "vue-router";
import HelloWorld from "./components/HelloWorld";
const routes = [
{
path: "/",
name: "home",
component: HelloWorld,
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
//主应用使用的嵌套路由
router.beforeEach((to, from, next) => {
if (!window.history.state.current) window.history.state.current = to.fullPath;
if (!window.history.state.back) window.history.state.back = from.fullPath;
// 手动修改history的state
return next();
});
export default router;
src/App.vue 修改
<template>
<div>
<img alt="Vue logo" src="./assets/logo.png" />
<br />
<router-link to="/">基座: Go to Home</router-link>
<br />
<router-link to="/mypage1">基座: 去子应用1</router-link>
<br />
<router-link to="/mypage2">基座: 去子应用2</router-link>
<br />
// 主应用路由渲染出口
<router-view></router-view>
// 微前端子应用渲染出口
<div id="container"></div>
</div>
</template>
子应用配置
vue.config.js(vue.webpack.js) 修改
const { name } = require("./package");
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 8031,
headers: {
// 允许cors跨域
"Access-Control-Allow-Origin": "*",
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: "umd", // 把微应用打包成 umd 库格式
},
},
});
package.json eslint 增加全局变量
"eslintConfig": {
...
"globals": {
"__webpack_public_path__": true
},
...
},
src/public-path.js 修改
动态修改子项目图片等素材地址,子项目中图片地址一般是相对根域名的,但是嵌入到主项目中,需要改变为绝对子项目域名,不然找不到图片
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
main.js - 2选1 - (vue2)修改
import "./public-path";
import Vue from "vue";
import App from "./App.vue";
import routes from "./router";
import VueRouter from "vue-router";
Vue.config.productionTip = false;
let router = null;
let instance = null;
function render(props = {}) {
const { container } = props;
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? "/mypage1/" : "/",
mode: "history",
routes,
});
instance = new Vue({
router,
render: (h) => h(App),
}).$mount(container ? container.querySelector("#app") : "#app");
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 子应用接入qiankun
// 导出三个必要的生命周期钩子函数
// 渲染之前
// 渲染函数
// 卸载函数
export async function bootstrap() {
console.log("[vue] vue app bootstraped");
}
export async function mount(props) {
console.log("[vue] props from main framework", props);
render(props);
}
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = "";
instance = null;
router = null;
}
main.js - 2选1 - (vue3)修改
import './public-path';
import { createApp } from 'vue'
import App from './App.vue'
import routes from './router'
import { createRouter, createWebHistory } from 'vue-router';
import store from './store'
let router = null;
let instance = null;
let history = null;
function render(props = {}) {
const { container } = props;
let arr = routes.options.routes;
console.log(arr)
history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/app-vue3' : '/');
router = createRouter({
history,
routes: routes.options.routes,
});
instance = createApp(App);
instance.use(router);
instance.use(store);
instance.mount(container ? container.querySelector('#app') : '#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('%c%s', 'color: green;', 'vue3.0 app bootstraped');
}
function storeTest(props) {
props.onGlobalStateChange &&
props.onGlobalStateChange(
(value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
true,
);
props.setGlobalState &&
props.setGlobalState({
ignore: props.name,
user: {
name: props.name,
},
});
}
export async function mount(props) {
storeTest(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;
router = null;
history.destroy();
}
router.js - 2选1 - (vue2) 修改
这里只能导出配置了,不然每次加载子项目会是同一个vue-router对象
import Vue from "vue";
import VueRouter from "vue-router";
import HelloWorld from "./components/HelloWorld";
import Hello from "./components/Hello";
import World from "./components/World";
Vue.use(VueRouter);
const routes = [
{ path: "/", component: HelloWorld },
{ path: "/hello", component: Hello },
{ path: "/world", component: World },
];
export default routes;
router.js - 2选1 - (vue3) 修改
App.vue 修改
<template>
<div id="app">
<img alt="Vue logo" style="height: 40px" src="./assets/big.jpg" />
<br />
<router-link to="/">子应用1:首页</router-link>
<br />
<router-link to="/hello">子应用1: hello 页</router-link>
<br />
<router-link to="/world">子应用1:world 页</router-link>
<br />
<router-view></router-view>
</div>
</template>
问题
1、主项目报错,主项目是 vue3 + vue-router@4
[Vue Router warn]: Error with push/replace State DOMException: Failed to execute ‘replaceState’ on ‘History’: A history state object with URL ‘http://localhost:8080undefined/’ cannot be created in a document with origin ‘http://localhost:8080’ and URL ‘http://localhost:8080/mypage1/’.
解决:在router.js中增加下面代码,页面跳转的时候一些内容的丢失。解决办法也查找了一些资料
//主应用使用的嵌套路由
router.beforeEach((to, from, next) => {
if (!window.history.state.current) window.history.state.current = to.fullPath;
if (!window.history.state.back) window.history.state.back = from.fullPath;
// 手动修改history的state
return next();
});
2、子项目 webpack_public_path not defined
解决: package.json 中增加全局变量 webpack_public_path
"eslintConfig": {
...
"globals": {
"__webpack_public_path__": true
},
...
},
点击查看原文链接
点击查看原文链接