我们可以使用循环递归的方式提取上面字符串资源中所有遍历到的link、style、script标签,提取静态资源地址并格式化标签。
在source.js文件中,添加extraSourceDom函数,用来提取link,script这种特殊标签
// 提取link的css链接
function extractSourceDom(parent, app) {
// 将伪数组转换成数组
const children = Array.from(parent.childNodes);
// 递归每一个元素
children.length && children.forEach(child => {
extractSourceDom(child, app)
})
for (const dom of children) {
// 如果是link标签
if (dom instanceof HTMLLinkElement) {
// 如果link标签有href属性,并且rel的属性===stylesheet,说明是css资源
const href = dom.getAttribute('href');
if (dom.getAttribute('rel') === 'stylesheet' && href) {
// 先将ref地址存放到缓存中
app.source.links.set(href, {
code: '',//具体的css代码内容,这个我们还需要远程获取
});
// 由于link标签需要转换成style标签,所以需要删除link标签
parent.removeChild(dom)
}
}else if(dom instanceof HTMLScriptElement){
// 获取src属性
const src = dom.getAttribute('src');
//如果有src则证明是外联的js资源,还需要远程获取
if(src){
app.source.scripts.set(src,{
code:'',
isExternal:true
})
}else if(dom.textContent){
// 内联js,随机名字当作缓存键名
const randomName = Math.random().toString(36).substring(2,15)
app.source.scripts.set(randomName,{
code:dom.textContent,
isExternal:false
})
}
parent.removeChild(dom)
}
}
}
这里放入了map集合中,可以直接在上面的函数中,调用打印一下看看存放的值
export default function loadingHtml(app) {
fetchSource(app.url).then(html => {
html = html
.replace(/<head[^>]*>[\s\S]*?<\/head>/i, (match) => {
// 将head标签替换为micro-app-head,因为web页面只允许有一个head标签
return match
.replace(/<head/i, "<micro-app-head")
.replace(/<\/head>/i, "</micro-app-head>");
})
.replace(/<body[^>]*>[\s\S]*?<\/body>/i, (match) => {
// 将body标签替换为micro-app-body,防止与基座应用的body标签重复导致的问题。
return match
.replace(/<body/i, "<micro-app-body")
.replace(/<\/body>/i, "</micro-app-body>");
});
// 将html字符串转化为DOM结构
const htmlDom = document.createElement("div");
htmlDom.innerHTML = html;
// console.log("html:", htmlDom);
//提取子应用的link或者script标签资源
extractSourceDom(htmlDom, app)
const microAppHead = htmlDom.querySelector('micro-app-head');
if(app.source.links.size){
fetchLinksFromHtml(app,microAppHead,htmlDom);
}else{
// 过滤link标签之后再加载到界面中
app.onLoad(htmlDom)
}
// 如果有script
if(app.source.scripts.size){
fetchScriptFromHtml(app,htmlDom)
}else{
app.onLoad(htmlDom)
}
}).catch(e => {
console.log('加载子应用远程报错', e);
})
}
// 提取link中的内容,并生成style标签,把内容放进去
// style标签放入micro-app-header标签中
export function fetchLinksFromHtml(app,microAppHead,htmlDom){
// linkEntries是二维数组
const linkEntries = Array.from(app.source.links.entries());
console.log(linkEntries,'linkEntries');
// 声明远程获取的promise数组
const fetchLinkPromise = [];
for(const [href,source] of linkEntries){
fetchLinkPromise.push(fetchSource(href));
}
Promise.all(fetchLinkPromise).then(res =>{
for(let i = 0; i < res.length; i++){
const code = res[i]
// 将代码放入到缓存
linkEntries[i][1].code = code
// 创建style标签
const link2Style = document.createElement('style');
link2Style.textContent = code;
microAppHead.appendChild(link2Style)
}
// 将处理好了的htmlDom挂载到micro-app
app.onLoad(htmlDom)
}).catch(e =>{
console.error('远程加载css出错',e);
})
}
// 提取script的内容并且直接执行
export function fetchScriptFromHtml(app, htmlDom) {
// 将map转成数组
const scriptEntries = Array.from(app.source.scripts.entries());
// 声明远程获取的promise数组
const fetchScriptPromise = [];
for (let [url, info] of scriptEntries) {
// 这里的url可能是相对地址,如果引用相对地址,就会到基座下查找js文件
if (!url.includes('https')) {
url = `${app.url.endsWith('/') ? app.url.substring(0, app.url.length - 1) : app.url}${url}`;
}
// 内联的js直接resolve外联的就请求
fetchScriptPromise.push(info.code ? Promise.resolve(info.code) : fetchSource(url));
}
Promise.all(fetchScriptPromise).then(res => {
for (let i = 0; i < res.length; i++) {
const code = res[i];
scriptEntries[i][1].code = code;
}
app.onLoad(htmlDom)
}).catch(e => {
console.log('加载js资源出错', e);
})
}
上面的代码中,把js字符串放入到eval中执行,使用了(0, eval)(info.code)这样的表达式,这其实就是一个立即执行的函数,然后将字符串info.code传给了eval。这样可以保证eval是在当前环境下执行,还是在全局环境下值。说到底,最重要的是保证如果传入字符串info.code中有this,保证this的作用域是指向全局的,因为上面的代码中,eval是放在一个函数中执行的。
效果图
source.js完整代码
import { fetchSource } from './utils.js';
export default function loadingHtml(app) {
fetchSource(app.url).then(html => {
html = html
.replace(/<head[^>]*>[\s\S]*?<\/head>/i, (match) => {
// 将head标签替换为micro-app-head,因为web页面只允许有一个head标签
return match
.replace(/<head/i, "<micro-app-head")
.replace(/<\/head>/i, "</micro-app-head>");
})
.replace(/<body[^>]*>[\s\S]*?<\/body>/i, (match) => {
// 将body标签替换为micro-app-body,防止与基座应用的body标签重复导致的问题。
return match
.replace(/<body/i, "<micro-app-body")
.replace(/<\/body>/i, "</micro-app-body>");
});
// 将html字符串转化为DOM结构
const htmlDom = document.createElement("div");
htmlDom.innerHTML = html;
// console.log("html:", htmlDom);
//提取子应用的link或者script标签资源
extractSourceDom(htmlDom, app)
const microAppHead = htmlDom.querySelector('micro-app-head');
if (app.source.links.size) {
fetchLinksFromHtml(app, microAppHead, htmlDom);
} else {
// 过滤link标签之后再加载到界面中
app.onLoad(htmlDom)
}
// 如果有script
if (app.source.scripts.size) {
fetchScriptFromHtml(app, htmlDom)
} else {
app.onLoad(htmlDom)
}
}).catch(e => {
console.log('加载子应用远程报错', e);
})
}
// 提取link的css链接
function extractSourceDom(parent, app) {
// 将伪数组转换成数组
const children = Array.from(parent.childNodes);
// 递归每一个元素
children.length && children.forEach(child => {
extractSourceDom(child, app)
})
for (const dom of children) {
// 如果是link标签
if (dom instanceof HTMLLinkElement) {
// 如果link标签有href属性,并且rel的属性===stylesheet,说明是css资源
const href = dom.getAttribute('href');
if (dom.getAttribute('rel') === 'stylesheet' && href) {
// 先将ref地址存放到缓存中
app.source.links.set(href, {
code: '',//具体的css代码内容,这个我们还需要远程获取
});
// 由于link标签需要转换成style标签,所以需要删除link标签
parent.removeChild(dom)
}
} else if (dom instanceof HTMLScriptElement) {
// 获取src属性
const src = dom.getAttribute('src');
//如果有src则证明是外联的js资源,还需要远程获取
if (src) {
app.source.scripts.set(src, {
code: '',
isExternal: true
})
} else if (dom.textContent) {
// 内联js,随机名字当作缓存键名
const randomName = Math.random().toString(36).substring(2, 15)
app.source.scripts.set(randomName, {
code: dom.textContent,
isExternal: false
})
}
parent.removeChild(dom)
}
}
}
// 提取link中的内容,并生成style标签,把内容放进去
// style标签放入micro-app-header标签中
export function fetchLinksFromHtml(app, microAppHead, htmlDom) {
// linkEntries是二维数组
const linkEntries = Array.from(app.source.links.entries());
console.log(linkEntries, 'linkEntries');
// 声明远程获取的promise数组
const fetchLinkPromise = [];
for (const [href, source] of linkEntries) {
fetchLinkPromise.push(fetchSource(href));
}
Promise.all(fetchLinkPromise).then(res => {
for (let i = 0; i < res.length; i++) {
const code = res[i]
// 将代码放入到缓存
linkEntries[i][1].code = code
// 创建style标签
const link2Style = document.createElement('style');
link2Style.textContent = code;
microAppHead.appendChild(link2Style)
}
// 将处理好了的htmlDom挂载到micro-app
app.onLoad(htmlDom)
}).catch(e => {
console.error('远程加载css出错', e);
})
}
// 提取script的内容并且直接执行
export function fetchScriptFromHtml(app, htmlDom) {
// 将map转成数组
const scriptEntries = Array.from(app.source.scripts.entries());
// 声明远程获取的promise数组
const fetchScriptPromise = [];
for (let [url, info] of scriptEntries) {
// 这里的url可能是相对地址,如果引用相对地址,就会到基座下查找js文件
if (!url.includes('https')) {
url = `${app.url.endsWith('/') ? app.url.substring(0, app.url.length - 1) : app.url}${url}`;
}
// 内联的js直接resolve外联的就请求
fetchScriptPromise.push(info.code ? Promise.resolve(info.code) : fetchSource(url));
}
Promise.all(fetchScriptPromise).then(res => {
for (let i = 0; i < res.length; i++) {
const code = res[i];
scriptEntries[i][1].code = code;
}
app.onLoad(htmlDom)
}).catch(e => {
console.log('加载js资源出错', e);
})
}