UNPKG

mustard-app

Version:

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

1 lines 121 kB
{"version":3,"file":"bundle.mjs","sources":["../src/typings.ts","../src/utils/tools.ts","../src/communication/base.ts","../src/communication/index.ts","../src/proxy/proxyEventListener.ts","../src/global/index.ts","../src/utils/scopedcss.ts","../src/utils/index.ts","../src/proxy/proxyDocument.ts","../src/proxy/proxyHistory.ts","../src/proxy/proxyLocation.ts","../src/proxy/proxyStorage.ts","../src/proxy/sandBox.ts","../node_modules/.pnpm/uuid@9.0.1/node_modules/uuid/dist/esm-browser/rng.js","../node_modules/.pnpm/uuid@9.0.1/node_modules/uuid/dist/esm-browser/stringify.js","../node_modules/.pnpm/uuid@9.0.1/node_modules/uuid/dist/esm-browser/native.js","../node_modules/.pnpm/uuid@9.0.1/node_modules/uuid/dist/esm-browser/v4.js","../src/utils/source.ts","../src/utils/log.ts","../src/app.ts","../src/check/urlCheck.ts","../src/element.ts"],"sourcesContent":["import App from './app';\r\nimport { EventCenterMicroApp } from './communication';\r\nimport { MustardApp } from './element';\r\n\r\nexport type TFunction = (...arg:unknown[]) => unknown\r\n\r\nexport const AppName = 'mustard-app'; // 元素名\r\n\r\nexport const LocationPrefix = 'mApp-'; // location path 前缀\r\n\r\nexport type IApp = App;\r\n\r\nexport const MainMustardApp = 'main'; // 基座标识(禁用)\r\n\r\nexport type MustardName = string; // 子应用标识, 不能为 MainMustardApp\r\n\r\nexport type MustardURL = string; // `http://${string}`|`https://${string}`; // 应用开启地址\r\n\r\n// 子应用状态\r\nexport enum IAppStatus {\r\n create = 0, // 初始化\r\n loading = 1, // 数据加载中\r\n mount = 2, // dom节点 挂载完成阶段\r\n unmount = 3, // dom节点 卸载\r\n destory = 4, // 销毁应用\r\n error = -100 // 子应用异常\r\n}\r\n\r\nexport type IAppStatusCN = keyof typeof IAppStatus;\r\n\r\nexport const APPStAtUSCNKEYS: IAppStatusCN[] = ['create', 'loading', 'mount', 'unmount', 'destory', 'error'];\r\n\r\nexport interface SpurceValue {\r\n code: string; // 具体代码\r\n isExternal?: boolean; // 是否远程资源\r\n}\r\n\r\nexport interface IAppConstructor {\r\n name: MustardName;\r\n url: MustardURL; \r\n container: MustardApp;\r\n} \r\n\r\ndeclare global{\r\n interface HTMLElementTagNameMap{\r\n 'mustard-app': MustardApp\r\n }\r\n interface Window{\r\n mustardAppInfos: { // 微应用实例\r\n currentReadDocMAppName?: MustardName;\r\n appInstanceMap: Map<MustardName, IApp>; // 当前所有子应用实例\r\n getAppProxyWindow: (appName: MustardName) => IApp['sandbox']['proxyWindow'];\r\n };\r\n // 子应用通讯方法\r\n microApp: EventCenterMicroApp\r\n }\r\n}\r\n\r\nexport abstract class AMustardApp extends HTMLElement {\r\n url:MustardURL; // 子应用资源地址\r\n name:MustardName; // 子应用标识\r\n\r\n // 组件刷新\r\n abstract reload : void;\r\n}","import { State } from '../proxy/proxyHistory';\r\nimport { APPStAtUSCNKEYS, IAppStatusCN } from '../typings';\r\n\r\ntype Tfunction = (...arg:unknown[])=>unknown;\r\n\r\nexport function isString (value:unknown):value is string {\r\n return typeof value === 'string';\r\n}\r\n\r\nexport function isNumber (value:unknown):value is number {\r\n return typeof value === 'number';\r\n}\r\n\r\nexport function isBoolean (value:unknown):value is boolean {\r\n return typeof value === 'boolean';\r\n}\r\n\r\nexport function isFunction (value:unknown):value is Tfunction {\r\n return value instanceof Function;\r\n}\r\n\r\nexport function isObject (value:unknown): value is ObjectConstructor {\r\n return value instanceof Object;\r\n}\r\n\r\nexport function isURL (value:unknown): value is URL {\r\n return value instanceof URL;\r\n}\r\n\r\n/**\r\n * 是否是子应用的state\r\n * @param value \r\n * @returns \r\n */\r\nexport function isMustardState (value: unknown):value is State {\r\n return (value as State)?.isMustard === 'MustardApp';\r\n}\r\n\r\n/**\r\n * 是否是生命周期的key\r\n * @param value \r\n * @returns \r\n */\r\nexport function isIAppStatusKey (value: unknown):value is IAppStatusCN {\r\n return APPStAtUSCNKEYS.includes(value as IAppStatusCN);\r\n}\r\n\r\n/**\r\n * 是否是远程类型资源\r\n * @param dom \r\n * @returns boolean\r\n */\r\nexport function isRemotezElement (dom: Element):dom is HTMLImageElement|HTMLVideoElement|HTMLAudioElement|HTMLSourceElement {\r\n return dom instanceof HTMLImageElement || dom instanceof HTMLVideoElement || dom instanceof HTMLAudioElement || dom instanceof HTMLSourceElement;\r\n}\r\n\r\n/**\r\n * 是否是相对地址\r\n * @param src \r\n * @returns boolean\r\n */\r\nexport function isRelativePath (src: string = '') {\r\n return /^(\\.){0,2}\\//.test(src);\r\n}","import { IAppStatus, IAppStatusCN, MustardName } from '../typings';\r\nimport { isFunction } from '../utils/tools';\r\n\r\nexport type EventName = MustardName; // 用于区分子应用\r\nexport type BindMethod = string;\r\n\r\nexport type TCallback = (value: unknown, oldValue:unknown, source:MustardName) => void;\r\nexport type TLifeCallback = (key: MustardName) => void;\r\nexport type TDataChangeCallback = (key: MustardName, data:unknown) => void;\r\n\r\ntype EventGlobalLifeKey = `globalLife_${IAppStatusCN}`; // 全局生命周期key\r\ntype EventGlobalDataChangeKey = 'globalDataChange'; // 全局prop修改key\r\n\r\ntype EventDataKey = `data_${EventName}`; // prop注入key \r\ntype EventDataChangeKey = `dataChange_${EventName}`; // prop修改key\r\ntype EventLifeKey = `life_${EventName}_${IAppStatusCN}`; // 生命周期key\r\ntype EventBindKey = `bind_${EventName}_${BindMethod}`; // 自定义事件key\r\n\r\nexport type EventKey = |EventDataKey\r\n |EventDataChangeKey\r\n |EventLifeKey\r\n |EventBindKey\r\n |EventGlobalLifeKey\r\n |EventGlobalDataChangeKey;\r\n\r\nexport interface EventValue {\r\n data: unknown, // 订阅的数据\r\n sourceOfData: MustardName, // 数据的来源\r\n assignment: boolean, // 是否已经赋值过\r\n callbacks: Set<TCallback>,\r\n repeatSend: WeakSet<TCallback>, // 多次dispatch相同的data,只有第一次生效, 用于去重\r\n}\r\n\r\n// 发布订阅系统中心\r\nexport class EventCenter {\r\n eventList : Map<EventKey, EventValue> = new Map();\r\n\r\n /**\r\n * 初始化\r\n * @param name 事件名\r\n * @param options 事件配置\r\n */\r\n private initEvent (name:EventKey, options:Partial<EventValue> = {}) {\r\n this.eventList.set(name, {\r\n data: undefined,\r\n sourceOfData: undefined,\r\n assignment: false,\r\n callbacks: new Set(),\r\n repeatSend: new WeakSet(),\r\n ...options\r\n });\r\n }\r\n\r\n /**\r\n * 订阅事件\r\n * @param name 事件名\r\n * @param fn 事件\r\n * @param param2.immediately 是否立即执行(对应消息dispatch过)\r\n * @param param2.repeatSend 多次dispatch相同的data,只有第一次生效\r\n */\r\n on (name:EventKey, fn: TCallback, { immediately, repeatSend }:{immediately?: boolean, repeatSend?: boolean} = {}) {\r\n if(!isFunction(fn)) {\r\n return;\r\n }\r\n const events = this.eventList.get(name);\r\n if(!events) {\r\n this.initEvent(name, {\r\n callbacks: new Set([fn]),\r\n repeatSend: new WeakSet(repeatSend ? [fn] : [])\r\n });\r\n }else{\r\n events.callbacks.add(fn);\r\n repeatSend && events.repeatSend.add(fn);\r\n immediately && events.assignment && fn(events.data, undefined, events.sourceOfData);\r\n }\r\n }\r\n\r\n /**\r\n * 注销订阅事件\r\n * @param name 事件名\r\n * @param fn 事件 不传递全部清空\r\n */\r\n off (name:EventKey, fn?: TCallback) {\r\n const events = this.eventList.get(name);\r\n if(events) {\r\n fn ? events.callbacks.delete(fn) : events.callbacks.clear();\r\n }\r\n }\r\n\r\n /**\r\n * 发布消息\r\n * @param name 事件名\r\n * @param source 那个应用发送的消息\r\n * @param data 数据\r\n */\r\n dispatch (name:EventKey, source: MustardName, data?:unknown) {\r\n const events = this.eventList.get(name);\r\n const isSameData = events?.data === data;\r\n if(events) {\r\n const oldData = events.data;\r\n events.assignment = true;\r\n for (const callback of events.callbacks) {\r\n // 存在且值等于上一次不执行,其他的都执行\r\n if(!(events.repeatSend.has(callback) && isSameData)) {\r\n callback(data, oldData, source); \r\n }\r\n }\r\n events.data = data;\r\n events.assignment = true;\r\n events.sourceOfData = source;\r\n }else{\r\n this.initEvent(name, {\r\n data: data,\r\n sourceOfData: source,\r\n assignment: true,\r\n callbacks: new Set([])\r\n });\r\n }\r\n }\r\n}\r\n\r\nexport function getEventGlobalLifeKeyByValue (value: IAppStatusCN):EventGlobalLifeKey {\r\n return `globalLife_${value}`; \r\n}\r\nexport function getEventGlobalDataChangeKey ():EventGlobalDataChangeKey {\r\n return 'globalDataChange'; \r\n}\r\nexport function getEventDataKey (name: EventName):EventDataKey {\r\n return `data_${name}`;\r\n}\r\nexport function getEventDataChangeKey (name: EventName):EventDataChangeKey {\r\n return `dataChange_${name}`;\r\n}\r\nexport function getEventLifeKeyByKey (name: EventName, key: IAppStatusCN):EventLifeKey {\r\n return `life_${name}_${key}`; \r\n}\r\nexport function getEventLifeKeyByValue (name: EventName, value: IAppStatus):EventLifeKey {\r\n return `life_${name}_${IAppStatus[value]}` as EventLifeKey; \r\n}\r\nexport function getEventBindKey (name: EventName, method: BindMethod):EventBindKey {\r\n return `bind_${name}_${method}`; \r\n}","import { MainMustardApp, IAppStatus, IAppStatusCN, MustardName } from '@typings';\r\nimport { isFunction, isIAppStatusKey } from '../utils/tools';\r\nimport { TDataChangeCallback, TLifeCallback, BindMethod, EventCenter, TCallback, getEventLifeKeyByValue, getEventBindKey, getEventDataChangeKey, getEventDataKey, getEventGlobalDataChangeKey, getEventGlobalLifeKeyByValue, getEventLifeKeyByKey } from './base';\r\n\r\nexport { getEventLifeKeyByValue, getEventBindKey, getEventDataChangeKey, getEventDataKey, getEventGlobalDataChangeKey, getEventGlobalLifeKeyByValue, getEventLifeKeyByKey };\r\n\r\n// 订阅实例\r\nconst eventCenter = new EventCenter();\r\n\r\n// 全局事件配置注册\r\ntype TGlobalEventConfigKeys = IAppStatusCN|'dataChange';\r\n\r\nexport function setGlobalEvents (options:Partial<Record<IAppStatusCN, TLifeCallback>>|Partial< Record<'dataChange', TDataChangeCallback>>) {\r\n (Reflect.ownKeys(options) as TGlobalEventConfigKeys[]).forEach(key => {\r\n if(isFunction(options[key])) {\r\n if(key === 'dataChange') {\r\n eventCenter.off(getEventGlobalDataChangeKey());\r\n eventCenter.on(getEventGlobalDataChangeKey(), options[key]);\r\n }else if(isIAppStatusKey(key)) {\r\n eventCenter.off(getEventGlobalLifeKeyByValue(key));\r\n eventCenter.on(getEventGlobalLifeKeyByValue(key), options[key]);\r\n }\r\n }\r\n });\r\n}\r\n\r\nexport function globalDataChangeDispatch (name: MustardName, data:unknown) {\r\n eventCenter.dispatch(getEventGlobalDataChangeKey(), name, data);\r\n \r\n}\r\nexport function globalLifeDispatch (key:IAppStatusCN, name: MustardName) {\r\n eventCenter.dispatch(getEventGlobalLifeKeyByValue(key), name);\r\n \r\n}\r\n\r\n// 基座通讯集合\r\nexport class EventCenterBaseApp {\r\n \r\n /**\r\n * 向子应用发送data数据\r\n * @param name 子应用名字\r\n * @param data 发送数据\r\n */\r\n dispatch (name:MustardName, data:unknown) {\r\n eventCenter.dispatch(getEventDataKey(name), MainMustardApp, data);\r\n }\r\n\r\n /**\r\n * 订阅props修改事件\r\n * @param name 子应用名字\r\n * @param fn 事件\r\n */\r\n onData (name:MustardName, fn: TCallback) {\r\n eventCenter.on(getEventDataChangeKey(name), fn);\r\n }\r\n\r\n /**\r\n * 订阅生命事件\r\n * @param name 子应用名字\r\n * @param life 生命周期映射\r\n * @param fn 事件\r\n */\r\n onLife (name:MustardName, life:IAppStatusCN, fn: TCallback) {\r\n eventCenter.on(getEventLifeKeyByKey(name, life), fn);\r\n }\r\n\r\n /**\r\n * 订阅自定义事件\r\n * @param name 子应用名字\r\n * @param method 自定义方法名\r\n * @param fn 事件\r\n */\r\n onCustomize (name:MustardName, method:BindMethod, fn: TCallback) {\r\n eventCenter.on(getEventBindKey(name, method), fn);\r\n }\r\n\r\n /**\r\n * 取消订阅生命事件\r\n * @param name 子应用名字\r\n * @param fn 事件\r\n */\r\n offData (name:MustardName, fn: TCallback) {\r\n eventCenter.off(getEventDataChangeKey(name), fn);\r\n }\r\n\r\n /**\r\n * 取消订阅生命事件\r\n * @param name 子应用名字\r\n * @param life 生命周期映射\r\n * @param fn 事件\r\n */\r\n offLife (name:MustardName, life:IAppStatusCN, fn: TCallback) {\r\n eventCenter.off(getEventLifeKeyByKey(name, life), fn);\r\n }\r\n\r\n /**\r\n * 取消订阅自定义事件\r\n * @param name 子应用名字\r\n * @param method 自定义方法名\r\n * @param fn 事件\r\n */\r\n offCustomize (name:MustardName, method:BindMethod, fn: TCallback) {\r\n eventCenter.off(getEventBindKey(name, method), fn);\r\n }\r\n}\r\n\r\n// 子应用通讯类(注入到子应用的window.microApp)\r\nexport class EventCenterMicroApp {\r\n name:MustardName;\r\n\r\n constructor (name:MustardName) {\r\n this.name = name;\r\n }\r\n \r\n // 添加data事件监听\r\n addDataListener (fn:TCallback) {\r\n eventCenter.on(getEventDataKey(this.name), fn, { immediately: true, repeatSend: true });\r\n }\r\n\r\n // 解除data监听\r\n removeDataListener (fn:TCallback) {\r\n fn && eventCenter.off(getEventDataKey(this.name), fn);\r\n }\r\n \r\n // 解除所以的data监听事件\r\n clearDateListener () {\r\n eventCenter.off(getEventDataKey(this.name));\r\n }\r\n\r\n /**\r\n * 发送data数据修改事件\r\n * @param data 发送数据\r\n */\r\n dispatch (data:unknown) {\r\n globalDataChangeDispatch(this.name, data);\r\n eventCenter.dispatch(getEventDataChangeKey(this.name), this.name, data);\r\n }\r\n\r\n /**\r\n * 发送自定义事件\r\n * @param method \r\n */\r\n dispatchCustomize (method:BindMethod, data:unknown) {\r\n eventCenter.dispatch(getEventBindKey(this.name, method), this.name, data);\r\n }\r\n}\r\n\r\n// 子应用生命周期通讯类(内部调用)\r\nexport class EventCenterMicorLife {\r\n private name:MustardName;\r\n\r\n constructor (name:MustardName) {\r\n this.name = name;\r\n }\r\n \r\n /**\r\n * 发送生命周期\r\n * @param state 子应用的生命周期\r\n */\r\n public dispatchLife (state:IAppStatus) {\r\n globalLifeDispatch(IAppStatus[state] as IAppStatusCN, this.name);\r\n eventCenter.dispatch(getEventLifeKeyByValue(this.name, state), this.name);\r\n }\r\n}","export type IEventLisParameters = Parameters<typeof addEventListener>;\r\nexport type IEventLisReturn = ReturnType<typeof addEventListener>;\r\n\r\ntype IType = IEventLisParameters[0];\r\ntype Ilistener = IEventLisParameters[1];\r\ntype Ioptions = IEventLisParameters[2];\r\n\r\nexport class ProxyEventListener {\r\n eventLis = new Map<IType, Map<Ilistener, Ioptions>>();\r\n\r\n // 添加事件\r\n addEventListener (type: IType, listener: Ilistener, options: Ioptions) {\r\n if(!this.eventLis.has(type)) {\r\n this.eventLis.set(type, new Map());\r\n }\r\n const listeners = this.eventLis.get(type);\r\n listeners?.set(listener, options);\r\n return window.addEventListener(type, listener, options);\r\n }\r\n\r\n // 删除事件\r\n removeEventListener (type: IType, listener: Ilistener, options: Ioptions) {\r\n if(!this.eventLis.has(type)) {\r\n this.eventLis.set(type, new Map());\r\n }\r\n const listeners = this.eventLis.get(type);\r\n if(listeners?.get(listener) === options) {\r\n listeners?.delete(listener);\r\n }\r\n return window.removeEventListener(type, listener, options);\r\n }\r\n\r\n // 全部清除事件\r\n clear () {\r\n Array.from(this.eventLis.keys()).forEach(key => {\r\n const listeners = this.eventLis.get(key);\r\n if(listeners instanceof Map) {\r\n Array.from(listeners.keys()).forEach(listener => {\r\n window.removeEventListener(key, listener, listeners.get(listener));\r\n });\r\n }\r\n });\r\n }\r\n}","import { IApp, MustardName } from '../typings';\r\n\r\ntype TMustardAppInfos = typeof window['mustardAppInfos'];\r\n\r\nexport const mustardAppInfos:TMustardAppInfos = window.mustardAppInfos = window?.mustardAppInfos ?? {\r\n currentReadDocMAppName: '', // 子应用临时标识\r\n appInstanceMap: new Map<MustardName, IApp>(), // 当前子应用的实例\r\n getAppProxyWindow (appName: MustardName) {\r\n // eslint-disable-next-line no-use-before-define\r\n const app = getAppFromInstance(appName);\r\n if(app) {\r\n return app.sandbox.proxyWindow;\r\n }\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * 获取所以实例app.name\r\n * @returns IApp[]\r\n */\r\nexport function getAllApp () {\r\n return Array.from(mustardAppInfos.appInstanceMap.keys());\r\n}\r\n\r\n/**\r\n * 实例写入缓存\r\n * @param name 子应用标识\r\n * @param app 实例\r\n * @returns \r\n */\r\nexport function addInstance (name:MustardName, app: IApp) {\r\n return mustardAppInfos.appInstanceMap.set(name, app);\r\n}\r\n\r\n/**\r\n * 删除实例\r\n * @param name 子应用标识\r\n */\r\nexport function removeInstance (name:MustardName) {\r\n mustardAppInfos.appInstanceMap.delete(name);\r\n}\r\n\r\n/**\r\n * 获取实例\r\n * @param name 子应用标识\r\n * @returns IApp\r\n */\r\nexport function getAppFromInstance (name:MustardName) {\r\n return mustardAppInfos.appInstanceMap.get(name);\r\n}\r\n\r\n/**\r\n * 子应用是否存在\r\n * @param name 子应用标识\r\n * @returns \r\n */\r\nexport function appIsExist (name:MustardName) {\r\n return mustardAppInfos.appInstanceMap.has(name);\r\n}\r\n\r\n\r\n/**\r\n * 设置子应用标识\r\n * 用于后续同步步骤的消费\r\n * e.g document.querySelector\r\n * @param appName \r\n */\r\nexport function setReadDocumentName (appName:MustardName) {\r\n return mustardAppInfos.currentReadDocMAppName = appName;\r\n}\r\n\r\n/**\r\n * 消费标识\r\n * @returns \r\n */\r\nexport function consumption () {\r\n const name = mustardAppInfos.currentReadDocMAppName;\r\n mustardAppInfos.currentReadDocMAppName = '';\r\n return name;\r\n}","import { getCompletePath } from '.';\r\n\r\nlet templateStyle:HTMLStyleElement;\r\n\r\nfunction scopedStyleRule (rule:CSSStyleRule, prefix:string, path?:string) {\r\n const { selectorText, cssText } = rule;\r\n let str = '';\r\n \r\n // 处理选择器\r\n if(/^((html[\\s>~,]+body)|(html|body|:root))$/.test(selectorText)) {\r\n // 处理顶层选择器,如 body,html 都转换为 micro-app[name=xxx]\r\n str = cssText.replace(/^((html[\\s>~,]+body)|(html|body|:root))/, prefix) + '\\n';\r\n }else {\r\n // 选择器添加前缀\r\n str = cssText.replace(/^[\\s\\S]*{/, cssHead => {\r\n return cssHead.replace(/(^|,)([^,]+)/g, (_, $1, $2)=>{\r\n if(/^[\\s]*((html|body|:root)|(html[\\s>~]+body))/.test($2)) {\r\n return `${$1} ${$2.replace(/^[\\s]*((html[\\s>~]+body)|(html|body|:root))/, prefix)}` + '\\n';\r\n }\r\n return `${$1} ${prefix} ${$2} \\n`;\r\n });\r\n });\r\n \r\n }\r\n\r\n // 处理样式里的相对地址\r\n return str.replace(/url\\(((\".+?\")|('.+?')|(.+?))\\)/gi, (_, url) => {\r\n const src = url.replace(/[\"']/g, '');\r\n return `url(${getCompletePath(src, path)})`;\r\n });\r\n}\r\n\r\n// 处理媒体查询和样式支持查询\r\nfunction scopedStyleRuleOther (rule:CSSMediaRule|CSSSupportsRule, prefix:string, packName:string, path?:string) {\r\n // eslint-disable-next-line no-use-before-define\r\n const rules = scopedStyleRules(rule.cssRules, prefix, path);\r\n return `@${packName} ${rule.conditionText} {\\n${rules}\\n}`;\r\n}\r\n\r\nfunction scopedStyleRules (cssRules:CSSRuleList, prefix:string, path?:string) {\r\n let rules = '';\r\n if(cssRules?.length) {\r\n Array.from(cssRules).forEach((cssRule) => {\r\n switch (cssRule.type) {\r\n case 1: // STYLE_RULE\r\n rules += scopedStyleRule(cssRule as CSSStyleRule, prefix, path) + '\\n';\r\n break;\r\n case 4: // MEDIA_RULE 媒体查询\r\n rules += scopedStyleRuleOther(cssRule as CSSMediaRule, prefix, 'media', path) + '\\n';\r\n break;\r\n case 12: // SUPPORTS_RULE 样式支持\r\n rules += scopedStyleRuleOther(cssRule as CSSSupportsRule, prefix, 'supports', path) + '\\n';\r\n break;\r\n default:\r\n rules += cssRule.cssText;\r\n break;\r\n }\r\n });\r\n \r\n }\r\n return rules;\r\n}\r\n\r\nexport function scopedCSSTextContent (textContent:string, appName:string, path?:string) {\r\n const prefix = `mustard-app[name='${appName}'] `; // 样式前缀\r\n\r\n if(!templateStyle) {\r\n templateStyle = document.createElement('style');\r\n document.body.appendChild(templateStyle);\r\n if(templateStyle?.sheet) {\r\n templateStyle.sheet.disabled = false; // 样式标记不可用\r\n }\r\n }\r\n\r\n if(textContent && templateStyle.sheet?.cssRules) {\r\n templateStyle.textContent = textContent;\r\n const _textContent = scopedStyleRules(templateStyle.sheet.cssRules, prefix, path);\r\n templateStyle.textContent = '';\r\n\r\n return _textContent;\r\n }\r\n return textContent;\r\n}\r\n\r\nexport function scopedCSS (styleEle:HTMLElement, appName:string, path?:string) {\r\n const s = new Date();\r\n\r\n styleEle.textContent = scopedCSSTextContent(styleEle.textContent, appName, path);\r\n console.log('============', new Date().getTime() - s.getTime());\r\n\r\n}\r\n\r\n\r\n","import { consumption, getAppFromInstance, mustardAppInfos } from '../global';\r\nimport { LocationPrefix, MustardName, MustardURL, TFunction } from '../typings';\r\nimport { scopedCSSTextContent } from './scopedcss';\r\nimport { isFunction, isRelativePath, isRemotezElement } from './tools';\r\n\r\n/**\r\n * 获取虚拟路由key\r\n * @param appName \r\n * @returns \r\n */\r\nexport function getLocationNameByAppName (appName:MustardName) {\r\n return LocationPrefix + appName;\r\n}\r\n\r\n/**\r\n * 根据相对地址和当前页面地址返回具体资源路径\r\n * @param relativePath 相对地址\r\n * @param absolutePath 当前页面地址\r\n * @returns \r\n */\r\nexport function getCompletePath (relativePath: string, absolutePath?: string) {\r\n if (!absolutePath || !isRelativePath(relativePath)) return relativePath;\r\n return new URL(relativePath, absolutePath).href;\r\n}\r\n\r\n/**\r\n * 请求资源\r\n * @param relativePath 相对地址\r\n * @param absolutePath 当前页面地址\r\n * @returns \r\n */\r\nexport function fetchSource (relativePath: string, absolutePath?: string) {\r\n return fetch(getCompletePath(relativePath, absolutePath)).then((res) => {\r\n return res.text();\r\n });\r\n}\r\n\r\n/**\r\n * 监听Dom变化\r\n * @param dom 需要监听的dom元素\r\n * @param config 需要监听的范围 e.g 属性变动/子节点变动\r\n * @param callback 监听变动回调函数\r\n */\r\nexport function mutationObserver (dom:Element, config:MutationObserverInit, callback:MutationCallback) {\r\n const observer = new MutationObserver((mutationsList, observer) => {\r\n observer.disconnect();\r\n callback(mutationsList, observer);\r\n observer.observe(dom, config);\r\n });\r\n\r\n // 以上述配置开始观察目标节点\r\n observer.observe(dom, config);\r\n\r\n return observer;\r\n}\r\n\r\n/**\r\n * 处理子应用的dom\r\n * 1. 加上子应用标识 appName\r\n * 2. 修改ownerDocument,代理到proxydocument\r\n * 3. 特殊dom,特殊处理 e.g 1. 远程资源src 2. 动态style处理(实时加入前缀)\r\n * @param dom \r\n * @param _appName 子应用标识 \r\n * @returns \r\n */\r\nexport function handleDom<T extends Element> (dom:T, _appName?:MustardName):T {\r\n if(!dom) return dom;\r\n const appName = _appName ?? consumption();\r\n\r\n if(appName && !(dom as unknown as {appName?:string})?.appName) {\r\n const app = getAppFromInstance(appName);\r\n const proxyWindow = mustardAppInfos.getAppProxyWindow(appName);\r\n\r\n const config:PropertyDescriptorMap = {\r\n // 1. 子应用标识\r\n // 2. 判断是否处理过的元素\r\n appName: {\r\n value: appName\r\n },\r\n ownerDocument: {\r\n enumerable: true,\r\n get () {\r\n return proxyWindow?.document ?? document;\r\n }\r\n }\r\n };\r\n\r\n // 远程资源地址适配\r\n if(isRemotezElement(dom)) {\r\n mutationObserver(dom, { attributes: true, attributeFilter: ['src'] }, function ([mutations] = []) {\r\n if(mutations.type === 'attributes') {\r\n const target = mutations.target as HTMLImageElement;\r\n target.src = getCompletePath(target.getAttribute('src'), app.url);\r\n }\r\n });\r\n }\r\n\r\n // 动态style适配\r\n if(dom instanceof HTMLStyleElement) {\r\n mutationObserver(dom, { childList: true }, function ([mutations] = []) {\r\n if(mutations.type === 'childList') {\r\n mutations.target.textContent = scopedCSSTextContent(mutations.target.textContent, appName);\r\n }\r\n });\r\n }\r\n return Object.defineProperties(dom, config);\r\n }\r\n\r\n return dom;\r\n}\r\n\r\n/**\r\n * 处理选择器\r\n * e.g.\r\n * 1. head -> mustard-app-head\r\n * 2. body -> mustard-app-body\r\n * @param selectors \r\n */\r\nexport function handleSelectors (selectors:string) {\r\n if(!selectors) return '';\r\n if(selectors?.trim() === 'head') return 'mustard-app-head';\r\n if(selectors?.trim() === 'body') return 'mustard-app-body';\r\n return selectors.split(',').map(\r\n _selector => _selector.replace(/(^|,|\\s)(head|body)([^a-zA-Z]|$)/g, function (test) {\r\n return test.replace(/(head|body)/, (_, $1)=> `mustard-app-${$1}`);\r\n })\r\n ).join(',');\r\n}\r\n\r\n/**\r\n * 获取相对地址\r\n * 根据子应用的appName,从loaction.search 上读取对应数据\r\n * @param appName \r\n * @returns \r\n */\r\nexport function getPath (appName:MustardName) {\r\n const search = location.search;\r\n const searchParams = new URLSearchParams(search);\r\n const href = searchParams.get(`${LocationPrefix}${appName}`) ?? '/';\r\n return decodeURIComponent(href);\r\n}\r\n\r\n/**\r\n * 获取地址的URL对象\r\n * @param appName \r\n * @param baseUrl \r\n * @returns \r\n */\r\nexport function getURL (appName:MustardName, baseUrl:MustardURL) {\r\n return new URL(getPath(appName), baseUrl);\r\n}\r\n\r\n/**\r\n * \r\n * @param path 子应用地址路径\r\n * @param appName 子应用标识\r\n * @param location // 父应用或基座location\r\n * @returns \r\n */\r\nexport function getNewPathToMustard (path:string, appName:MustardName) {\r\n const { pathname: pathnameFromTop, search: searchFromTop = '', hash: hashFromTop } = location; \r\n \r\n const SearchParams = new URLSearchParams(searchFromTop);\r\n\r\n SearchParams.set(LocationPrefix + appName, encodeURIComponent(path));\r\n\r\n const searchParams = SearchParams.toString();\r\n return `${pathnameFromTop}${searchParams ? '?' + searchParams : ''}${hashFromTop}`;\r\n}\r\n\r\n/**\r\n * 异步下一微任务运行\r\n * @param fn 待运行的方法\r\n */\r\nexport function nextTick (fn: TFunction) {\r\n Promise.resolve().then(() => {\r\n isFunction(fn) && fn();\r\n });\r\n}","import { MustardName } from '../typings';\r\nimport { consumption, getAppFromInstance, setReadDocumentName } from '../global';\r\nimport { handleDom, handleSelectors } from '../utils';\r\n\r\nexport function proxyDocument (appName:MustardName) {\r\n return new Proxy(document, {\r\n get: (target, key) => {\r\n if(key === 'defaultView') {\r\n return getAppFromInstance(appName)?.sandbox?.proxyWindow;\r\n }\r\n // 设置要消费的子应用标识\r\n if(target[key] instanceof Function) {\r\n return function (...args) {\r\n setReadDocumentName(appName);\r\n return target[key].call(target, ...args);\r\n };\r\n }\r\n return target[key];\r\n }\r\n });\r\n}\r\n\r\nexport function changeDomPropety () {\r\n // 修改Document原型链\r\n const createElement = Document.prototype.createElement;\r\n const createElementNS = Document.prototype.createElementNS;\r\n const createTextNode = Document.prototype.createTextNode;\r\n const createComment = Document.prototype.createComment;\r\n const createDocumentFragment = Document.prototype.createDocumentFragment;\r\n const caretRangeFromPoint = Document.prototype.caretRangeFromPoint;\r\n \r\n Document.prototype.createElement = function (tagName:string, ...options) {\r\n return handleDom(createElement.call(this, tagName, ...options));\r\n };\r\n Document.prototype.createElementNS = function (...options) {\r\n return handleDom(createElementNS.call(this, ...options));\r\n };\r\n Document.prototype.createTextNode = function (...options) {\r\n return handleDom(createTextNode.call(this, ...options));\r\n };\r\n Document.prototype.createComment = function (...options) {\r\n return handleDom(createComment.call(this, ...options));\r\n };\r\n Document.prototype.createDocumentFragment = function (...options) {\r\n return handleDom(createDocumentFragment.call(this, ...options));\r\n };\r\n caretRangeFromPoint && (Document.prototype.caretRangeFromPoint = function (...options) {\r\n return handleDom(caretRangeFromPoint.call(this, ...options));\r\n });\r\n\r\n const getElementById = Document.prototype.getElementById;\r\n const querySelector = Document.prototype.querySelector;\r\n const querySelectorAll = Document.prototype.querySelectorAll;\r\n const getElementsByClassName = Document.prototype.getElementsByClassName;\r\n const getElementsByTagName = Document.prototype.getElementsByTagName;\r\n const getElementsByName = Document.prototype.getElementsByName;\r\n\r\n Document.prototype.getElementById = function (selectors:string) {\r\n const appName = consumption();\r\n if(appName) {\r\n const miniRootDom = getAppFromInstance(appName)?.container;\r\n const ele = miniRootDom?.querySelector(`#${selectors}`); \r\n return handleDom(ele, appName); // 已经消费过唯一标识,需要手动传递标识\r\n }\r\n return getElementById.call(this, selectors);\r\n };\r\n Document.prototype.querySelector = function (selectors:string) {\r\n const appName = consumption();\r\n if(appName) {\r\n const miniRootDom = getAppFromInstance(appName)?.container;\r\n const ele = miniRootDom?.querySelector(handleSelectors(selectors));\r\n return handleDom(ele, appName); // 已经消费过唯一标识,需要手动传递标识\r\n }\r\n return querySelector.call(this, selectors);\r\n };\r\n Document.prototype.querySelectorAll = function (selectors:string) {\r\n const appName = consumption();\r\n if(appName) {\r\n const miniRootDom = getAppFromInstance(appName)?.container;\r\n const eles = miniRootDom?.querySelectorAll(handleSelectors(selectors));\r\n Array.from(eles).forEach((ele)=>{\r\n handleDom(ele, appName); // 已经消费过唯一标识,需要手动传递标识\r\n });\r\n return eles;\r\n }\r\n return querySelectorAll.call(this, selectors);\r\n };\r\n Document.prototype.getElementsByClassName = function (selectors:string) {\r\n const appName = consumption();\r\n if(appName) {\r\n const miniRootDom = getAppFromInstance(appName)?.container;\r\n const eles = miniRootDom?.getElementsByClassName(selectors);\r\n Array.from(eles).forEach((ele)=>{\r\n handleDom(ele, appName); // 已经消费过唯一标识,需要手动传递标识\r\n });\r\n return eles;\r\n }\r\n return getElementsByClassName.call(this, selectors);\r\n };\r\n Document.prototype.getElementsByTagName = function (selectors:string) {\r\n const appName = consumption();\r\n if(appName) {\r\n const miniRootDom = getAppFromInstance(appName)?.container;\r\n const eles = miniRootDom?.getElementsByTagName(handleSelectors(selectors));\r\n Array.from(eles).forEach((ele)=>{\r\n handleDom(ele, appName); // 已经消费过唯一标识,需要手动传递标识\r\n });\r\n return eles;\r\n }\r\n return getElementsByTagName.call(this, selectors);\r\n };\r\n Document.prototype.getElementsByName = function (name:string) {\r\n const appName = consumption();\r\n if(appName) {\r\n const miniRootDom = getAppFromInstance(appName)?.container;\r\n const eles = miniRootDom?.querySelectorAll(`[name=${name}]`);\r\n Array.from(eles).forEach((ele)=>{\r\n handleDom(ele, appName); // 已经消费过唯一标识,需要手动传递标识\r\n });\r\n return eles;\r\n }\r\n return getElementsByName.call(this, name);\r\n };\r\n}","/* eslint-disable no-use-before-define */\r\nimport { getAllApp, getAppFromInstance } from '../global';\r\nimport { MainMustardApp, MustardName, MustardURL } from '../typings';\r\nimport { getLocationNameByAppName } from '../utils';\r\nimport { isFunction, isMustardState, isURL } from '../utils/tools';\r\n\r\ntype ChangeState = (_state:unknown, _unused:string, _url:string | URL, _isMustard?:boolean) => void;\r\n\r\nexport interface MustardStateOptions {\r\n origin?:MustardURL, // document 来源\r\n flushed?:boolean // 是否更新文档\r\n}\r\n\r\nexport interface MustardState extends MustardStateOptions{\r\n data?: unknown, // pushState 和 replaceState 第一个参数\r\n index: number, // 用于计算当前state 的顺序\r\n}\r\n\r\nexport type State = {\r\n [key: string]: MustardState;\r\n} & {\r\n isMustard: 'MustardApp';\r\n [MainMustardApp]: undefined\r\n}\r\n\r\nexport function encodeState (data:unknown, appName:MustardName, options?:MustardStateOptions) {\r\n const app = getAppFromInstance(appName);\r\n const index = getStateIndex(appName) + 1;\r\n // todo origin\r\n const { flushed = false, origin = app?.state?.origin } = options ?? {};\r\n const allAppState = getAllAppState();\r\n return {\r\n ...allAppState,\r\n [appName]: {\r\n data,\r\n index,\r\n origin,\r\n flushed \r\n }\r\n };\r\n}\r\n\r\nexport function decodeState (appName:MustardName):MustardState|undefined {\r\n const state = history.state;\r\n if(state?.[appName]) {\r\n return state[appName];\r\n }\r\n}\r\n\r\nexport function getAllAppState () {\r\n const state = {\r\n isMustard: 'MustardApp',\r\n [MainMustardApp]: undefined\r\n } as State;\r\n\r\n getAllApp().forEach(name => {\r\n if(decodeState(name)) {\r\n state[name] = decodeState(name);\r\n }\r\n });\r\n\r\n return state;\r\n}\r\n\r\nexport function getStateIndex (appName:MustardName) {\r\n return decodeState(appName)?.index ?? 0;\r\n}\r\n\r\nexport function initState (appName: MustardName, state:unknown, unused:string, url:string) {\r\n let preState = history.state;\r\n if(!isMustardState(preState)) {\r\n preState = {\r\n isMustard: 'MustardApp',\r\n [MainMustardApp]: preState\r\n };\r\n }\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n (history.replaceState as any as ChangeState)({ \r\n ...preState,\r\n [appName]: {\r\n index: 0,\r\n origin: url\r\n }\r\n }, unused, undefined, true);\r\n}\r\n\r\nexport function navigateTo (appName:MustardName, type: 'pushState'|'replaceState', flushed?:boolean) {\r\n const navigateToMehtods = function (_state:unknown, _unused:string, _url?:string | URL) {\r\n return history[type].call(history, _state, _unused, _url, true);\r\n };\r\n const pathKey = getLocationNameByAppName(appName);\r\n return function (_state:unknown, _unused:string, _url?:string | URL) {\r\n const app = getAppFromInstance(appName);\r\n const preState = decodeState(appName);\r\n\r\n if(!_url) {\r\n // 不刷新\r\n return navigateToMehtods(encodeState(_state, appName, {\r\n flushed: type === 'replaceState' ? preState?.flushed : flushed\r\n }), _unused);\r\n }\r\n\r\n // 处理_url:相对地址->绝对地址\r\n const url = new URL(isURL(_url) ? _url.href : _url, app.url);\r\n\r\n const state = encodeState(_state, appName, {\r\n flushed: type === 'replaceState' ? preState?.flushed : flushed,\r\n origin: !flushed ? app.state.origin : url.href // 不刷新页面 使用当前 app.state.origin,否则使用跳转的地址做文档来源\r\n });\r\n\r\n const { pathname: pathnameFromLocation, search: searchFromLocation = '', hash: hashFromLocation } = location;\r\n\r\n const searchFromLocationParams = new URLSearchParams(searchFromLocation);\r\n \r\n searchFromLocationParams.set(pathKey, encodeURIComponent(url.href)); // 设置app对应的地址\r\n const searchParams = searchFromLocationParams.toString();\r\n\r\n app.state = state[appName];\r\n\r\n return navigateToMehtods(state, _unused, `${pathnameFromLocation}${searchParams ? '?' + searchParams : ''}${hashFromLocation}`);\r\n };\r\n}\r\n\r\nexport function proxyHistory (appName:MustardName) {\r\n return new Proxy(history, {\r\n get (target, key) {\r\n if(key === 'pushState') {\r\n return navigateTo(appName, 'pushState');\r\n }else if(key === 'replaceState') {\r\n return navigateTo(appName, 'replaceState');\r\n }else if(key === 'state') {\r\n return decodeState(appName)?.data;\r\n }else if(isFunction(target[key])) {\r\n return target[key].bind(history);\r\n }\r\n return target[key];\r\n }\r\n });\r\n}\r\n\r\n// 修改全局history方法\r\nexport function changeHistoryPropety () {\r\n const pushState = History.prototype.pushState;\r\n const replaceState = History.prototype.replaceState;\r\n\r\n function changeState (type:'pushState'|'replaceState') {\r\n const methodState = type === 'pushState' ? pushState : replaceState;\r\n return function (_state:unknown, _unused:string, _url:string | URL, _isMustard?:boolean) {\r\n if(_isMustard) {\r\n return methodState.call(this, _state, _unused, _url);\r\n }else{\r\n const allAppState = getAllAppState();\r\n return methodState.call(this, { ...allAppState, [MainMustardApp]: _state }, _unused, _url);\r\n }\r\n };\r\n }\r\n \r\n History.prototype.pushState = changeState('pushState');\r\n History.prototype.replaceState = changeState('replaceState');\r\n}","import { getAppFromInstance } from '../global';\r\nimport { MustardName, MustardURL } from '../typings';\r\nimport { getPath, getURL } from '../utils';\r\nimport { navigateTo } from './proxyHistory';\r\n\r\n/**\r\n * 创建子应用的location\r\n */\r\nexport function createLocation (appName:MustardName) {\r\n const assign = navigateTo(appName, 'pushState', true);\r\n const replace = navigateTo(appName, 'replaceState', true);\r\n \r\n class Location extends URL {\r\n assign (url:MustardURL) {\r\n const app = getAppFromInstance(appName);\r\n assign('', '', url);\r\n app && app.reload();\r\n }\r\n\r\n reload () {\r\n const app = getAppFromInstance(appName);\r\n app && app.reload();\r\n }\r\n\r\n replace (url:MustardURL) {\r\n const app = getAppFromInstance(appName);\r\n replace('', '', url);\r\n app && app.reload();\r\n }\r\n \r\n toString () {\r\n return this.href;\r\n }\r\n }\r\n \r\n return (path: string | URL, base?: string) => {\r\n return (base ? new Location('' + path, base) : new Location('' + path));\r\n };\r\n}\r\n\r\n\r\nexport function proxyLocation (appName:MustardName, url:MustardURL) {\r\n const _createLocation = createLocation(appName);\r\n const _location = _createLocation(getPath(appName), url);\r\n return new Proxy(_location, {\r\n get (target, key) {\r\n target.href = getURL(appName, url).href;\r\n return target[key];\r\n },\r\n set (target, key, value) {\r\n target.href = getURL(appName, url).href;\r\n const result = target[key] = value;\r\n target.assign(target.href);\r\n return result;\r\n }\r\n });\r\n}","import { getAppFromInstance } from '../global';\r\nimport { MustardName } from '../typings';\r\n\r\nexport function proxyStorage (appName:MustardName, _storage:typeof localStorage|typeof sessionStorage) {\r\n\r\n const nullObject = JSON.stringify(Object.create(null));\r\n\r\n const getItem = _storage.getItem.bind(_storage);\r\n const setItem = _storage.setItem.bind(_storage);\r\n const removeItem = _storage.removeItem.bind(_storage);\r\n\r\n const app = getAppFromInstance(appName);\r\n const origin = new URL(app.url)?.origin;\r\n\r\n function _getAllItem () {\r\n return JSON.parse(getItem(origin) ?? nullObject) as Record<string, string>;\r\n }\r\n function _getItem (key:string) {\r\n return _getAllItem()[key];\r\n }\r\n function _setItem (value:Record<string, string>) {\r\n return setItem(origin, JSON.stringify(value));\r\n }\r\n\r\n class Storage {\r\n constructor () {\r\n const data = _getAllItem();\r\n Reflect.ownKeys(data).forEach((key:string) => {\r\n this[key] = data[key];\r\n });\r\n }\r\n get length () {\r\n const data = _getAllItem();\r\n return Reflect.ownKeys(data)?.length;\r\n }\r\n clear () {\r\n Reflect.ownKeys(this).forEach(key => {\r\n Reflect.deleteProperty(this, key);\r\n });\r\n removeItem(origin);\r\n }\r\n getItem (key:string) {\r\n return _getItem(key);\r\n }\r\n setItem (key:string, value:unknown) {\r\n const data = _getAllItem();\r\n this[key] = data[key] = value?.toString?.();\r\n return _setItem(data);\r\n }\r\n removeItem (key:string) {\r\n const data = _getAllItem();\r\n Reflect.deleteProperty(this, key);\r\n const result = Reflect.deleteProperty(data, key);\r\n _setItem(data);\r\n return result;\r\n }\r\n key (index:number) {\r\n const data = _getAllItem();\r\n return Reflect.ownKeys(data)[index];\r\n }\r\n }\r\n\r\n const storage = new Storage();\r\n\r\n return new Proxy(storage, {\r\n get (target, key:string) {\r\n if(key === 'length') {\r\n return target.length;\r\n }else if(['clear', 'getItem', 'setItem', 'removeItem', 'key'].includes(key)) {\r\n return target[key].bind(target);\r\n }else{\r\n return _getItem(key);\r\n }\r\n },\r\n set (target, key:string, value:unknown) {\r\n if(key === 'length') return value;\r\n return target.setItem(key, value);\r\n },\r\n ownKeys () {\r\n const data = _getAllItem();\r\n return Reflect.ownKeys(data);\r\n },\r\n deleteProperty (target, key:string) {\r\n return target.removeItem(key);\r\n }\r\n });\r\n}\r\n\r\nexport function proxyLocalStorage (appName:MustardName) {\r\n return proxyStorage(appName, localStorage);\r\n}\r\n\r\nexport function proxySessionStorage (appName:MustardName) {\r\n return proxyStorage(appName, sessionStorage);\r\n}","import { ProxyEventListener }from './proxyEventListener';\r\nimport { proxyDocument }from './proxyDocument';\r\nimport { proxyHistory }from './proxyHistory';\r\nimport { proxyLocation }from './proxyLocation';\r\nimport { proxyLocalStorage, proxySessionStorage }from './proxyStorage';\r\nimport { MustardName, MustardURL } from '../typings';\r\nimport { EventCenterMicroApp } from '../communication';\r\n\r\nexport class SandBox {\r\n active = false; // 沙箱是否在运行\r\n microWindow = {}; // 代理的对象\r\n injectedKeys = new Set<string | symbol>(); // 新添加的属性,在卸载时清空\r\n\r\n name:MustardName; // 沙箱标识同app标识一致\r\n proxyEventListener:ProxyEventListener; // 全局事件代理\r\n proxyWindow; // window 代理\r\n proxyDocument; // document 代理\r\n proxyHistory; // history 代理\r\n proxyLocation; // location 代理\r\n proxyLocalStorage; // localStorage 代理\r\n proxySessionStorage; // sessionStorage 代理\r\n\r\n microApp: EventCenterMicroApp; // 事件通讯\r\n \r\n // todo\r\n // url: MustardURL\r\n constructor (name:MustardName, url: MustardURL) {\r\n this.name = name;\r\n this.proxyLocation = proxyLocation(this.name, url);\r\n this.proxyHistory = proxyHistory(this.name);\r\n this.proxyLocalStorage = proxyLocalStorage(this.name);\r\n this.proxySessionStorage = proxySessionStorage(this.name);\r\n this.proxyDocument = proxyDocument(this.name);\r\n this.proxyEventListener = new ProxyEventListener();\r\n this.microApp = new EventCenterMicroApp(this.name);\r\n this.proxyWindow = new Proxy(this.microWindow, {\r\n // 取值\r\n get: (target, key) => {\r\n // 优先从代理对象上取值\r\n if (Reflect.has(target, key)) {\r\n return Reflect.get(target, key);\r\n }\r\n if(key === 'document') {\r\n return this.proxyDocument;\r\n }\r\n if(key === 'addEventListener') {\r\n return this.proxyEventListener.addEventListener.bind(this.proxyEventListener);\r\n }\r\n if(key === 'history') {\r\n return this.proxyHistory;\r\n }\r\n if(key === 'location') {\r\n return this.proxyLocation;\r\n }\r\n if(key === 'localStorage') {\r\n return this.proxyLocalStorage;\r\n }\r\n if(key === 'sessionStorage') {\r\n return this.proxySessionStorage;\r\n }\r\n\r\n if(key === 'microApp') {\r\n return this.microApp;\r\n }\r\n \r\n // 否则兜底到window对象上取值\r\n const rawValue = Reflect.get(window, key);\r\n \r\n // 如果兜底的值为函数,则需要绑定window对象,如:console、alert等\r\n if (typeof rawValue === 'function') {\r\n const valueStr = rawValue.toString();\r\n // 排除构造函数\r\n if (!/^function\\s+[A-Z]/.test(valueStr) && !/^class\\s+/.test(valueStr)) {\r\n return rawValue.bind(window);\r\n }\r\n }\r\n \r\n // 其它情况直接返回\r\n return rawValue;\r\n },\r\n // 设置变量\r\n set: (target, key, value) => {\r\n // 沙箱只有在运行时可以设置变量\r\n if (this.active) {\r\n Reflect.set(target, key, value);\r\n // 记录添加的变量,用于后续清空操作\r\n this.injectedKeys.add(key);\r\n }\r\n return true;\r\n },\r\n deleteProperty: (target, key) => {\r\n // 当前key存在于代理对象上时才满足删除条件\r\n if (Object.prototype.hasOwnProperty.call(target, key)) {\r\n return Reflect.deleteProperty(target, key);\r\n }\r\n return true;\r\n },\r\n has (target, key) {\r\n return key in target || key in window;\r\n }\r\n });\r\n }\r\n\r\n start () {\r\n if(!this.active) {\r\n this.active = true;\r\n }\r\n }\r\n\r\n stop () {\r\n if(this.active) {\r\n this.microApp.clearDateListener();\r\n this.active = false;\r\n Array.from(this.injectedKeys.keys()).forEach(key => Reflect.deleteProperty(this.microWindow, key));\r\n this.injectedKeys.clear();\r\n this.proxyEventListener.clear();\r\n }\r\n }\r\n\r\n // 修改js作用域\r\n bindScope (code) {\r\n return `\r\n ;(function(window, self){\r\n const microApp = window.microApp;\r\n const history = window.history;\r\n const location = window.location;\r\n const document = window.document;\r\n const localStorage = window.localStorage;\r\n const sessionStorage = window.sessionStorage;\r\n const addEventListener = window.addEventListener;\r\n ${code}\\n;\r\n }).call(\r\n mustardAppInfos.getAppProxyWindow('${this.name}'),\r\n mustardAppInfos.getAppProxyWindow('${this.name}'),\r\n mustardAppInfos.getAppProxyWindow('${this.name}'),\r\n )\r\n `;\r\n }\r\n}","// Unique ID creation requires a high quality random # generator. In the browser we therefore\n// require the crypto API and do not support built-in fallback