mustard-app
Version:
个人前端微应用建设中。。。
179 lines (159 loc) • 5.77 kB
text/typescript
import { consumption, getAppFromInstance, mustardAppInfos } from '../global';
import { LocationPrefix, MustardName, MustardURL, TFunction } from '../typings';
import { scopedCSSTextContent } from './scopedcss';
import { isFunction, isRelativePath, isRemotezElement } from './tools';
/**
* 获取虚拟路由key
* @param appName
* @returns
*/
export function getLocationNameByAppName (appName:MustardName) {
return LocationPrefix + appName;
}
/**
* 根据相对地址和当前页面地址返回具体资源路径
* @param relativePath 相对地址
* @param absolutePath 当前页面地址
* @returns
*/
export function getCompletePath (relativePath: string, absolutePath?: string) {
if (!absolutePath || !isRelativePath(relativePath)) return relativePath;
return new URL(relativePath, absolutePath).href;
}
/**
* 请求资源
* @param relativePath 相对地址
* @param absolutePath 当前页面地址
* @returns
*/
export function fetchSource (relativePath: string, absolutePath?: string) {
return fetch(getCompletePath(relativePath, absolutePath)).then((res) => {
return res.text();
});
}
/**
* 监听Dom变化
* @param dom 需要监听的dom元素
* @param config 需要监听的范围 e.g 属性变动/子节点变动
* @param callback 监听变动回调函数
*/
export function mutationObserver (dom:Element, config:MutationObserverInit, callback:MutationCallback) {
const observer = new MutationObserver((mutationsList, observer) => {
observer.disconnect();
callback(mutationsList, observer);
observer.observe(dom, config);
});
// 以上述配置开始观察目标节点
observer.observe(dom, config);
return observer;
}
/**
* 处理子应用的dom
* 1. 加上子应用标识 appName
* 2. 修改ownerDocument,代理到proxydocument
* 3. 特殊dom,特殊处理 e.g 1. 远程资源src 2. 动态style处理(实时加入前缀)
* @param dom
* @param _appName 子应用标识
* @returns
*/
export function handleDom<T extends Element> (dom:T, _appName?:MustardName):T {
if(!dom) return dom;
const appName = _appName ?? consumption();
if(appName && !(dom as unknown as {appName?:string})?.appName) {
const app = getAppFromInstance(appName);
const proxyWindow = mustardAppInfos.getAppProxyWindow(appName);
const config:PropertyDescriptorMap = {
// 1. 子应用标识
// 2. 判断是否处理过的元素
appName: {
value: appName
},
ownerDocument: {
enumerable: true,
get () {
return proxyWindow?.document ?? document;
}
}
};
// 远程资源地址适配
if(isRemotezElement(dom)) {
mutationObserver(dom, { attributes: true, attributeFilter: ['src'] }, function ([mutations] = []) {
if(mutations.type === 'attributes') {
const target = mutations.target as HTMLImageElement;
target.src = getCompletePath(target.getAttribute('src'), app.url);
}
});
}
// 动态style适配
if(dom instanceof HTMLStyleElement) {
mutationObserver(dom, { childList: true }, function ([mutations] = []) {
if(mutations.type === 'childList') {
mutations.target.textContent = scopedCSSTextContent(mutations.target.textContent, appName);
}
});
}
return Object.defineProperties(dom, config);
}
return dom;
}
/**
* 处理选择器
* e.g.
* 1. head -> mustard-app-head
* 2. body -> mustard-app-body
* @param selectors
*/
export function handleSelectors (selectors:string) {
if(!selectors) return '';
if(selectors?.trim() === 'head') return 'mustard-app-head';
if(selectors?.trim() === 'body') return 'mustard-app-body';
return selectors.split(',').map(
_selector => _selector.replace(/(^|,|\s)(head|body)([^a-zA-Z]|$)/g, function (test) {
return test.replace(/(head|body)/, (_, $1)=> `mustard-app-${$1}`);
})
).join(',');
}
/**
* 获取相对地址
* 根据子应用的appName,从loaction.search 上读取对应数据
* @param appName
* @returns
*/
export function getPath (appName:MustardName) {
const search = location.search;
const searchParams = new URLSearchParams(search);
const href = searchParams.get(`${LocationPrefix}${appName}`) ?? '/';
return decodeURIComponent(href);
}
/**
* 获取地址的URL对象
* @param appName
* @param baseUrl
* @returns
*/
export function getURL (appName:MustardName, baseUrl:MustardURL) {
return new URL(getPath(appName), baseUrl);
}
/**
*
* @param path 子应用地址路径
* @param appName 子应用标识
* @param location // 父应用或基座location
* @returns
*/
export function getNewPathToMustard (path:string, appName:MustardName) {
const { pathname: pathnameFromTop, search: searchFromTop = '', hash: hashFromTop } = location;
const SearchParams = new URLSearchParams(searchFromTop);
SearchParams.set(LocationPrefix + appName, encodeURIComponent(path));
const searchParams = SearchParams.toString();
return `${pathnameFromTop}${searchParams ? '?' + searchParams : ''}${hashFromTop}`;
}
/**
* 异步下一微任务运行
* @param fn 待运行的方法
*/
export function nextTick (fn: TFunction) {
Promise.resolve().then(() => {
isFunction(fn) && fn();
});
}