UNPKG

mustard-app

Version:

个人前端微应用建设中。。。

179 lines (159 loc) 5.77 kB
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(); }); }