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