qiankun原理解析

这篇具有很好参考价值的文章主要介绍了qiankun原理解析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

qiankun框架简介

qiankun 是一个基于single-spa框架实现的一个微前端框架,single-spa虽然实现了路由劫持和应用加载,但是没有实现样式隔离和js隔离,并不是一个完善的微前端框架;
qiankun 在实现了路由劫持和应用加载的同时还实现了沙箱和import-html-entry

一、qiankun 特性

  1. 基于 single-spa 封装了更加开箱即用的api
  2. 技术栈无关
  3. html-entry 接入方式,让接入微应用如同使用iframe一样简单(资源加载机制)
  4. 样式隔离
  5. js沙箱
  6. 资源预加载:在浏览器空闲时间预加载未打开的微应用资源,加速为应用打开速度
  7. umi插件

二、qiankun 隔离方案

<一>、js 隔离

1. 主流浏览器(支持Proxy), 使用 基于 proxy 的多实例沙箱实现

!!#ff0000 实现原理是:通过Proxy劫持沙箱全局window对象, 劫持对全局对象属性的更改,来修改window对象的属性和方法,在卸载和加载应用时关闭/激活沙箱!!

class ProxySandbox {
	constructor(name, context){
		this.name = name;
		this.context = context; // 共享执行上下文
		this.proxy = null; // 代理对象
		this.fakeWindow = Object.create({}); // sandbox 全局对象
		// 记录沙箱的激活状态
		this.sandboxRunning = true;
		const proxy = new Proxy(fakeWindow, {
			get(target, prop) {
				// 如果共享对象中有该属性,则优先使用共享对象中的属性
				if(Object.keys(this.context).inclueds(props)) {
					return this.context[prop]
				}
				return target[prop]
			},
			set(target, prop, value) {
				if(this.sandboxRunning) {
					if(Object.keys(this.context).inclueds(prop)) {
						// 更新共享对像中的属性
						this.context[props] = value
					}
					// 更新sandbox全局对象中的属性
					target[prop] = value
				}
			}
		})
		this.proxy = proxy
	}
	
	// 激活沙箱
	activeSandbox() {
		this.sandboxRunning = true;
	}
	
	// 关闭沙箱
	inactiveSandbox() {
		this.sandboxRunning = false;
	}
}
2. 针对不支持Proxy 的浏览器,使用基于diff 的沙箱实现

!!#ff0000 实现的原理是: 在运行子应用时保存一个全局共享对象window的快照对象,将当前全局共享对象的所有属性方法全部复制到这个快照对象中;子应用卸载时将全局共享对象window对象和快照进行diff比对,将有差异的属性保存下载,等再次挂载子应用的时候再添加上这些属性!!

class DiffSandbox {
	constructor(name) {
		this.name = name;
		this.modifyProps = {}; // 记录变更过的属性
		this.windowSnapshot = {}; // 快照对象
	}
	
	// 激活沙箱
	activeSandbox() {
		// 先清空快照对象
		this.windowSnapshot = {}
		for(let key in window) {
			// 拷贝全局对象
			this.windowSnapshot[key] = window[key]
		}
		
		// 将变更属性添加到全局对象中
		Object.keys(this.modifyProps).forEach(prop => {
			window[prop] = this.modifyProps[prop]
		})
	}
	
	// 关闭沙箱
	inactiveSandbox() {
		this.modifyProps = {}
		for(const key in window) {
			if(this.windowSnapshot[key] !=== window[key]) {
				// 记录变更属性
				this.modifyProps[key] = window[key]
				
				// 还原全局共享对象
				window[key] = this.windowSnapshot[key]
			}
		}
	}
}

<二>、样式隔离

1. 支持Shadow Dom 的浏览器使用 Shadow Dom
2. 不支持Shadow Dom 的浏览器使用 Scope Css

!!#ff0000 原理是为当前激活子应用的样式添加上唯一的命名空间!!

三、Qiankun 通信方案

1. 使用

!!#38761d 主应用!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UnFRtrOF-1677465300443)(/download/attachments/2405664573/%E6%88%AA%E5%B1%8F2023-02-21%2020.24.40.png?version=1&modificationDate=1676982284622&api=v2)]

