关键在于父应用嵌入子应用
前置条件
- 父子应用均为Vue
应用层
第一阶段:基础引入
关键点
-
子应用不再直接渲染,而是达成一个lib包的形式
-
子应用应该按照spa的协议约定进行打包,即
- lib名为
singleVue
,导出方式为umd
- 主应用引入时,不再
new Vue
,而是将配置交由singleSpa
进行管理,从而生成boostrap mount unmount
三个对外暴露的接口
- lib名为
-
在父应用启动项目中,进行应用注册,如果匹配到子应用路径,则执行定义的逻辑(即渲染子应用于指定区域)
实现
子应用
不再直接new Vue
生成vue实例,而是根据协议(即让singleSpaVue接管)生成暴露接口
import singleSpaVue from 'single-spa-vue';
const appOptions = {
el: '#vue', // 挂载在父应用中id为vue的标签中
router,
render: h => h(App)
}
const vueLifeCycle = singleSpaVue({
Vue,
appOptions
})
// 暴露接口
export const boostrap = vueLifeCycle.boostrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
配置Vue,将子应用打包成一个个的lib去给父应用使用
src根路径下,新建vue.config.js
module.exports = {
configureWebpack : {
output: {
library: 'singleVue', // 指定lib名称
libraryTarget: 'umd' // 指定导出格式
},
devServer: {
port: 10000 // 指定服务运行端口
}
}
}
父应用
父应用注册子应用
import {
registerApplication,
start
} from 'single-spa';
import router from './router' //引入路由
// 注册子应用
registerApplication(
'myVueApp',// 只是一个标识而已
async () => {
console.log('加载子vue应用'); // 会在匹配时执行 一定要是一个返回Promise 的函数
},
location => location.hash.startsWith('#/vue'), // 判断是否命中的函数 会传递location对象 本例中就是用户切换到/vue开头的路径下,我需要加载刚刚定义的子应用
)
// 启动应用
start();
实现效果
第二阶段:渲染子应用
关键点:
- 在路由匹配到时,进行子应用的渲染,而我们现在都是
All in JS
,问题也就转为了引入其对应的vendor.js
和app.js
(以最简单的vue项目为例)- 子应用需要固定导出的静态资源路径,避免动态引入时,根域名变为了父应用的域名,导致无法找到的情况
- 通过
fetch
的方式去获取子应用的静态资源,从而完成渲染
- 优化:支持子应用独立运行,即如果不是父应用引入的情况下,子应用应该采用传统的
new Vue
的形式,便于独立开发
实现
在子应用中进行一层逻辑判断即可, 如果是微前端嵌入的形式,则设置静态资源全为固定的绝对路径,避免父应用引入时找不到的情况
// singleSpaNavigate属性是singleSpa默认会设置的属性,其值是一个函数
if(window.singleSpaNavigate){
// 此处应该实现将webpack静态资源路径设置为子应用路径的逻辑
}
// 支持子应用独立运行
else {
delete appOptions.el;
new Vue(appOptions).$mount('#app');
}
我们知道,webpack存在配置项publicPath,可以设置webpack输出静态资源的路径,但我们现在需要的是动态设置
查看webpack文档,发现一神器__webpack_public_path__。用一句话来解释的话,这货就是output配置中的“publicPath”参数另外一种配置方式。
那么,我们就可以完善上述逻辑为
// singleSpaNavigate属性是singleSpa默认会设置的属性,其值是一个函数
if(window.singleSpaNavigate){
// 此处应该实现将webpack静态资源路径设置为子应用路径的逻辑
__webpack_public_path__ = 'http://localhost:10000/'
}
// 支持子应用独立运行
else {
delete appOptions.el;
new Vue(appOptions).$mount('#app');
}
效果图如下
注意域名,可以发现我们已经实现了在父应用中加载子应用的需求了
第三阶段:隔离
CSS隔离
- 子应用之间的样式隔离
- 主应用和子应用之间的样式隔离
- BEM约定项目前缀
- CSS-Modules 打包时生成不冲突的选择器名
Shadom DOM
css-in-js
具体实现
shadowDom
创建一个shadowDom作为子应用的容器,然后让style生效于shadowDOM的作用域中,这样就实现了纯净的隔离
- 假设p是子应用
- 假设styleElm是子应用的样式
let shadomDOM = document.getElementById('shadow').attachShadow({
mode: 'closed'
});
let pElm = document.createElement('p');
pElm.innerHTML = 'hello zf';
let styleElm = document.createElement('style');
styleElm.textContent = `
p{color:red}
`
shadomDOM.appendChild(styleElm);
// shadomDOM.appendChild(pElm);
问题:当存在第三方库的组件(比如antd的modal)是挂载在body上时,就会失效
document.body.appendChild(pElm);
沙箱
定义沙箱类,intereface如下
// 默认会执行一次active
interface {
Object proxy; // 沙箱代理对象 沙箱环境的真实管理者(现状态)
Object windowSnapshot; // 快照对象(上一个状态)
Object modifyPropsMap; // 记录在proxy上的修改态
// 1. 记录快照 2. 应用上次快照的修改态 简言之:更新快照并根据修改态更新沙箱
active(){}
// 1. 更新修改态 2. 还原沙箱为上次快照状态 简言之:根据快照更新修改态并还原沙箱
inactive(){}
}
实例如下
class SnapshotSandbox {
constructor(){
this.proxy = window; // window属性
this.modifyPropsMap = {}; // 记录在window上的修改
this.active();
}
// 更新快照并根据修改态更新沙箱
active(){
this.windowSnapshot = {};
// 进行拍照(浅拷贝) 便于
for (const prop in this.proxy) {
if (Object.hasOwnProperty.call(this.proxy, prop)) {
this.windowSnapshot[prop] = this.proxy[prop];
}
}
// 应用上次的修改 以还原状态
Object.keys(this.modifyPropsMap).forEach(p=>{
this.proxy[p] = this.modifyPropsMap[p];
})
}
// 根据快照更新修改态并还原沙箱
inactive(){
for (const prop in this.proxy) {
if (Object.hasOwnProperty.call(this.proxy, prop)) {
if(this.proxy[prop] !== this.windowSnapshot[prop]){
this.modifyPropsMap[prop] = this.proxy[prop];
this.proxy[prop] = this.windowSnapshot[prop];
}
}
}
}
}
eg如下
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);
个人建议记忆图
- 代表更新 - 代表不变 ↑ 代表更新 ↓ 代表还原
快照 | 修改态 | 沙箱 | active | + | - | ↑(代表更新) | inactive | - | + | ↓(代表还原) |
---|
相关链接
微前端之qiankun实战
微前端之自实现singleSpa
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!