umijs/qiankun

[Feature Request] 关于子系统的热重载问题

Open

#830 opened on Aug 6, 2020

View on GitHub
 (25 comments) (7 reactions) (0 assignees)TypeScript (15,166 stars) (1,968 forks)batch import
enhancementgood first issue

Description

Background

目前使用qiankun2.x的版本,业务提出需求在发布新的版本时候,期望可以不刷新页面(主应用暂时不考虑)进行该系统的热重载。类似于pc端的热更新,期望qiankun可以导出unload方法,完全销毁app的生命周期,业务系统再次将其重载。

Proposal

  1. 翻看源码发现不能热更新的原因是因为import-html-entry中缓存了 embedHTMLCache
  2. qiankunimportEntry时,如果已经获取过html资源,每次获取的都是内存中的html,以至于script & style也是缓存的
  3. 我的解决方案是,在import-html-entry中导出根据app去清除缓存的钩子函数
  4. 在qiankun中包裹其方法,根据single-spa的appStatus进行卸载应用

Additional context

// import-html-entry
export async function clearCatchByUrl (app) {
	const htmlCatch = embedHTMLCache[app.entry]

	if (!htmlCatch) return
	await htmlCatch.then(res => {
		/**
		 * 因为 res.template里面的script && style是被注释掉的
		 * processTpl 拿不到真实的script & style
		 */
		// const { scripts, styles } = processTpl(res.template, defaultGetPublicPath(url));

		// 清除该url下 script的缓存
		Object.keys(scriptCache).map(scriptK => {
			if(scriptK.startsWith(res.assetPublicPath)) {
				delete scriptCache[scriptK]
			}
		})
		// 清除该url下 style的缓存
		Object.keys(styleCache).map(styleK => {
			if(styleK.startsWith(res.assetPublicPath)) {
				delete styleCache[styleK]
			}
		})

		// 清除该html
		delete embedHTMLCache[url]
		/**
		 * 1. 清除execScripts、bootstrap执行之后对window产生的副作用(-app是因为业务系统打包的umd-name)
		 * 2. 业务代码中对window产生副作用的代码 也需要去除
		 */
		delete window[`${app.name}-app`]
	})
}
import { clearCatchByUrl } from 'shsc-import-html-entry';
import { getAppStatus, unloadApplication } from 'single-spa';
import { AppMetadata } from './interfaces';

/**
 * @description: 用于热重载app
 * @param {Object} app 卸载微应用name, entry
 * @returns false
 */
export async function unloadApp(app: AppMetadata) {
  await clearCatchByUrl(app);
  const appStatus = getAppStatus(app.name);
  
  if (appStatus !== 'NOT_LOADED') {
    unloadApplication(app.name);
    // 调用unloadApplication时,Single-spa将执行以下步骤。

    // 在要卸载的注册应用程序上调用卸载生命周期。
    // 将应用程序状态设置为NOT_LOADED
    // 触发重新路由,在此期间,单spa可能会挂载刚刚卸载的应用程序。
    // 由于unloadApplication调用时可能会挂载已注册的应用程序,因此您可以指定是要立即卸载还是要等待直到不再挂载该应用程序。这是通过该waitForUnmount选项完成的。
  }
}
reloadApp () {
      /**
       * 1. 根据soket返回的数据,判定此次更新了那个服务
       *    a: 将clearHtmlCatch() && unloadApplication() 合并为 unloadApp()
       *    a: 此服务为当前激活服务 unloadApp()
       *    b: 此服务为其他子服务 unloadApp()
       */
      let app = ''
      MicroApps.some(_app => {
        if (_app.name === 'shsc-gaia-cs-web') app = _app
        return _app.name === 'shsc-gaia-cs-web'
      })
      // 先清除因为子系统注册导致的副作用
      delete window.func
      unloadApp(app).then(() => {
        // 重载完成
      })
}

Contributor guide