!!#38761d 微应用!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A84xtzKF-1677465300444)(/download/attachments/2405664573/%E6%88%AA%E5%B1%8F2023-02-21%2020.25.12.png?version=1&modificationDate=1676982318323&api=v2)]

2. 实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xmTAEsaP-1677465300445)(/download/attachments/2405664573/%E6%88%AA%E5%B1%8F2023-02-21%2019.54.16.png?version=1&modificationDate=1676980462197&api=v2)]文章来源地址https://www.toymoban.com/news/detail-602965.html

let globalState = {}; // 全局状态
let deps = {}; // 微应用状态变更依赖

function emitGlobal(state, preState) {
	Object.keys(deps).forEach(id => {
		if(deps[i] instance of Funtion){
			deps[i](cloneDeep(state), cloneDeep(prevState))
		}
	})
}

function initGlobalState(state) {
	if(state === globalState) {
		consle.log('state is not changed!')
	} else {
		// 更新之前的全局依赖
		const prevGlobalState = cloneDeep(globalState);
		// 更新全局依赖
		globalState = cloneDeep(state);
		// 执行依赖更新
		emitGlobal(globalState, prevGlobalState)
	}
	return getMicroAppStateActions(`global-${new Date()}`, true)
}

function getMicroAppStateActions(id, isMaster) {
 return {
 	onGlobalStateChange(callback, fireImmediately) {
		if(!deps[id]) {
			// 依赖收集
			deps[id] = callback
			if(fireImmediately) {
				// 立即执行更新依赖的操作
				callback(cloneDeep(globalState), cloneDeep(globalState))
			}
		}
	},
	
	setGlobalState(state) {
		const changedKeys = []; // 记录变更的依赖项
		const prevGlobalState = cloneDeep(globalState); // 记录变更之前的依赖
		// 更新全局状态
		globalState = cloneDeep(
			Object.keys(state).reduce((_globalState, changeKey) => {
				if(isMater || _globalState.hasOwnProperty(changeKey)) {
					changeKeys.push(changeKey)
				}
				return _globalState
			}, globalState)
		)
		emitGlobal(globalState, prevGlobal)
		return true;
	},
	
	offGlobalStateChange() {
		delete deps[i]
		return true;
	}
 }
}

四、 资源预加载

JS Entry

  1. single-spa在加载微应用的时候就用到的了js entry, 在加载微应用时我们加载的不是微应用本身,而是微应用导出的js文件。在微应用的入口文件中会导出一个包含微应用生命周期的对象
  2. 采用JS Entry 存在的一个问题就是,对微应用的改造入侵性太强,而且和主应用的耦合性太强

HTML Entry

  1. 通过http请求加载制定地址的首屏内容即html页面
  2. 然后解析这个html页面得到 templatescriptsentrystyles
{
	template, // 经过处理的脚本,link、script标签都被注释掉了
	scripts, // 脚本的http地址或者"{async: true, src: xxx}"代码块
	styles // 样式的http地址,
	entry: // 入口脚本地址
}
  1. 通过http从远程加载stypes文件,将template中注释掉的linkscript标签替换为对应的style文件
  2. 最后向外暴露一个Promise对象
{
	// template 是link替换为style之后的template
	template: embedHTML,
	// 静态资源文件
	assetPublicPath,
	// 获取外部脚本
	getExternalScripts: () => getExternalScripts(scripts, fetch),
	// 获取外部样式文件
	getExternalStyleSheets: () => getExternalStylesSheets(styles, fetch),
	// 脚本执行器, 让js代码在指定上下文中运行
	execScripts: (proxy, strictGlobal) => {
		if(!scripts.length){
			return Promise.resolve()
		}
		return execScripts(entry, scripts, proxy, {fetch, strictGlobal})
	}
}
1. 资源加载基本实现
// 调用 requestIdCallback方法在浏览器空闲时载入资源
function prefetch(entry: Entry, opts?: ImportEntryOpts): void {
  if (!navigator.onLine || isSlowNetwork) {
    // Don't prefetch if in a slow network or offline
    return;
  }

  requestIdleCallback(async () => {
    // html-entry
    const { getExternalScripts, getExternalStyleSheets } = await importEntry(entry, opts);
    requestIdleCallback(getExternalStyleSheets);
    requestIdleCallback(getExternalScripts);
  });
}
2. 资源加载策略
  • 监听 single-spa 提供的 single-spa:first-mount 方法,第一个应用加载之后,再加载其他应用的资源
  • 传入一个微应用列表, 在第一个微应用资源加载之后再加载指定微应用的资源
  • 主应用执行了start方法之后,直接开始加载所有微应用的资源
  • 接收 一个自定义函数,自定义函数返回两个微应用组成的数组,一个是关键微应用列表是需要立即加载的微应用资源,第二个是一个普通的微应用列表在第一个微应用资源加载之后加载列表中的资源
