mustard-app
Version:
个人前端微应用建设中。。。
147 lines (135 loc) • 5.3 kB
text/typescript
import { fetchSource, getCompletePath } from '.';
import { IApp } from '../typings';
import { scopedCSS } from './scopedcss';
import { v4 } from 'uuid';
import { isRelativePath, isRemotezElement } from './tools';
// 请求link
function fetchLinkFormHtml (app:IApp, htmlDom: HTMLDivElement) {
const head = htmlDom.querySelector('mustard-app-head');
const linkEntries = Array.from(app.source.links.entries());
// 通过fetch请求所有css资源
const fetchLinkPromise:Promise<string>[] = [];
for (const [url, info] of linkEntries) {
fetchLinkPromise.push(
info.isExternal ?
fetchSource(url, app.url) :
Promise.resolve(info.code)
);
}
Promise.all(fetchLinkPromise).then(res =>{
res.forEach(code => {
const styleEle = document.createElement('style');
styleEle.textContent = code;
// 处理css,加上前缀 mustard-app[name='${appName}']
scopedCSS(styleEle, app.name);
head && head.appendChild(styleEle);
});
app.onLoad(htmlDom);
}).catch(error => app.error(error));
}
function fetchScriptFormHtml (app:IApp, htmlDom: HTMLDivElement) {
const scriptEntries = Array.from(app.source.scripts.entries());
// 通过fetch请求所有css资源
const fetchPromise:Promise<string>[] = [];
for (const [url, info] of scriptEntries) {
fetchPromise.push(
info.isExternal ?
fetchSource(url, app.url) :
Promise.resolve(info.code));
}
Promise.all(fetchPromise).then(res =>{
res.forEach((code, i) => {
// 将代码放入缓存,再次渲染时可以从缓存中获取
scriptEntries[i][1].code = code;
});
app.onLoad(htmlDom);
}).catch(error => app.error(error));
}
/**
* 递归并处理dom
* 收集静态的样式和js
* 处理远程资源 e.g img,video,audio
* @param parent
* @param app
*/
function extractSourceDom (parent:Element, app:IApp) {
const children = Array.from(parent.children);
children?.length && children.forEach(child => extractSourceDom(child, app));
for (const dom of children) {
// const attrs = dom.getAttributeNames();
// attrs.forEach(attr => {
// // 处理dom上直接绑定的事件
// if(/^on[a-zA_Z]+/.test(attr)){
// const time = attr + v4();
// app.source.domClick += `
// this.${time} = function(){
// ${dom.getAttribute(attr)}}\n;
// `;
// dom.setAttribute(attr,`proxyWindow.${time}()`);
// }
// });
// link 记录并收集,并提取src
if(dom instanceof HTMLLinkElement) {
const href = dom.getAttribute('href');
if (dom.getAttribute('rel') === 'stylesheet' && href) {
app.source.links.set(href, {
code: '',
isExternal: true
});
}
parent.removeChild(dom);
}else if(dom instanceof HTMLStyleElement) {
// style 记录并收集,并提取code
app.source.links.set(v4(), {
code: dom.textContent ?? ''
});
parent.removeChild(dom);
}else if(dom instanceof HTMLScriptElement) {
// script 记录并收集,并提取code
const src = dom.getAttribute('src');
// 远程js
if(src) {
app.source.scripts.set(src, {
code: '',
isExternal: true // 是否远程js
});
}else{
app.source.scripts.set(v4(), {
code: dom.textContent ?? ''
});
}
parent.removeChild(dom);
}else if(isRemotezElement(dom)) {
// 远程资源相对地址处理
const src = dom.getAttribute('src');
if(isRelativePath(src)) {
dom.setAttribute('src', getCompletePath(src, app.url));
}
}
}
}
export function loadHtml (app:IApp) {
fetchSource(app.url).then(html => {
html = html
.replace(/<head[^>]*>[\s\S]*?<\/head>/i, match =>
match.replace(/<head/i, '<mustard-app-head').replace(/<\/head>/i, '</mustard-app-head>')
).replace(/<body[^>]*>[\s\S]*?<\/body>/i, match =>
match.replace(/<body/i, '<mustard-app-body').replace(/<\/body>/i, '</mustard-app-body>')
);
// htmlText -> Dom
const Box = document.createElement('div');
Box.innerHTML = html;
// 提取静态js和link,处理style
extractSourceDom(Box, app);
if(app.source.links.size) {
fetchLinkFormHtml(app, Box);
}else{
app.onLoad(Box);
}
if(app.source.scripts.size) {
fetchScriptFormHtml(app, Box);
}else{
app.onLoad(Box);
}
}).catch(error => app.error(error));
}