umijs/qiankun

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

Open

#830 opened on Aug 6, 2020

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

Repository metrics

Stars
 (15,166 stars)
PR merge metrics
 (Avg merge 10d 22h) (1 merged PR in 30d)

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