/**
 * 微应用预加载策略
 * @param apps 微应用列表
 * @param prefetchStrategy  预加载策略(一共有四种策略)
 * 1. true - 第一个微应用挂载以后加载其他应用的静态资源,利用dingle-spa提供的first-mount事件来实现
 * 2. string[] - 微应用名称列表,在第一个微应用挂载以后加载指定的微应用的静态资源
 * 3. all - 主应用执行start以后直接开始 预加载所有微应用的静态资源
 * 4. 自定义函数 - 返回两个微应用组成的数组,一个时关键微应用组成的数组,需要马上执行预加载的微应用,一个是普通的微应用组成的数组,在第一个微应用挂载以后预加载这些微应用的静态资源
 * @param importEntryOpts 
 */
export function doPrefetchStrategy(
  apps: AppMetadata[],
  prefetchStrategy: PrefetchStrategy,
  importEntryOpts?: ImportEntryOpts,
) {
  // 自定义函数,接收微应用名称组成的数组,然后从微应用列表中过滤出对应名称的微应用
  const appsName2Apps = (names: string[]): AppMetadata[] => apps.filter((app) => names.includes(app.name));

  if (Array.isArray(prefetchStrategy)) {
    // 第二种预加载策略,在第一个微应用挂载之后加载其他指定的微饮用资源
    prefetchAfterFirstMounted(appsName2Apps(prefetchStrategy as string[]), importEntryOpts);
  } else if (isFunction(prefetchStrategy)) {
    // 自定义函数,第四种预加载策略
    (async () => {
      // critical rendering apps would be prefetch as earlier as possible
      const { criticalAppNames = [], minorAppsName = [] } = await prefetchStrategy(apps);
      // 需要马上执行预加载的微应用
      prefetchImmediately(appsName2Apps(criticalAppNames), importEntryOpts);
      prefetchAfterFirstMounted(appsName2Apps(minorAppsName), importEntryOpts);
    })();
  } else {
    switch (prefetchStrategy) {
      case true:
        // 第一种资源预加载策略: 利用single-spa 的finst-mount时间加载其他静态资源
        prefetchAfterFirstMounted(apps, importEntryOpts);
        break;

      case 'all':
        // 第三种资源预加载策略:预加载所有微应用资源
        prefetchImmediately(apps, importEntryOpts);
        break;

      default:
        break;
    }
  }
}

五、源码解析

1. 源码结构
├── apis.ts // API相关方法
├── effects.ts
├── error.ts
├── errorHandler.ts
├── globalState.ts  // 通信
├── index.ts
├── interfaces.ts
├── loader.ts // 加载应用核心方法
├── prefetch.ts // 预加载
├── sandbox // 沙箱
├── utils.ts

