其他 微前端 Vue3 Single-SPA
浅尝微前端,欲穷千里目
1.关于微前端
微前端其实就是把一个页面的路由或者是一个模块都应用化(使之成为一个独立的应用,分别部署)然后整合在一起,这就是我所理解的微前端。有点类似于iframe
1.1 为什么不用iframe
其实如果去仔细阅读最近比较流行的微前端框架——qiankun框架,其文档就有说明,这里在强调一下
url不同步。浏览器刷新iframe,url状态丢失、后退前进按钮无法使用。UI不同步,DOM结构不共享。- 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
- 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
1.2 微前端应用通信
- 基于url来进行通信,但是消息传递能力弱.
- 基于
CustomEvent实现通信. - 基于
props主子应用间通信. - 使用全局变量,
Redux进行通信.
2. Single-SPA
是一个基于前端微服务化的JavaScript前端解决方案,本身没有进行样式的隔离,js执行隔离,只是实现了路由劫持和应用加载
接下来试用一下single-spa
2.1 创建项目
- 先创建2个vue项目(越简单越好),一个当子应用一个当主应用(也可以称为基座)
- 在子应用安装single-spa,yarn add single-spa-vue -S
- 在子应用main.js上进行修改
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import singleSpaVue from 'single-spa-vue';
Vue.config.productionTip = false
const appOptions = {
el:'#vue-p', // 挂载再父应用的id为vue-p的标签中,
router,
store,
render: h => h(App)
}
const vueLifeCycle = singleSpaVue({
Vue,
appOptions:appOptions
})
// 判断父应用引用
if(window.singleSpaNavigate){
__webpack_public_path__ = 'http://localhost:10001/'
}
if(!window.singleSpaNavigate){
// const appOptions = Object.assign()
delete appOptions.el
new Vue(appOptions).$mount('#app')
}
// 协议接入,定义好协议,父应用会调用这些方法
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
复制代码
这里有一个注意点就是,协议接入必须要导出这三个方法:
a. bootstrap
b. mount
c. unmount
2.2 子应用基本配置
主应用加载子应用,将子应用打包生成一个lib去给父应用使用,这里就需要配置子应用的webpack打包
module.exports = {
configureWebpack:{
output:{
library:'singleVue',
// 打包成的singleVue挂载到window上
// 这样就是等效于window.singleVue.bootstrap/mount/unmount
libraryTarget:'umd'
}
},
devServer:{
// 因为不同的子应用,和主应用部署的地址不一样,所以需要跨域
headers: {
'Access-Control-Allow-Origin': '*',
},
port: '10001'
}
}
复制代码
2.3 主应用基本配置
- 首先在
App.vue当中放入子应用需要挂载的dom,<!-- 子应用加载的位置 --><div id="vue-p"></div> - 安装
single-spa库 - 在
main.js中引入库,并注册子应用,具体如下:
import { registerApplication,start } from 'single-spa'
// 挂载script函数
async function loadScript(url){
return new Promise((resolve,reject)=>{
let scrpit = document.createElement('script');
scrpit.src = url;
scrpit.onload = resolve;
scrpit.onerror = reject;
document.head.appendChild(scrpit);
})
}
registerApplication(
'myVueApp',
async ()=>{
console.log('加载模块');
loadScript('http://localhost:10001/js/app.js');
loadScript('http://localhost:10001/js/chunk-vendors.js');
},
// 用户切换到/about这个路由的时候加载
location => location.pathname.startsWith('/about'),
{
params:'这是父应用给子应用的一个参数'
}
)
start();
复制代码
针对上面代码,对加载的js做一下解释:
在子应用当中,我们打开调试工具的Network可以很清晰的看到,子应用会加载两个重要的js文件,一个是app.js,一个是chunk-vendors.js 又因为父应用调用子应用,需要获取到子应用的js资源并加载,所以需要对子应用的js进行一个引入
2.4 Single-SPA的问题
2.4.1 css隔离问题
在single-spa中这个问题是没有被解决的,需要我们自己来解决,一般解决方式如下:
-
子应用之间的样式隔离,可以采取动态样式表的方式,当应用切换的时候移除老应用的样式,添加新应用的样式
-
主应用和子应用的样式隔离
- BEM:约定项目前缀
- CSS-Modules 打包时生成不冲突的选择器名
- Shadow DOM
- css-in-js
2.4.2 js隔离问题(主要是隔离window上的属性)
-
一般来说是采取js沙箱模式,创建一个干净的环境给这个子应用使用,当切换时,可以选择丢弃属性和恢复属性
-
如何实现js沙箱
- 快照沙箱 将前后js进行比对,将区别保存起来,返回的时候,恢复之前的即可(只适合子应用为一个的情况)
- 代理沙箱,不同的应用使用不同的代理来处理
3.qiankun微前端框架
基于Single-SPA提供了更加开箱即用的API,技术栈无关
关于乾坤框架说的并不多,因为其框架本身文档很丰富,这里就简单的说一下文档里面目前没有的,关于微应用是Vue3怎么配置
3.1 主应用配置
主应用配置思路其实很简单,按文档做即可,只是entry的路径要区分一下开发地址和现上部署地址,该例子的部署地址是tangjievic.top,因此有如下配置代码:(在主应用的main.js中进行)
let path = process.env.NODE_ENV !=='development'?'https://tangjievic.top':'http://localhost:40001'
registerMicroApps([
{
name: 'datamap', // app name registered
entry: `${path}/public/childapp/datamap/`,
container: '#data-map',// data-map
activeRule: '/public/home/datamap',
},
]);
start({
sandbox:{
strictStyleIsolation: true
}
});
复制代码
3.2 子应用配置
基本配置步骤:
- 新增 public-path.js 文件,用于修改运行时的 publicPath
- 写一个渲染函数,在渲染函数中我们要获取到应用的实例,在此项目中也就是dataMapApp
- 分情况渲染,其情况一个是作为微应用渲染,一个是作为独立项目渲染
- 导出约定协议的api即:bootstrap,unmount,mount
import './public-path';
let dataMapApp:any = null;
interface propsRender{
container?:HTMLElement;
}
let router = null;
let box:any = null
function render(props:propsRender ){
const container:any = props.container?props.container:null;
box = container ? (container).querySelector('#app') : '#app'
router = createRouter({
history:createWebHashHistory((window as Window & typeof globalThis & {__POWERED_BY_QIANKUN__:string}).__POWERED_BY_QIANKUN__ ? '/public/childapp/datamap/':'/'),
routes
})
dataMapApp = createApp(App);
dataMapApp.use(router).use(store).mount(box);
}
if(!(window as Window & typeof globalThis & {__POWERED_BY_QIANKUN__:string}).__POWERED_BY_QIANKUN__){
render({});
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
export async function mount<F>(props:F) {
console.log('[vue] props from main framework', props);
render(props);
}
export async function unmount() {
dataMapApp.unmount();
dataMapApp = null as any;
router = null;
}
复制代码





















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)