Skip to content

js的加载与执行

入口加载与解析

qiankun中主要使用了 import-html-entry库中的API importEntry

加载入口

处理入口html文档中的css、js、publicPath

js
// entry: 子应用的入口url,一般返回html
const { template, execScripts, assetPublicPath } = await importEntry(entry, importEntryOpts)
/*
  template(String):外联的css加载后被内联(全部内联策略)
  execScripts(Function):调用执行template中所有的js(可指定上下文)
  assetPublicPath(String):子应用的资源公共路径【动态指定webpack配置的publicPath(子应用资源的公共前缀)】
*/

js执行

调用execScripts执行子应用的js代码,并指定上下文为沙箱的proxy(原理在import-html-entry库中实现的,后续动态创建的script都是通过这个库去加载和执行的)

源码地址

js
/* scriptExports为子应用暴露的钩子,后续开始执行生命周期钩子要用到
  true指定是否严格模式执行子应用js代码
  global为沙箱的proxy
*/
const scriptExports = await execScripts(global, true)

execScripts原理初探,主要使用了getExternalScripts加载js,eval执行js

js
execScripts(entry, scripts, proxy = window, opts = {}) {
	const { fetch = defaultFetch, strictGlobal = false, success, error = () => { }, } = opts
  // 调用 getExternalScripts API
	return getExternalScripts(scripts, fetch, error)
		.then((scriptTexts) => {
      // 伪代码
      // for...
      eval(getExecutableScript(scriptTexts[i]))
    })

getExternalScripts用于获取script text,缓存功能 就是在这个函数中实现的(加载过的src/url地址不会再重复去加载)

js
export function getExternalScripts(scripts, fetch = defaultFetch) {
  // 定义fetchScript函数
  /* 
   scriptCache: 用于缓存已请求过的script src资源
   先从缓存中取,取不到调用fetch加载
  */
	const fetchScript = (scriptUrl) => scriptCache[scriptUrl] ||
		(scriptCache[scriptUrl] = fetch(scriptUrl).then(response => {
			return response.text() // 返回script文本
		}))
  // 处理 scripts 参数,以及各种情况, 外联、内联、异步(async、defer)
  // 返回Promise,resolve后就是一个Array,里面是每个script的text
	return Promise.all(scripts.map(script => {
			if (typeof script === 'string') {
				if (isInlineCode(script)) {
					return getInlineCode(script) // 内联
				} else {
					return fetchScript(script) // 外联
				}
			} else {
				const { src, async } = script //异步【空闲时加载 requestIdleCallback】
				if (async) {
					return {
						src,
						async: true,
						content: new Promise((resolve, reject) => requestIdleCallback(() => fetchScript(src).then(resolve, reject))),
					}
				}
				return fetchScript(src)
			}
		},
	))

getExecutableScript 用于返回即将被eval执行的js,在这里指定用with作为传入js的上下文

js
function getExecutableScript(scriptSrc, scriptText, proxy, strictGlobal) {
  // 加个后缀,表示这段js代码是哪个src加载过来的
	const sourceUrl = isInlineCode(scriptSrc) ? '' : `//# sourceURL=${scriptSrc}\n`;

  // 指定的上下文
	window.proxy = proxy;
  // 返回String给eval执行,用with执行上下文环境为传入的proxy
	return strictGlobal
		? `;(function(window, self){with(window){;${scriptText}\n${sourceUrl}}}).bind(window.proxy)(window.proxy, window.proxy);`
		: `;(function(window, self){;${scriptText}\n${sourceUrl}}).bind(window.proxy)(window.proxy, window.proxy);`;
}

WARNING

with语句不推荐使用, 具体见MDN-with