到了这里,关于qiankun原理解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微前端框架qiankun剖析

    要了解qiankun的实现机制,那我们不得不从其底层依赖的single-spa说起。随着微前端的发展,我们看到在这个领域之中出现了各式各样的工具包和框架来帮助我们方便快捷的实现自己的微前端应用。在发展早期,single-spa可以说是独树一帜,为我们提供了一种简便的微前端路由工

    2024年02月05日
    浏览(44)
  • 微前端框架篇一,了解qiankun

    微前端是一种将复杂单体应用拆分为多个小型独立前端应用,然后将这些小应用按需加载并集成到主应用的技术方案。 每个子应用都有自己的 JavaScript 和 CSS 代码。 单页Web应用(single page web application,SPA),就是只有一张Web页面的应用。所有页面都来回在这张页面上切换。

    2024年01月22日
    浏览(41)
  • qiankun原理解析

    qiankun 是一个基于 single-spa 框架实现的一个微前端框架, single-spa 虽然实现了路由劫持和应用加载,但是没有实现样式隔离和js隔离,并不是一个完善的微前端框架; qiankun 在实现了路由劫持和应用加载的同时还实现了沙箱和import-html-entry 基于 single-spa 封装了更加开箱即用的a

    2024年02月16日
    浏览(30)
  • 微前端--qiankun原理概述

    demo放最后了。。。 一》微前端概述         微前端概念是从微服务概念扩展而来的,摒弃大型单体方式,将前端整体分解为小而简单的块,这些块可以独立开发、测试和部署,同时仍然聚合为一个产品出现在客户面前。可以理解微前端是一种将多个可独立交付的小型前端

    2023年04月14日
    浏览(32)
  • 使用vite-plugin-qiankun插件, 将应用快速接入乾坤(vue3 vite)

    qiankun官网 vite-plugin-qiankun插件github地址:vite-plugin-qiankun 1、安装乾坤 2、在主应用中注册微应用(main.ts) 3、挂载 在App.vue挂载微应用节点 1、安装插件 qiankun目前是不支持vite的,需要借助插件完成 2、修改vite.config.ts 3、修改main.ts

    2024年02月13日
    浏览(46)
  • 【前端打怪升级日志之微前端框架篇】微前端qiankun框架子应用间跳转方法

    参考链接 qiankun官网:微应用之间如何跳转? 1.主应用、子应用路由都是hash模式    主应用根据 hash 来判断微应用,无需考虑该问题 2.主应用根据path判断子应用 方法 实现 适用条件 参数传递 存在问题 a标签跳转 a href=\\\"/toA\\\"/a 页面重新刷新,原来的状态丢失,用户体验不好 h

    2024年02月07日
    浏览(43)
  • 前端三大框架的生命周期最底层原理解析

    在现代前端开发中,React、Angular和Vue.js等三大框架已经成为了行业中最受欢迎和广泛使用的工具。这些框架的核心功能之一是生命周期管理,通过生命周期方法,我们可以在这些关键点执行特定的操作,以实现更好的控制和管理前端应用程序的行为。然而,你是否好奇这些生

    2024年02月13日
    浏览(49)
  • 07. vue3+vite+qiankun搭建微应用前端框架,并接入vue3微应用

    因为业务系统接入的需要,决定将一个vue3+vite+ts的主应用系统,改造成基于qiankun的微应用架构。此文记录了改造的过程及vue3微应用接入的种种问题。 网上有很多关于微应用改造的案例,但很多都没写部署之后什么情况。写了部署的,没有实操部署在二级目录、三级目录是什

    2024年01月16日
    浏览(62)
  • Java之Spring Boot+Vue+Element UI前后端分离项目,前端插件化主流框架和实现原理

    三、设置Axios发起请求统一前缀的路径 https://code100.blog.csdn.net/article/details/123302546 1、HelloWorld.vue getInfo() { this.$http.get(‘blog/queryBlogByPage?title=’ + this.title + ‘page=’ + this.page + ‘rows=’ + this.rows) .then(response = ( this.info = response.data, this.total = this.info.total, this.totalPage = this.info.tota

    2024年04月16日
    浏览(63)
  • 前端框架技术革新历程:从原生DOM操作、数据双向绑定到虚拟DOM等框架原理深度解析,Web开发与用户体验的共赢

    前端的发展与前端框架的发展相辅相成,形成了相互驱动、共同演进的关系。前端技术的进步不仅催生了前端框架的产生,也为其发展提供了源源不断的动力。 前端,即Web前端,是指在创建Web应用程序或网站过程中负责用户界面(User Interface, UI)构建与交互的部分,是用户与

    2024年04月26日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包