xpe 0.0.0-alpha.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +627 -71
- package/dist/child/app-client.d.ts +44 -0
- package/dist/child/app-client.js +151 -0
- package/dist/child/index.d.ts +3 -0
- package/dist/child/index.js +3 -0
- package/dist/child/lifecycle.d.ts +12 -0
- package/dist/child/lifecycle.js +47 -0
- package/dist/child/mock-host.d.ts +8 -0
- package/dist/child/mock-host.js +41 -0
- package/dist/core/bridge.d.ts +28 -0
- package/dist/core/bridge.js +19 -0
- package/dist/core/events.d.ts +13 -0
- package/dist/core/events.js +56 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +3 -0
- package/dist/core/types.d.ts +102 -0
- package/dist/core/types.js +20 -0
- package/dist/dev-shell/index.d.ts +26 -0
- package/dist/dev-shell/index.js +56 -0
- package/dist/host/app-path-router.d.ts +33 -0
- package/dist/host/app-path-router.js +183 -0
- package/dist/host/host-runtime.d.ts +29 -0
- package/dist/host/host-runtime.js +176 -0
- package/dist/host/index.d.ts +1 -0
- package/dist/host/index.js +1 -0
- package/dist/host/xpe-host.d.ts +41 -0
- package/dist/host/xpe-host.js +61 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/kernel/common.d.ts +62 -0
- package/dist/kernel/common.js +207 -0
- package/dist/kernel/constant.d.ts +26 -0
- package/dist/kernel/constant.js +42 -0
- package/dist/kernel/effect.d.ts +32 -0
- package/dist/kernel/effect.js +529 -0
- package/dist/kernel/entry.d.ts +43 -0
- package/dist/kernel/entry.js +224 -0
- package/dist/kernel/event.d.ts +22 -0
- package/dist/kernel/event.js +120 -0
- package/dist/kernel/iframe.d.ts +141 -0
- package/dist/kernel/iframe.js +1104 -0
- package/dist/kernel/index.d.ts +25 -0
- package/dist/kernel/index.js +86 -0
- package/dist/kernel/plugin.d.ts +36 -0
- package/dist/kernel/plugin.js +68 -0
- package/dist/kernel/proxy.d.ts +17 -0
- package/dist/kernel/proxy.js +440 -0
- package/dist/kernel/runtime-engine.d.ts +154 -0
- package/dist/kernel/runtime-engine.js +209 -0
- package/dist/kernel/sandbox.d.ts +235 -0
- package/dist/kernel/sandbox.js +879 -0
- package/dist/kernel/shadow.d.ts +64 -0
- package/dist/kernel/shadow.js +385 -0
- package/dist/kernel/sync.d.ts +21 -0
- package/dist/kernel/sync.js +185 -0
- package/dist/kernel/template.d.ts +67 -0
- package/dist/kernel/template.js +226 -0
- package/dist/kernel/tracker.d.ts +44 -0
- package/dist/kernel/tracker.js +92 -0
- package/dist/kernel/utils.d.ts +100 -0
- package/dist/kernel/utils.js +325 -0
- package/dist/kernel/xpe-bridge.d.ts +27 -0
- package/dist/kernel/xpe-bridge.js +205 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +3 -0
- package/package.json +79 -57
- package/LICENSE +0 -21
- package/framework/es/core/Application.d.ts +0 -126
- package/framework/es/core/Command/Command.d.ts +0 -7
- package/framework/es/core/Container/Container.d.ts +0 -80
- package/framework/es/core/Contracts/AxiosAdapter.d.ts +0 -12
- package/framework/es/core/Helper/Helper.d.ts +0 -43
- package/framework/es/core/Helper/Storage/WebStorage.d.ts +0 -71
- package/framework/es/core/Libs/Exception.d.ts +0 -24
- package/framework/es/core/Libs/Lib.d.ts +0 -72
- package/framework/es/core/Provider/CommandProvider.d.ts +0 -4
- package/framework/es/core/Provider/HelperProvider.d.ts +0 -4
- package/framework/es/core/Provider/HttpProvider.d.ts +0 -13
- package/framework/es/core/Provider/ServiceProvider.d.ts +0 -15
- package/framework/es/core/Service/HttpService.d.ts +0 -133
- package/framework/es/core/Service/Service.d.ts +0 -6
- package/framework/es/core/Spanner/RESTful.d.ts +0 -30
- package/framework/es/core/Transformer/Transformer.d.ts +0 -46
- package/framework/es/core/Type/adapters.d.ts +0 -25
- package/framework/es/core/Type/types.d.ts +0 -161
- package/framework/es/core/TypeFunc/index.d.ts +0 -6
- package/framework/es/core/Wrenches/EventBus.d.ts +0 -56
- package/framework/es/core/Wrenches/is.d.ts +0 -19
- package/framework/es/ege.js +0 -4
- package/framework/es/ege.js.map +0 -1
- package/framework/es/index.d.ts +0 -13
- package/framework/es/pdd.js +0 -4
- package/framework/es/pdd.js.map +0 -1
- package/framework/es/src/core/Application.d.ts +0 -127
- package/framework/es/src/core/Application.d.ts.map +0 -1
- package/framework/es/src/core/Command/Command.d.ts +0 -8
- package/framework/es/src/core/Command/Command.d.ts.map +0 -1
- package/framework/es/src/core/Container/Container.d.ts +0 -81
- package/framework/es/src/core/Container/Container.d.ts.map +0 -1
- package/framework/es/src/core/Contracts/AxiosAdapter.d.ts +0 -13
- package/framework/es/src/core/Contracts/AxiosAdapter.d.ts.map +0 -1
- package/framework/es/src/core/Helper/Helper.d.ts +0 -45
- package/framework/es/src/core/Helper/Helper.d.ts.map +0 -1
- package/framework/es/src/core/Helper/Storage/WebStorage.d.ts +0 -72
- package/framework/es/src/core/Helper/Storage/WebStorage.d.ts.map +0 -1
- package/framework/es/src/core/Libs/Exception.d.ts +0 -25
- package/framework/es/src/core/Libs/Exception.d.ts.map +0 -1
- package/framework/es/src/core/Libs/Lib.d.ts +0 -73
- package/framework/es/src/core/Libs/Lib.d.ts.map +0 -1
- package/framework/es/src/core/Provider/CommandProvider.d.ts +0 -5
- package/framework/es/src/core/Provider/CommandProvider.d.ts.map +0 -1
- package/framework/es/src/core/Provider/HelperProvider.d.ts +0 -5
- package/framework/es/src/core/Provider/HelperProvider.d.ts.map +0 -1
- package/framework/es/src/core/Provider/HttpProvider.d.ts +0 -14
- package/framework/es/src/core/Provider/HttpProvider.d.ts.map +0 -1
- package/framework/es/src/core/Provider/ServiceProvider.d.ts +0 -16
- package/framework/es/src/core/Provider/ServiceProvider.d.ts.map +0 -1
- package/framework/es/src/core/Service/HttpService.d.ts +0 -128
- package/framework/es/src/core/Service/HttpService.d.ts.map +0 -1
- package/framework/es/src/core/Service/Service.d.ts +0 -7
- package/framework/es/src/core/Service/Service.d.ts.map +0 -1
- package/framework/es/src/core/Spanner/RESTful.d.ts +0 -32
- package/framework/es/src/core/Spanner/RESTful.d.ts.map +0 -1
- package/framework/es/src/core/Transformer/Transformer.d.ts +0 -47
- package/framework/es/src/core/Transformer/Transformer.d.ts.map +0 -1
- package/framework/es/src/core/Type/adapters.d.ts +0 -26
- package/framework/es/src/core/Type/adapters.d.ts.map +0 -1
- package/framework/es/src/core/Type/types.d.ts +0 -186
- package/framework/es/src/core/Type/types.d.ts.map +0 -1
- package/framework/es/src/core/TypeFunc/index.d.ts +0 -8
- package/framework/es/src/core/TypeFunc/index.d.ts.map +0 -1
- package/framework/es/src/core/Wrenches/EventBus.d.ts +0 -57
- package/framework/es/src/core/Wrenches/EventBus.d.ts.map +0 -1
- package/framework/es/src/core/Wrenches/is.d.ts +0 -20
- package/framework/es/src/core/Wrenches/is.d.ts.map +0 -1
- package/framework/es/src/index.d.ts +0 -14
- package/framework/es/src/index.d.ts.map +0 -1
- package/framework/es/src/utils.d.ts +0 -17
- package/framework/es/src/utils.d.ts.map +0 -1
- package/framework/es/utils.d.ts +0 -16
- package/framework/umd/core/Application.d.ts +0 -126
- package/framework/umd/core/Command/Command.d.ts +0 -7
- package/framework/umd/core/Container/Container.d.ts +0 -80
- package/framework/umd/core/Contracts/AxiosAdapter.d.ts +0 -12
- package/framework/umd/core/Helper/Helper.d.ts +0 -43
- package/framework/umd/core/Helper/Storage/WebStorage.d.ts +0 -71
- package/framework/umd/core/Libs/Exception.d.ts +0 -24
- package/framework/umd/core/Libs/Lib.d.ts +0 -72
- package/framework/umd/core/Provider/CommandProvider.d.ts +0 -4
- package/framework/umd/core/Provider/HelperProvider.d.ts +0 -4
- package/framework/umd/core/Provider/HttpProvider.d.ts +0 -13
- package/framework/umd/core/Provider/ServiceProvider.d.ts +0 -15
- package/framework/umd/core/Service/HttpService.d.ts +0 -133
- package/framework/umd/core/Service/Service.d.ts +0 -6
- package/framework/umd/core/Spanner/RESTful.d.ts +0 -30
- package/framework/umd/core/Transformer/Transformer.d.ts +0 -46
- package/framework/umd/core/Type/adapters.d.ts +0 -25
- package/framework/umd/core/Type/types.d.ts +0 -161
- package/framework/umd/core/TypeFunc/index.d.ts +0 -6
- package/framework/umd/core/Wrenches/EventBus.d.ts +0 -56
- package/framework/umd/core/Wrenches/is.d.ts +0 -19
- package/framework/umd/ege.js +0 -4
- package/framework/umd/ege.js.map +0 -1
- package/framework/umd/index.d.ts +0 -13
- package/framework/umd/pdd.js +0 -4
- package/framework/umd/pdd.js.map +0 -1
- package/framework/umd/src/core/Application.d.ts +0 -127
- package/framework/umd/src/core/Application.d.ts.map +0 -1
- package/framework/umd/src/core/Command/Command.d.ts +0 -8
- package/framework/umd/src/core/Command/Command.d.ts.map +0 -1
- package/framework/umd/src/core/Container/Container.d.ts +0 -81
- package/framework/umd/src/core/Container/Container.d.ts.map +0 -1
- package/framework/umd/src/core/Contracts/AxiosAdapter.d.ts +0 -13
- package/framework/umd/src/core/Contracts/AxiosAdapter.d.ts.map +0 -1
- package/framework/umd/src/core/Helper/Helper.d.ts +0 -45
- package/framework/umd/src/core/Helper/Helper.d.ts.map +0 -1
- package/framework/umd/src/core/Helper/Storage/WebStorage.d.ts +0 -72
- package/framework/umd/src/core/Helper/Storage/WebStorage.d.ts.map +0 -1
- package/framework/umd/src/core/Libs/Exception.d.ts +0 -25
- package/framework/umd/src/core/Libs/Exception.d.ts.map +0 -1
- package/framework/umd/src/core/Libs/Lib.d.ts +0 -73
- package/framework/umd/src/core/Libs/Lib.d.ts.map +0 -1
- package/framework/umd/src/core/Provider/CommandProvider.d.ts +0 -5
- package/framework/umd/src/core/Provider/CommandProvider.d.ts.map +0 -1
- package/framework/umd/src/core/Provider/HelperProvider.d.ts +0 -5
- package/framework/umd/src/core/Provider/HelperProvider.d.ts.map +0 -1
- package/framework/umd/src/core/Provider/HttpProvider.d.ts +0 -14
- package/framework/umd/src/core/Provider/HttpProvider.d.ts.map +0 -1
- package/framework/umd/src/core/Provider/ServiceProvider.d.ts +0 -16
- package/framework/umd/src/core/Provider/ServiceProvider.d.ts.map +0 -1
- package/framework/umd/src/core/Service/HttpService.d.ts +0 -128
- package/framework/umd/src/core/Service/HttpService.d.ts.map +0 -1
- package/framework/umd/src/core/Service/Service.d.ts +0 -7
- package/framework/umd/src/core/Service/Service.d.ts.map +0 -1
- package/framework/umd/src/core/Spanner/RESTful.d.ts +0 -32
- package/framework/umd/src/core/Spanner/RESTful.d.ts.map +0 -1
- package/framework/umd/src/core/Transformer/Transformer.d.ts +0 -47
- package/framework/umd/src/core/Transformer/Transformer.d.ts.map +0 -1
- package/framework/umd/src/core/Type/adapters.d.ts +0 -26
- package/framework/umd/src/core/Type/adapters.d.ts.map +0 -1
- package/framework/umd/src/core/Type/types.d.ts +0 -186
- package/framework/umd/src/core/Type/types.d.ts.map +0 -1
- package/framework/umd/src/core/TypeFunc/index.d.ts +0 -8
- package/framework/umd/src/core/TypeFunc/index.d.ts.map +0 -1
- package/framework/umd/src/core/Wrenches/EventBus.d.ts +0 -57
- package/framework/umd/src/core/Wrenches/EventBus.d.ts.map +0 -1
- package/framework/umd/src/core/Wrenches/is.d.ts +0 -20
- package/framework/umd/src/core/Wrenches/is.d.ts.map +0 -1
- package/framework/umd/src/index.d.ts +0 -14
- package/framework/umd/src/index.d.ts.map +0 -1
- package/framework/umd/src/utils.d.ts +0 -17
- package/framework/umd/src/utils.d.ts.map +0 -1
- package/framework/umd/utils.d.ts +0 -16
- package/index.d.ts +0 -724
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type AppBridge, type AppContext, type AppEventHandler, type AppNavigationTarget, type HostNavigationTarget, type RuntimeInfo, type RouteMode, type Unsubscribe } from "../core/index.js";
|
|
2
|
+
export type CreateAppClientOptions = {
|
|
3
|
+
appCode: string;
|
|
4
|
+
basename: string;
|
|
5
|
+
routeMode?: RouteMode;
|
|
6
|
+
devHost?: AppBridge & {
|
|
7
|
+
runtime?: RuntimeInfo;
|
|
8
|
+
};
|
|
9
|
+
targetWindow?: Window;
|
|
10
|
+
};
|
|
11
|
+
export type RouterSyncRoute = {
|
|
12
|
+
fullPath?: string;
|
|
13
|
+
path?: string;
|
|
14
|
+
};
|
|
15
|
+
export type RouterSyncAdapter = {
|
|
16
|
+
currentRoute?: RouterSyncRoute | {
|
|
17
|
+
value: RouterSyncRoute;
|
|
18
|
+
};
|
|
19
|
+
push(path: string): void | Promise<unknown>;
|
|
20
|
+
replace(path: string): void | Promise<unknown>;
|
|
21
|
+
afterEach(handler: (to: RouterSyncRoute) => void): Unsubscribe;
|
|
22
|
+
isReady?: () => Promise<unknown>;
|
|
23
|
+
};
|
|
24
|
+
export type RouterSyncOptions = {
|
|
25
|
+
initialPath?: string;
|
|
26
|
+
getCurrentPath?: () => string;
|
|
27
|
+
targetWindow?: Window;
|
|
28
|
+
};
|
|
29
|
+
export type AppClient = {
|
|
30
|
+
isHosted(): boolean;
|
|
31
|
+
getRuntime(): RuntimeInfo | null;
|
|
32
|
+
getContext(): AppContext;
|
|
33
|
+
onContextChange(handler: AppEventHandler<AppContext>): Unsubscribe;
|
|
34
|
+
hasPermission(permissionCode: string): boolean;
|
|
35
|
+
emit<TPayload = unknown>(eventName: string, payload: TPayload): void;
|
|
36
|
+
on<TPayload = unknown>(eventName: string, handler: AppEventHandler<TPayload>): Unsubscribe;
|
|
37
|
+
off<TPayload = unknown>(eventName: string, handler: AppEventHandler<TPayload>): void;
|
|
38
|
+
navigate(target: string | AppNavigationTarget): void;
|
|
39
|
+
navigateHost(target: string | HostNavigationTarget): void;
|
|
40
|
+
reportRouteChange(path: string): void;
|
|
41
|
+
onRouteChange(handler: AppEventHandler<string>): Unsubscribe;
|
|
42
|
+
syncRouter(router: RouterSyncAdapter, options?: RouterSyncOptions): Promise<Unsubscribe>;
|
|
43
|
+
};
|
|
44
|
+
export declare function createAppClient(options: CreateAppClientOptions): AppClient;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { getRuntimeWindow, XPE_ROUTE_CHANGE_EVENT, XpeRuntimeError, } from "../core/index.js";
|
|
2
|
+
export function createAppClient(options) {
|
|
3
|
+
const runtimeWindow = getRuntimeWindow(options.targetWindow);
|
|
4
|
+
const bridge = options.devHost ?? runtimeWindow.__XPE__;
|
|
5
|
+
const runtime = options.devHost?.runtime ?? runtimeWindow.__XPE_RUNTIME__;
|
|
6
|
+
function getContext() {
|
|
7
|
+
const context = bridge?.getContext?.() ?? bridge?.context;
|
|
8
|
+
if (!context) {
|
|
9
|
+
throw new XpeRuntimeError("XPE_CONTEXT_MISSING", "XPE 应用上下文不存在", {
|
|
10
|
+
appCode: options.appCode,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return context;
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
isHosted() {
|
|
17
|
+
return Boolean(runtime?.poweredByXpe || bridge);
|
|
18
|
+
},
|
|
19
|
+
getRuntime() {
|
|
20
|
+
return runtime ?? null;
|
|
21
|
+
},
|
|
22
|
+
getContext,
|
|
23
|
+
onContextChange(handler) {
|
|
24
|
+
return bridge?.onContextChange?.(handler) ?? (() => undefined);
|
|
25
|
+
},
|
|
26
|
+
hasPermission(permissionCode) {
|
|
27
|
+
return getContext().permissions.includes(permissionCode);
|
|
28
|
+
},
|
|
29
|
+
emit(eventName, payload) {
|
|
30
|
+
bridge?.emit?.(eventName, payload);
|
|
31
|
+
},
|
|
32
|
+
on(eventName, handler) {
|
|
33
|
+
return bridge?.on?.(eventName, handler) ?? (() => undefined);
|
|
34
|
+
},
|
|
35
|
+
off(eventName, handler) {
|
|
36
|
+
bridge?.off?.(eventName, handler);
|
|
37
|
+
},
|
|
38
|
+
navigate(target) {
|
|
39
|
+
if (bridge?.navigate) {
|
|
40
|
+
bridge.navigate(target);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
runtimeWindow.location.href = assertAllowedNavigationTarget(typeof target === "string" ? target : formatStandaloneNavigationTarget(target));
|
|
44
|
+
},
|
|
45
|
+
navigateHost(target) {
|
|
46
|
+
if (bridge?.navigateHost) {
|
|
47
|
+
bridge.navigateHost(target);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
runtimeWindow.location.href = assertAllowedNavigationTarget(typeof target === "string" ? target : formatStandaloneNavigationTarget(target));
|
|
51
|
+
},
|
|
52
|
+
reportRouteChange(path) {
|
|
53
|
+
bridge?.reportRouteChange?.(path);
|
|
54
|
+
},
|
|
55
|
+
onRouteChange(handler) {
|
|
56
|
+
return bridge?.on?.(XPE_ROUTE_CHANGE_EVENT, handler) ?? (() => undefined);
|
|
57
|
+
},
|
|
58
|
+
async syncRouter(router, syncOptions = {}) {
|
|
59
|
+
// 用「待抑制的 host 路径」而非布尔标志做回声抑制:host 驱动的 replace 会触发
|
|
60
|
+
// afterEach,需要跳过这一次上报,避免把 host→child 的同步又回报给 host。
|
|
61
|
+
// 旧实现用布尔 syncingFromHost,依赖 afterEach 必然触发来复位;但 replace 在
|
|
62
|
+
// 同路径 / 去重 / 守卫拒绝时不触发 afterEach,标志就永久卡在 true,后续真实
|
|
63
|
+
// 用户导航被静默吞掉、永不上报。改为按路径比对 + finally 兜底清理,两个问题都解。
|
|
64
|
+
let pendingHostPath = null;
|
|
65
|
+
const getCurrentPath = syncOptions.getCurrentPath ?? (() => getCurrentRouterPath(router));
|
|
66
|
+
const initialPath = syncOptions.initialPath ?? getInitialRoutePath(syncOptions.targetWindow ?? runtimeWindow);
|
|
67
|
+
await router.push(initialPath);
|
|
68
|
+
await router.isReady?.();
|
|
69
|
+
this.reportRouteChange(getCurrentPath());
|
|
70
|
+
const removeRouteHook = router.afterEach((to) => {
|
|
71
|
+
const toPath = to.fullPath ?? to.path ?? getCurrentPath();
|
|
72
|
+
if (pendingHostPath !== null && toPath === pendingHostPath) {
|
|
73
|
+
// 这一次导航是 host 驱动的回声,吞掉不回报,并清除待抑制标记。
|
|
74
|
+
pendingHostPath = null;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.reportRouteChange(toPath);
|
|
78
|
+
});
|
|
79
|
+
const unsubscribeRouteChange = this.onRouteChange((path) => {
|
|
80
|
+
pendingHostPath = path;
|
|
81
|
+
// replace 若未触发 afterEach(同路径/去重/守卫拒绝),用 finally 清除残留标记,
|
|
82
|
+
// 防止下一次真实用户导航被错误抑制。
|
|
83
|
+
Promise.resolve(router.replace(path)).finally(() => {
|
|
84
|
+
if (pendingHostPath === path)
|
|
85
|
+
pendingHostPath = null;
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
return () => {
|
|
89
|
+
removeRouteHook();
|
|
90
|
+
unsubscribeRouteChange();
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function getInitialRoutePath(targetWindow) {
|
|
96
|
+
return targetWindow.location.hash.replace(/^#/, "") || "/";
|
|
97
|
+
}
|
|
98
|
+
function getCurrentRouterPath(router) {
|
|
99
|
+
const route = unwrapCurrentRoute(router.currentRoute);
|
|
100
|
+
return route.fullPath ?? route.path ?? "/";
|
|
101
|
+
}
|
|
102
|
+
function unwrapCurrentRoute(route) {
|
|
103
|
+
if (!route)
|
|
104
|
+
return {};
|
|
105
|
+
if ("value" in route)
|
|
106
|
+
return route.value;
|
|
107
|
+
return route;
|
|
108
|
+
}
|
|
109
|
+
function formatStandaloneNavigationTarget(target) {
|
|
110
|
+
const search = formatQuery(target.query);
|
|
111
|
+
return `${target.path}${search}`;
|
|
112
|
+
}
|
|
113
|
+
function assertAllowedNavigationTarget(target) {
|
|
114
|
+
if (isAllowedNavigationTarget(target))
|
|
115
|
+
return target;
|
|
116
|
+
throw new XpeRuntimeError("XPE_NAVIGATION_BLOCKED", "XPE 导航目标协议不被允许", { target });
|
|
117
|
+
}
|
|
118
|
+
function isAllowedNavigationTarget(target) {
|
|
119
|
+
const trimmed = target.trim();
|
|
120
|
+
if (!trimmed)
|
|
121
|
+
return false;
|
|
122
|
+
if (trimmed.startsWith("/") ||
|
|
123
|
+
trimmed.startsWith("./") ||
|
|
124
|
+
trimmed.startsWith("../") ||
|
|
125
|
+
trimmed.startsWith("#") ||
|
|
126
|
+
trimmed.startsWith("?")) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const url = new URL(trimmed, "http://xpe.local");
|
|
131
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
132
|
+
}
|
|
133
|
+
catch (_) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function formatQuery(query) {
|
|
138
|
+
if (!query)
|
|
139
|
+
return "";
|
|
140
|
+
const params = new URLSearchParams();
|
|
141
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
142
|
+
const values = Array.isArray(value) ? value : [value];
|
|
143
|
+
values.forEach((item) => {
|
|
144
|
+
if (item === null || item === undefined)
|
|
145
|
+
return;
|
|
146
|
+
params.append(key, String(item));
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
const queryString = params.toString();
|
|
150
|
+
return queryString ? `?${queryString}` : "";
|
|
151
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AppClient } from "./app-client.js";
|
|
2
|
+
import { type AppContext, type RouteMode } from "../core/index.js";
|
|
3
|
+
export type AppLifecycleMountResult = void | (() => void | Promise<void>);
|
|
4
|
+
export type DefineAppLifecycleOptions = {
|
|
5
|
+
appCode?: string;
|
|
6
|
+
basename?: string;
|
|
7
|
+
routeMode?: RouteMode;
|
|
8
|
+
targetWindow?: Window;
|
|
9
|
+
defaultContainer?: string | Element;
|
|
10
|
+
mount: (container: Element, context: AppContext, client: AppClient) => AppLifecycleMountResult | Promise<AppLifecycleMountResult>;
|
|
11
|
+
};
|
|
12
|
+
export declare function defineAppLifecycle(options: DefineAppLifecycleOptions): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createAppClient } from "./app-client.js";
|
|
2
|
+
import { getRuntimeWindow, XpeRuntimeError } from "../core/index.js";
|
|
3
|
+
export function defineAppLifecycle(options) {
|
|
4
|
+
const runtimeWindow = getRuntimeWindow(options.targetWindow);
|
|
5
|
+
let cleanup;
|
|
6
|
+
const mount = async () => {
|
|
7
|
+
const clientOptions = {
|
|
8
|
+
appCode: options.appCode ?? runtimeWindow.__XPE_RUNTIME__?.appCode ?? "standalone",
|
|
9
|
+
basename: options.basename ?? runtimeWindow.__XPE_RUNTIME__?.basename ?? "/",
|
|
10
|
+
routeMode: options.routeMode ?? runtimeWindow.__XPE_RUNTIME__?.routeMode,
|
|
11
|
+
targetWindow: runtimeWindow,
|
|
12
|
+
};
|
|
13
|
+
const appClient = createAppClient(clientOptions);
|
|
14
|
+
const container = resolveContainer(options.defaultContainer, runtimeWindow.__XPE__);
|
|
15
|
+
cleanup = await options.mount(container, appClient.getContext(), appClient);
|
|
16
|
+
};
|
|
17
|
+
const unmount = async () => {
|
|
18
|
+
if (typeof cleanup === "function") {
|
|
19
|
+
await cleanup();
|
|
20
|
+
cleanup = undefined;
|
|
21
|
+
}
|
|
22
|
+
// 卸载后清除挂载全局,避免闭包(cleanup/client/container)随 window 长期保活,
|
|
23
|
+
// 也防止宿主在 teardown 后重读 __XPE_MOUNT__ 拿到 stale 挂载函数。delete 而非赋
|
|
24
|
+
// undefined,与 clearRuntimeBridge 一致,彻底断开属性引用。
|
|
25
|
+
delete runtimeWindow.__XPE_MOUNT__;
|
|
26
|
+
delete runtimeWindow.__XPE_UNMOUNT__;
|
|
27
|
+
};
|
|
28
|
+
if (runtimeWindow.__XPE_RUNTIME__) {
|
|
29
|
+
runtimeWindow.__XPE_MOUNT__ = mount;
|
|
30
|
+
runtimeWindow.__XPE_UNMOUNT__ = unmount;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
void mount();
|
|
34
|
+
}
|
|
35
|
+
function resolveContainer(container, bridge) {
|
|
36
|
+
if (container instanceof Element)
|
|
37
|
+
return container;
|
|
38
|
+
const selector = container ?? "#app";
|
|
39
|
+
const element = bridge?.resolveContainer?.(selector) ?? document.querySelector(selector);
|
|
40
|
+
if (!element) {
|
|
41
|
+
// 与 dev-shell 统一使用 typed error,容器缺失在宿主与子应用两侧是同一种失败。
|
|
42
|
+
throw new XpeRuntimeError("XPE_CONTAINER_MISSING", `XPE 子应用挂载容器不存在:${selector}`, {
|
|
43
|
+
selector,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return element;
|
|
47
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type AppBridge, type AppContext, type RuntimeInfo } from "../core/index.js";
|
|
2
|
+
export type MockHostOptions = {
|
|
3
|
+
context: AppContext;
|
|
4
|
+
runtime?: Partial<RuntimeInfo>;
|
|
5
|
+
};
|
|
6
|
+
export declare function createMockHost(options: MockHostOptions): AppBridge & {
|
|
7
|
+
runtime: RuntimeInfo;
|
|
8
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createEventHub } from "../core/index.js";
|
|
2
|
+
export function createMockHost(options) {
|
|
3
|
+
let currentContext = options.context;
|
|
4
|
+
const contextEvents = createEventHub();
|
|
5
|
+
const appEvents = createEventHub();
|
|
6
|
+
const runtime = {
|
|
7
|
+
poweredByXpe: true,
|
|
8
|
+
appCode: currentContext.appCode,
|
|
9
|
+
// 与真实 host 的 getRuntimeName 对齐(appCode 原样),避免 dev 与 prod 下
|
|
10
|
+
// runtime.name 不一致,导致按 name 键的实例复用行为分叉。
|
|
11
|
+
name: currentContext.appCode,
|
|
12
|
+
basename: currentContext.basePath,
|
|
13
|
+
routeMode: "hash",
|
|
14
|
+
...options.runtime,
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
runtime,
|
|
18
|
+
get context() {
|
|
19
|
+
return currentContext;
|
|
20
|
+
},
|
|
21
|
+
getContext() {
|
|
22
|
+
return currentContext;
|
|
23
|
+
},
|
|
24
|
+
setContext(nextContext) {
|
|
25
|
+
currentContext = nextContext;
|
|
26
|
+
contextEvents.emit("context-change", nextContext);
|
|
27
|
+
},
|
|
28
|
+
onContextChange(handler) {
|
|
29
|
+
return contextEvents.on("context-change", handler);
|
|
30
|
+
},
|
|
31
|
+
emit(eventName, payload) {
|
|
32
|
+
appEvents.emit(eventName, payload);
|
|
33
|
+
},
|
|
34
|
+
on(eventName, handler) {
|
|
35
|
+
return appEvents.on(eventName, handler);
|
|
36
|
+
},
|
|
37
|
+
off(eventName, handler) {
|
|
38
|
+
appEvents.off(eventName, handler);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AppContext, AppEventHandler, AppNavigationTarget, HostNavigationTarget, RuntimeInfo, Unsubscribe } from "./types.js";
|
|
2
|
+
export type AppBridge = {
|
|
3
|
+
context?: AppContext;
|
|
4
|
+
getContext?: () => AppContext | undefined;
|
|
5
|
+
setContext?: (context: AppContext) => void;
|
|
6
|
+
onContextChange?: (handler: AppEventHandler<AppContext>) => Unsubscribe;
|
|
7
|
+
emit?: <TPayload = unknown>(eventName: string, payload: TPayload) => void;
|
|
8
|
+
on?: <TPayload = unknown>(eventName: string, handler: AppEventHandler<TPayload>) => Unsubscribe;
|
|
9
|
+
off?: <TPayload = unknown>(eventName: string, handler: AppEventHandler<TPayload>) => void;
|
|
10
|
+
navigate?: (target: string | AppNavigationTarget) => void;
|
|
11
|
+
navigateHost?: (target: string | HostNavigationTarget) => void;
|
|
12
|
+
reportRouteChange?: (path: string) => void;
|
|
13
|
+
syncRoute?: (path: string) => void;
|
|
14
|
+
mount?: () => void | Promise<void>;
|
|
15
|
+
resolveContainer?: (selector?: string) => Element | null;
|
|
16
|
+
};
|
|
17
|
+
export type XpeRuntimeWindow = Window & typeof globalThis & {
|
|
18
|
+
__XPE_RUNTIME__?: RuntimeInfo;
|
|
19
|
+
__XPE__?: AppBridge;
|
|
20
|
+
__XPE_MOUNT__?: () => void | Promise<void>;
|
|
21
|
+
__XPE_UNMOUNT__?: () => void | Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
export declare function getRuntimeWindow(targetWindow?: Window): XpeRuntimeWindow;
|
|
24
|
+
export declare function installRuntimeBridge(targetWindow: Window, options: {
|
|
25
|
+
runtime: RuntimeInfo;
|
|
26
|
+
bridge?: AppBridge;
|
|
27
|
+
}): void;
|
|
28
|
+
export declare function clearRuntimeBridge(targetWindow: Window): void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function getRuntimeWindow(targetWindow = window) {
|
|
2
|
+
return targetWindow;
|
|
3
|
+
}
|
|
4
|
+
export function installRuntimeBridge(targetWindow, options) {
|
|
5
|
+
const runtimeWindow = getRuntimeWindow(targetWindow);
|
|
6
|
+
runtimeWindow.__XPE_RUNTIME__ = options.runtime;
|
|
7
|
+
if (options.bridge) {
|
|
8
|
+
runtimeWindow.__XPE__ = options.bridge;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function clearRuntimeBridge(targetWindow) {
|
|
12
|
+
const runtimeWindow = getRuntimeWindow(targetWindow);
|
|
13
|
+
// 用 delete 真正移除属性,而非赋 undefined:后者会在 window 上留下持有 undefined 的
|
|
14
|
+
// 自有属性,`in` 探测与 ownKeys 仍会命中,且无法回收闭包引用;delete 彻底断开。
|
|
15
|
+
delete runtimeWindow.__XPE_RUNTIME__;
|
|
16
|
+
delete runtimeWindow.__XPE__;
|
|
17
|
+
delete runtimeWindow.__XPE_MOUNT__;
|
|
18
|
+
delete runtimeWindow.__XPE_UNMOUNT__;
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AppEventHandler, Unsubscribe } from "./types.js";
|
|
2
|
+
import { XpeRuntimeError } from "./types.js";
|
|
3
|
+
export declare const XPE_ROUTE_CHANGE_EVENT = "xpe:route-change";
|
|
4
|
+
export type EventMap = Map<string, Set<AppEventHandler>>;
|
|
5
|
+
export type CreateEventHubOptions = {
|
|
6
|
+
onError?: (error: XpeRuntimeError) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function createEventHub(options?: CreateEventHubOptions): {
|
|
9
|
+
on: <TPayload = unknown>(eventName: string, handler: AppEventHandler<TPayload>) => Unsubscribe;
|
|
10
|
+
off: <TPayload = unknown>(eventName: string, handler: AppEventHandler<TPayload>) => void;
|
|
11
|
+
emit: <TPayload = unknown>(eventName: string, payload: TPayload) => void;
|
|
12
|
+
clear: () => void;
|
|
13
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { XpeRuntimeError } from "./types.js";
|
|
2
|
+
export const XPE_ROUTE_CHANGE_EVENT = "xpe:route-change";
|
|
3
|
+
export function createEventHub(options = {}) {
|
|
4
|
+
const events = new Map();
|
|
5
|
+
const { onError } = options;
|
|
6
|
+
function on(eventName, handler) {
|
|
7
|
+
const handlers = events.get(eventName) ?? new Set();
|
|
8
|
+
handlers.add(handler);
|
|
9
|
+
events.set(eventName, handlers);
|
|
10
|
+
return () => {
|
|
11
|
+
off(eventName, handler);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function off(eventName, handler) {
|
|
15
|
+
const handlers = events.get(eventName);
|
|
16
|
+
if (!handlers)
|
|
17
|
+
return;
|
|
18
|
+
handlers.delete(handler);
|
|
19
|
+
if (handlers.size === 0)
|
|
20
|
+
events.delete(eventName);
|
|
21
|
+
}
|
|
22
|
+
function emit(eventName, payload) {
|
|
23
|
+
const handlers = events.get(eventName);
|
|
24
|
+
if (!handlers)
|
|
25
|
+
return;
|
|
26
|
+
// 先快照再派发:handler 内部 on/off 会改动原 Set,迭代期增删会漏发或重复发;
|
|
27
|
+
// 同时逐个 try/catch,单个 handler 抛错不阻断其余订阅者,错误包装为 XPE_EVENT_FAILED
|
|
28
|
+
// 交由 onError,未提供时落到 console.error,避免静默吞掉。
|
|
29
|
+
for (const handler of [...handlers]) {
|
|
30
|
+
try {
|
|
31
|
+
handler(payload);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const wrapped = new XpeRuntimeError("XPE_EVENT_FAILED", `XPE 事件处理器执行失败:${eventName}`, {
|
|
35
|
+
eventName,
|
|
36
|
+
cause: error,
|
|
37
|
+
});
|
|
38
|
+
if (onError) {
|
|
39
|
+
onError(wrapped);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error(wrapped);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function clear() {
|
|
48
|
+
events.clear();
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
on,
|
|
52
|
+
off,
|
|
53
|
+
emit,
|
|
54
|
+
clear,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export type RuntimeMode = "xpe";
|
|
2
|
+
export type RouteMode = "hash" | "history";
|
|
3
|
+
export type AccessStatus = "enterable" | "no_permission" | "not_opened" | "expired";
|
|
4
|
+
export type RuntimeCredentials = "include" | "omit";
|
|
5
|
+
export type MenuNodeType = "directory" | "menu" | "action";
|
|
6
|
+
export type MenuNode = {
|
|
7
|
+
id: string;
|
|
8
|
+
appCode: string;
|
|
9
|
+
parentId: string | null;
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
component?: string | null;
|
|
13
|
+
icon: string | null;
|
|
14
|
+
permissionCode: string | null;
|
|
15
|
+
type?: MenuNodeType;
|
|
16
|
+
visible?: boolean;
|
|
17
|
+
children: MenuNode[];
|
|
18
|
+
};
|
|
19
|
+
export type RemoteRuntimeConfig = {
|
|
20
|
+
mode?: RuntimeMode;
|
|
21
|
+
name?: string;
|
|
22
|
+
url: string;
|
|
23
|
+
basename?: string;
|
|
24
|
+
routeMode?: RouteMode;
|
|
25
|
+
alive?: boolean;
|
|
26
|
+
sync?: boolean;
|
|
27
|
+
credentials?: RuntimeCredentials;
|
|
28
|
+
degradeToExternalLink?: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type AppManifest = {
|
|
31
|
+
appCode: string;
|
|
32
|
+
appName: string;
|
|
33
|
+
shortName?: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
icon?: string;
|
|
36
|
+
color?: string;
|
|
37
|
+
basePath: string;
|
|
38
|
+
entryPath?: string;
|
|
39
|
+
runtimeMode?: "xpe";
|
|
40
|
+
runtime: RemoteRuntimeConfig;
|
|
41
|
+
menus?: MenuNode[];
|
|
42
|
+
permissions?: string[];
|
|
43
|
+
version?: string;
|
|
44
|
+
};
|
|
45
|
+
export type CurrentUser = {
|
|
46
|
+
memberId: string;
|
|
47
|
+
userId: string;
|
|
48
|
+
realName: string;
|
|
49
|
+
phone: string;
|
|
50
|
+
};
|
|
51
|
+
export type CurrentTenant = {
|
|
52
|
+
tenantId: string;
|
|
53
|
+
tenantCode: string;
|
|
54
|
+
tenantName: string;
|
|
55
|
+
};
|
|
56
|
+
export type CurrentOrganization = {
|
|
57
|
+
orgId: string;
|
|
58
|
+
orgPath: string | null;
|
|
59
|
+
} | null;
|
|
60
|
+
export type AppContext = {
|
|
61
|
+
appCode: string;
|
|
62
|
+
appName: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
basePath: string;
|
|
65
|
+
entryPath: string;
|
|
66
|
+
defaultPath: string;
|
|
67
|
+
accessStatus: AccessStatus;
|
|
68
|
+
reason?: string;
|
|
69
|
+
currentUser: CurrentUser;
|
|
70
|
+
currentTenant: CurrentTenant;
|
|
71
|
+
currentOrganization: CurrentOrganization;
|
|
72
|
+
menus: MenuNode[];
|
|
73
|
+
permissions: string[];
|
|
74
|
+
};
|
|
75
|
+
export type RuntimeInfo = {
|
|
76
|
+
poweredByXpe: true;
|
|
77
|
+
appCode: string;
|
|
78
|
+
name: string;
|
|
79
|
+
basename: string;
|
|
80
|
+
routeMode: RouteMode;
|
|
81
|
+
};
|
|
82
|
+
export type RuntimeErrorCode = "XPE_APP_NOT_REGISTERED" | "XPE_APP_NOT_ENTERABLE" | "XPE_CONTEXT_MISSING" | "XPE_CONTAINER_MISSING" | "XPE_NAVIGATION_BLOCKED" | "XPE_RUNTIME_LOAD_FAILED" | "XPE_LIFECYCLE_FAILED" | "XPE_EVENT_FAILED";
|
|
83
|
+
export type RuntimeErrorDetails = Record<string, unknown>;
|
|
84
|
+
export type RouteQueryValue = string | number | boolean | null | undefined;
|
|
85
|
+
export type AppNavigationTarget = {
|
|
86
|
+
appCode?: string;
|
|
87
|
+
path: string;
|
|
88
|
+
query?: Record<string, RouteQueryValue | RouteQueryValue[]>;
|
|
89
|
+
replace?: boolean;
|
|
90
|
+
};
|
|
91
|
+
export type HostNavigationTarget = {
|
|
92
|
+
path: string;
|
|
93
|
+
query?: Record<string, RouteQueryValue | RouteQueryValue[]>;
|
|
94
|
+
replace?: boolean;
|
|
95
|
+
};
|
|
96
|
+
export declare class XpeRuntimeError extends Error {
|
|
97
|
+
readonly code: RuntimeErrorCode;
|
|
98
|
+
readonly details?: RuntimeErrorDetails;
|
|
99
|
+
constructor(code: RuntimeErrorCode, message: string, details?: RuntimeErrorDetails);
|
|
100
|
+
}
|
|
101
|
+
export type AppEventHandler<TPayload = unknown> = (payload: TPayload) => void;
|
|
102
|
+
export type Unsubscribe = () => void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class XpeRuntimeError extends Error {
|
|
2
|
+
constructor(code, message, details) {
|
|
3
|
+
super(message);
|
|
4
|
+
Object.defineProperty(this, "code", {
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true,
|
|
8
|
+
value: void 0
|
|
9
|
+
});
|
|
10
|
+
Object.defineProperty(this, "details", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: void 0
|
|
15
|
+
});
|
|
16
|
+
this.name = "XpeRuntimeError";
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.details = details;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type AppBridge, type AppContext, type RuntimeInfo } from "../core/index.js";
|
|
2
|
+
export type DevShellOptions = {
|
|
3
|
+
context: AppContext;
|
|
4
|
+
runtime?: Partial<RuntimeInfo>;
|
|
5
|
+
mountSelector?: string;
|
|
6
|
+
targetWindow?: Window;
|
|
7
|
+
};
|
|
8
|
+
export type DevShell = {
|
|
9
|
+
bridge: AppBridge & {
|
|
10
|
+
runtime: RuntimeInfo;
|
|
11
|
+
};
|
|
12
|
+
runtime: RuntimeInfo;
|
|
13
|
+
/**
|
|
14
|
+
* dev-shell 解析到的展示用容器。惰性解析:仅在访问时按 mountSelector 查询,
|
|
15
|
+
* 构造 createDevShell 时不再急切要求 DOM 就绪。实际挂载仍由子应用自身的
|
|
16
|
+
* defineAppLifecycle.resolveContainer 负责(默认 #app 或 app 的 defaultContainer),
|
|
17
|
+
* 二者不一致时以子应用的 defaultContainer 为准。
|
|
18
|
+
*/
|
|
19
|
+
readonly container: Element | null;
|
|
20
|
+
install(): void;
|
|
21
|
+
mount(): Promise<void>;
|
|
22
|
+
unmount(): Promise<void>;
|
|
23
|
+
updateContext(context: AppContext): void;
|
|
24
|
+
destroy(): Promise<void>;
|
|
25
|
+
};
|
|
26
|
+
export declare function createDevShell(options: DevShellOptions): DevShell;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createMockHost } from "../child/index.js";
|
|
2
|
+
import { clearRuntimeBridge, getRuntimeWindow, installRuntimeBridge, XpeRuntimeError, } from "../core/index.js";
|
|
3
|
+
export function createDevShell(options) {
|
|
4
|
+
const targetWindow = options.targetWindow ?? window;
|
|
5
|
+
const runtimeWindow = getRuntimeWindow(targetWindow);
|
|
6
|
+
const bridge = createMockHost({
|
|
7
|
+
context: options.context,
|
|
8
|
+
runtime: options.runtime,
|
|
9
|
+
});
|
|
10
|
+
function install() {
|
|
11
|
+
installRuntimeBridge(runtimeWindow, {
|
|
12
|
+
runtime: bridge.runtime,
|
|
13
|
+
bridge,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async function mount() {
|
|
17
|
+
install();
|
|
18
|
+
if (runtimeWindow.__XPE_MOUNT__) {
|
|
19
|
+
await runtimeWindow.__XPE_MOUNT__();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function unmount() {
|
|
23
|
+
if (runtimeWindow.__XPE_UNMOUNT__) {
|
|
24
|
+
await runtimeWindow.__XPE_UNMOUNT__();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function destroy() {
|
|
28
|
+
await unmount();
|
|
29
|
+
clearRuntimeBridge(runtimeWindow);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
bridge,
|
|
33
|
+
runtime: bridge.runtime,
|
|
34
|
+
// 惰性解析:仅在访问 container 时才查询 DOM。避免 createDevShell 构造期
|
|
35
|
+
// 就硬要求容器存在(实际挂载走 child 的 __XPE_MOUNT__ + 自身 resolveContainer)。
|
|
36
|
+
get container() {
|
|
37
|
+
return resolveContainer(targetWindow, options.mountSelector);
|
|
38
|
+
},
|
|
39
|
+
install,
|
|
40
|
+
mount,
|
|
41
|
+
unmount,
|
|
42
|
+
updateContext(context) {
|
|
43
|
+
bridge.setContext?.(context);
|
|
44
|
+
},
|
|
45
|
+
destroy,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function resolveContainer(targetWindow, selector = "#app") {
|
|
49
|
+
const container = targetWindow.document.querySelector(selector);
|
|
50
|
+
if (!container) {
|
|
51
|
+
throw new XpeRuntimeError("XPE_CONTAINER_MISSING", "XPE dev shell 挂载容器不存在", {
|
|
52
|
+
selector,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return container;
|
|
56
|
+
}
|