mustard-app
Version:
个人前端微应用建设中。。。
733 lines (646 loc) • 23.6 kB
TypeScript
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 };