UNPKG

mustard-app

Version:

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

733 lines (646 loc) 23.6 kB
type IEventLisParameters$1 = Parameters<typeof addEventListener>; type IType$1 = IEventLisParameters$1[0]; type Ilistener$1 = IEventLisParameters$1[1]; type Ioptions$1 = IEventLisParameters$1[2]; declare class ProxyEventListener$1 { eventLis = new Map<IType, Map<Ilistener, Ioptions>>(); // 添加事件 addEventListener (type: IType$1, listener: Ilistener$1, options: Ioptions$1) { if(!this.eventLis.has(type)) { this.eventLis.set(type, new Map()); } const listeners = this.eventLis.get(type); listeners?.set(listener, options); return window.addEventListener(type, listener, options); } // 删除事件 removeEventListener (type: IType$1, listener: Ilistener$1, options: Ioptions$1) { if(!this.eventLis.has(type)) { this.eventLis.set(type, new Map()); } const listeners = this.eventLis.get(type); if(listeners?.get(listener) === options) { listeners?.delete(listener); } return window.removeEventListener(type, listener, options); } // 全部清除事件 clear () { Array.from(this.eventLis.keys()).forEach(key => { const listeners = this.eventLis.get(key); if(listeners instanceof Map) { Array.from(listeners.keys()).forEach(listener => { window.removeEventListener(key, listener, listeners.get(listener)); }); } }); } } /* eslint-disable no-use-before-define */ interface MustardStateOptions$1 { origin?:MustardURL$1, // document 来源 flushed?:boolean // 是否更新文档 } interface MustardState$1 extends MustardStateOptions$1{ data?: unknown, // pushState 和 replaceState 第一个参数 index: number, // 用于计算当前state 的顺序 } // 用于区分子应用 type BindMethod$1 = string; type TCallback$1 = (value: unknown, oldValue:unknown, source:MustardName$1) => void; // 子应用通讯类(注入到子应用的window.microApp) declare class EventCenterMicroApp$1 { name:MustardName$1; constructor (name:MustardName$1) { this.name = name; } // 添加data事件监听 addDataListener (fn:TCallback$1) { eventCenter.on(getEventDataKey(this.name), fn, { immediately: true, repeatSend: true }); } // 解除data监听 removeDataListener (fn:TCallback$1) { fn && eventCenter.off(getEventDataKey(this.name), fn); } // 解除所以的data监听事件 clearDateListener () { eventCenter.off(getEventDataKey(this.name)); } /** * 发送data数据修改事件 * @param data 发送数据 */ dispatch (data:unknown) { globalDataChangeDispatch(this.name, data); eventCenter.dispatch(getEventDataChangeKey(this.name), this.name, data); } /** * 发送自定义事件 * @param method */ dispatchCustomize (method:BindMethod$1, data:unknown) { eventCenter.dispatch(getEventBindKey(this.name, method), this.name, data); } } // 子应用生命周期通讯类(内部调用) declare class EventCenterMicorLife$1 { private name:MustardName$1; constructor (name:MustardName$1) { this.name = name; } /** * 发送生命周期 * @param state 子应用的生命周期 */ public dispatchLife (state:IAppStatus$1) { globalLifeDispatch(IAppStatus[state] as IAppStatusCN, this.name); eventCenter.dispatch(getEventLifeKeyByValue(this.name, state), this.name); } } declare class SandBox$1 { active = false; // 沙箱是否在运行 microWindow = {}; // 代理的对象 injectedKeys = new Set<string | symbol>(); // 新添加的属性,在卸载时清空 name:MustardName$1; // 沙箱标识同app标识一致 proxyEventListener:ProxyEventListener$1; // 全局事件代理 proxyWindow; // window 代理 proxyDocument; // document 代理 proxyHistory; // history 代理 proxyLocation; // location 代理 proxyLocalStorage; // localStorage 代理 proxySessionStorage; // sessionStorage 代理 microApp: EventCenterMicroApp$1; // 事件通讯 // todo // url: MustardURL constructor (name:MustardName$1, url: MustardURL$1) { this.name = name; this.proxyLocation = proxyLocation(this.name, url); this.proxyHistory = proxyHistory(this.name); this.proxyLocalStorage = proxyLocalStorage(this.name); this.proxySessionStorage = proxySessionStorage(this.name); this.proxyDocument = proxyDocument(this.name); this.proxyEventListener = new ProxyEventListener(); this.microApp = new EventCenterMicroApp(this.name); this.proxyWindow = new Proxy(this.microWindow, { // 取值 get: (target, key) => { // 优先从代理对象上取值 if (Reflect.has(target, key)) { return Reflect.get(target, key); } if(key === 'document') { return this.proxyDocument; } if(key === 'addEventListener') { return this.proxyEventListener.addEventListener.bind(this.proxyEventListener); } if(key === 'history') { return this.proxyHistory; } if(key === 'location') { return this.proxyLocation; } if(key === 'localStorage') { return this.proxyLocalStorage; } if(key === 'sessionStorage') { return this.proxySessionStorage; } if(key === 'microApp') { return this.microApp; } // 否则兜底到window对象上取值 const rawValue = Reflect.get(window, key); // 如果兜底的值为函数,则需要绑定window对象,如:console、alert等 if (typeof rawValue === 'function') { const valueStr = rawValue.toString(); // 排除构造函数 if (!/^function\s+[A-Z]/.test(valueStr) && !/^class\s+/.test(valueStr)) { return rawValue.bind(window); } } // 其它情况直接返回 return rawValue; }, // 设置变量 set: (target, key, value) => { // 沙箱只有在运行时可以设置变量 if (this.active) { Reflect.set(target, key, value); // 记录添加的变量,用于后续清空操作 this.injectedKeys.add(key); } return true; }, deleteProperty: (target, key) => { // 当前key存在于代理对象上时才满足删除条件 if (Object.prototype.hasOwnProperty.call(target, key)) { return Reflect.deleteProperty(target, key); } return true; }, has (target, key) { return key in target || key in window; } }); } start () { if(!this.active) { this.active = true; } } stop () { if(this.active) { this.microApp.clearDateListener(); this.active = false; Array.from(this.injectedKeys.keys()).forEach(key => Reflect.deleteProperty(this.microWindow, key)); this.injectedKeys.clear(); this.proxyEventListener.clear(); } } // 修改js作用域 bindScope (code) { return ` ;(function(window, self){ const microApp = window.microApp; const history = window.history; const location = window.location; const document = window.document; const localStorage = window.localStorage; const sessionStorage = window.sessionStorage; const addEventListener = window.addEventListener; ${code}\n; }).call( mustardAppInfos.getAppProxyWindow('${this.name}'), mustardAppInfos.getAppProxyWindow('${this.name}'), mustardAppInfos.getAppProxyWindow('${this.name}'), ) `; } } declare const rawAddEventListener$1 = HTMLElement.prototype.addEventListener; type AddEventListenerParams$1 = Parameters<typeof rawAddEventListener$1> type EventListenerType$1 = 'dataChange' | IAppStatusCN$1 | keyof HTMLElementEventMap; type EventListenerListener$1 = AddEventListenerParams$1[1]; type EventListenerOptions$1 = AddEventListenerParams$1[2]; declare class MustardApp$1 extends HTMLElement { url:MustardURL$1 = ''; // 子应用资源地址 name:MustardName$1 = ''; // 子应用标识 // keepAlive:boolean = true; // dom移除是否保活 checkUrlStop:()=>void; // URL 校验关闭 static get observedAttributes () { return ['name', 'url']; } constructor () { super(); } // 组件刷新 reload () { const app = getAppFromInstance(this.name); if(app) { app.reload(); } } // 子应用添加至页面 connectedCallback () { if(!appIsExist(this.name) && this.url) { nextTick(() => { new App({ url: this.url, name: this.name, container: this }); }); } // 开启URL校验 this.checkUrlStop = addEventListenerUrl(()=>{ checkUrl(this.name); }); } // 子应用从页面中移除 disconnectedCallback () { // 关闭URL校验 this.checkUrlStop?.(); // getAppFromInstance(this.name)?.unmount(true); } // 子应用移动至新页面。 adoptedCallback () {} /** * 属性变化 * @param name 属性名 * @param oldValue 属性旧值 * @param newValue 属性新值 */ attributeChangedCallback (name, oldValue, newValue) { if(!this.name && name === 'name') { if(newValue === MainMustardApp) { throw new Error('子应用标识非法'); } if(appIsExist(name)) { throw new Error(`子应用标识已存在: ${name}`); }else{ this.name = newValue; } }else if(!this.url && name === 'url') { this.url = newValue; } } setAttribute (key:string, value:unknown) { if (/^mustard-app/i.test(this.tagName) && key === 'data') { // 发送数据 eventCenter.dispatch(this.name, value); } else { rawSetAttribute.call(this, key, value); } } addEventListener (type: EventListenerType$1, listener: EventListenerListener$1, options?: EventListenerOptions$1): void { if(isFunction(listener)) { if(type === 'dataChange') { eventCenter.onData(this.name, listener); }else if(isIAppStatusKey(type)) { eventCenter.onLife(this.name, type, listener); }else{ rawAddEventListener.call(this, type, listener, options); } } } removeEventListener (type: EventListenerType$1, listener: EventListenerListener$1, options?: EventListenerOptions$1): void { if(isFunction(listener)) { if(type === 'dataChange') { eventCenter.offData(this.name, listener); }else if(isIAppStatusKey(type)) { eventCenter.offLife(this.name, type, listener); }else{ rawRemoveEventListener.call(this, type, listener, options); } } } } declare class App$1 { baseUrl: MustardURL$1; url: MustardURL$1; name: MustardName$1; container: MustardApp$1; sandbox: SandBox$1; loadCount: number = 0; status = IAppStatus$1.create; state: MustardState$1; // document 来源 microLifeCenter: EventCenterMicorLife$1; // 生命周期通讯 // 存放动态资源 source = { links: new Map<string, SpurceValue>(), // 存放links scripts: new Map<string, SpurceValue>(), // 存放scripts domClick: '\n;' // 存放 dom attrs 上的事件 }; constructor ({ name, url: baseUrl, container }: IAppConstructor$1) { this.name = name; this.baseUrl = baseUrl; this.container = container; this.microLifeCenter = new EventCenterMicorLife(this.name); this.init(); } // 刷新 卸载->初始化 reload () { this.unmount(true); this.init(); } // 初始化 init () { this.status = IAppStatus.create; this.microLifeCenter.dispatchLife(this.status); // 初始化立刻存入 mustardAppInfos.appInstanceMap addInstance(this.name, this); this.loadCount = 0; // 设置资源的真正地址 this.url = getURL(this.name, this.baseUrl).href; this.source = { links: new Map<string, SpurceValue>(), // 存放links scripts: new Map<string, SpurceValue>(), // 存放scripts domClick: '\n;' // 存放 dom attrs 上的事件 }; // 刷新页面时,为了保证 document.origin 正确,默认取history this.state = decodeState(this.name); if(!this.state) { initState(this.name, '', '', this.url); this.state = decodeState(this.name); } this.status = IAppStatus.loading; this.microLifeCenter.dispatchLife(this.status); // 加载对应的资源 loadHtml(this); // 初始化砂箱 this.sandbox = new SandBox(this.name, this.url); } // 资源加载完时执行 onLoad (htmlDom: HTMLElement) { this.loadCount += 1; // 第二次执行且组件未卸载时执行渲染 if (this.loadCount === 2 && this.status !== IAppStatus.unmount) { // 执行mount方法 this.mount(htmlDom); } } /** * 资源加载完成后进行渲染 */ mount (html: HTMLElement) { this.sandbox.start(); // 克隆DOM节点 const cloneHtml = html.cloneNode(true); // 创建一个fragment节点作为模版,这样不会产生冗余的元素 const fragment = document.createDocumentFragment(); Array.from(cloneHtml.childNodes).forEach((node) => { fragment.appendChild(node); }); // 将格式化后的DOM结构插入到容器中 this.container.appendChild(fragment); // 执行js let scripts = ''; this.source.scripts.forEach((info) => { scripts += info.code + '\n;'; }); { (0, eval)(this.sandbox.bindScope(scripts + '\n;' + this.source.domClick)); } // 标记应用为已渲染 this.status = IAppStatus.mount; this.microLifeCenter.dispatchLife(this.status); } /** * 卸载应用 * 执行关闭沙箱,清空缓存等操作 * @param destory 是否销毁应用 */ unmount (destory: boolean) { this.sandbox.stop(); // 暂停沙箱 this.status = IAppStatus.unmount; this.microLifeCenter.dispatchLife(this.status); if (destory) { this.destory(); } } /** * 销毁应用 */ destory () { this.sandbox = null; this.container.innerHTML = ''; this.status = IAppStatus.destory; this.microLifeCenter.dispatchLife(this.status); removeInstance(this.name); } /** * 子应用加载失败 * @param error 失败原因 */ error (error: Error): void { errorLog(error); this.status = IAppStatus.error; this.microLifeCenter.dispatchLife(this.status); this.microLifeCenter = null; } } // location path 前缀 type IApp$1 = App$1; // 基座标识(禁用) type MustardName$1 = string; // 子应用标识, 不能为 MainMustardApp type MustardURL$1 = string; // `http://${string}`|`https://${string}`; // 应用开启地址 // 子应用状态 declare enum IAppStatus$1 { create = 0, // 初始化 loading = 1, // 数据加载中 mount = 2, // dom节点 挂载完成阶段 unmount = 3, // dom节点 卸载 destory = 4, // 销毁应用 error = -100 // 子应用异常 } type IAppStatusCN$1 = keyof typeof IAppStatus$1; interface IAppConstructor$1 { name: MustardName$1; url: MustardURL$1; container: MustardApp$1; } declare global{ interface HTMLElementTagNameMap{ 'mustard-app': MustardApp$1 } interface Window{ mustardAppInfos: { // 微应用实例 currentReadDocMAppName?: MustardName$1; appInstanceMap: Map<MustardName$1, IApp$1>; // 当前所有子应用实例 getAppProxyWindow: (appName: MustardName$1) => IApp$1['sandbox']['proxyWindow']; }; // 子应用通讯方法 microApp: EventCenterMicroApp$1 } } type IEventLisParameters = Parameters<typeof addEventListener>; type IType = IEventLisParameters[0]; type Ilistener = IEventLisParameters[1]; type Ioptions = IEventLisParameters[2]; declare class ProxyEventListener { eventLis: Map<string, Map<EventListenerOrEventListenerObject, boolean | AddEventListenerOptions>>; addEventListener(type: IType, listener: Ilistener, options: Ioptions): void; removeEventListener(type: IType, listener: Ilistener, options: Ioptions): void; clear(): void; } declare class SandBox { active: boolean; microWindow: {}; injectedKeys: Set<string | symbol>; name: MustardName; proxyEventListener: ProxyEventListener; proxyWindow: any; proxyDocument: any; proxyHistory: any; proxyLocation: any; proxyLocalStorage: any; proxySessionStorage: any; microApp: EventCenterMicroApp; constructor(name: MustardName, url: MustardURL); start(): void; stop(): void; bindScope(code: any): string; } declare const rawAddEventListener: { <K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; }; type AddEventListenerParams = Parameters<typeof rawAddEventListener>; type EventListenerType = 'dataChange' | IAppStatusCN | keyof HTMLElementEventMap; type EventListenerListener = AddEventListenerParams[1]; type EventListenerOptions = AddEventListenerParams[2]; declare class MustardApp extends HTMLElement { url: MustardURL; name: MustardName; checkUrlStop: () => void; static get observedAttributes(): string[]; constructor(); reload(): void; connectedCallback(): void; disconnectedCallback(): void; adoptedCallback(): void; /** * 属性变化 * @param name 属性名 * @param oldValue 属性旧值 * @param newValue 属性新值 */ attributeChangedCallback(name: any, oldValue: any, newValue: any): void; setAttribute(key: string, value: unknown): void; addEventListener(type: EventListenerType, listener: EventListenerListener, options?: EventListenerOptions): void; removeEventListener(type: EventListenerType, listener: EventListenerListener, options?: EventListenerOptions): void; } declare function defineMustardAppElement(): void; interface MustardStateOptions { origin?: MustardURL; flushed?: boolean; } interface MustardState extends MustardStateOptions { data?: unknown; index: number; } declare class App { baseUrl: MustardURL; url: MustardURL; name: MustardName; container: MustardApp; sandbox: SandBox; loadCount: number; status: IAppStatus; state: MustardState; microLifeCenter: EventCenterMicorLife; source: { links: Map<string, SpurceValue>; scripts: Map<string, SpurceValue>; domClick: string; }; constructor({ name, url: baseUrl, container }: IAppConstructor); reload(): void; init(): void; onLoad(htmlDom: HTMLElement): void; /** * 资源加载完成后进行渲染 */ mount(html: HTMLElement): void; /** * 卸载应用 * 执行关闭沙箱,清空缓存等操作 * @param destory 是否销毁应用 */ unmount(destory: boolean): void; /** * 销毁应用 */ destory(): void; /** * 子应用加载失败 * @param error 失败原因 */ error(error: Error): void; } type IApp = App; type MustardName = string; type MustardURL = string; declare enum IAppStatus { create = 0,// 初始化 loading = 1,// 数据加载中 mount = 2,// dom节点 挂载完成阶段 unmount = 3,// dom节点 卸载 destory = 4,// 销毁应用 error = -100 } type IAppStatusCN = keyof typeof IAppStatus; interface SpurceValue { code: string; isExternal?: boolean; } interface IAppConstructor { name: MustardName; url: MustardURL; container: MustardApp; } declare global { interface HTMLElementTagNameMap { 'mustard-app': MustardApp; } interface Window { mustardAppInfos: { currentReadDocMAppName?: MustardName; appInstanceMap: Map<MustardName, IApp>; getAppProxyWindow: (appName: MustardName) => IApp['sandbox']['proxyWindow']; }; microApp: EventCenterMicroApp; } } type BindMethod = string; type TCallback = (value: unknown, oldValue: unknown, source: MustardName) => void; type TLifeCallback = (key: MustardName) => void; type TDataChangeCallback = (key: MustardName, data: unknown) => void; declare function setGlobalEvents(options: Partial<Record<IAppStatusCN$1, TLifeCallback>> | Partial<Record<'dataChange', TDataChangeCallback>>): void; declare class EventCenterMicroApp { name: MustardName$1; constructor(name: MustardName$1); addDataListener(fn: TCallback): void; removeDataListener(fn: TCallback): void; clearDateListener(): void; /** * 发送data数据修改事件 * @param data 发送数据 */ dispatch(data: unknown): void; /** * 发送自定义事件 * @param method */ dispatchCustomize(method: BindMethod, data: unknown): void; } declare class EventCenterMicorLife { private name; constructor(name: MustardName$1); /** * 发送生命周期 * @param state 子应用的生命周期 */ dispatchLife(state: IAppStatus$1): void; } export { MustardApp, defineMustardAppElement, setGlobalEvents };