@zero-bits/kernel 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 +119 -0
- package/dist/chunk-D4E5XKWD.js +482 -0
- package/dist/chunk-ETCJDJTI.cjs +482 -0
- package/dist/core/index.cjs +28 -0
- package/dist/core/index.d.cts +58 -0
- package/dist/core/index.d.ts +58 -0
- package/dist/core/index.js +28 -0
- package/dist/react/index.cjs +147 -0
- package/dist/react/index.d.cts +42 -0
- package/dist/react/index.d.ts +42 -0
- package/dist/react/index.js +147 -0
- package/dist/types-JlxTjSfC.d.cts +120 -0
- package/dist/types-JlxTjSfC.d.ts +120 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# @zero-bits/kernel
|
|
2
|
+
|
|
3
|
+
一个面向微前端架构的高性能、零防备、框架无关的内核引擎。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- 🏗 **框架无关**:核心层(Core)不依赖任何 UI 框架,支持 Vue/React/Svelte 等任意架构。
|
|
8
|
+
- ⚛️ **原生 React 支持**:提供专门的 React 绑定层,内置 `PluginSlot` 插槽组件和 `Context` 实例分发。
|
|
9
|
+
- 🛡 **极佳容错与并发**:
|
|
10
|
+
- 自带多实例全局并发锁,同一个插件在多实例下只会加载一次网络请求。
|
|
11
|
+
- 自动缓存失效处理(基于 URL)。
|
|
12
|
+
- 内置超时控制(Timeout)与指数退避重试(Retry)。
|
|
13
|
+
- 🔌 **干净的 API 与自定义命名空间**:支持隔离多个引擎在同一页面的内核变量,防止命名空间污染。
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @zero-bits/kernel
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 导出说明
|
|
22
|
+
|
|
23
|
+
本包采用 `exports` 子路径导出设计,彻底隔离框架核心与视图层依赖:
|
|
24
|
+
|
|
25
|
+
- `@zero-bits/kernel` (只包含纯核心逻辑,无 React 依赖)
|
|
26
|
+
- `@zero-bits/kernel/react` (包含 React 插槽与上下文层,需在宿主安装 React)
|
|
27
|
+
|
|
28
|
+
## 快速使用 (React 环境)
|
|
29
|
+
|
|
30
|
+
### 1. 注册核心实例
|
|
31
|
+
|
|
32
|
+
在主应用入口或 Context 顶层,初始化并提供 `Kernel` 实例:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import React from 'react'
|
|
36
|
+
import { createKernel } from '@zero-bits/kernel'
|
|
37
|
+
import { KernelProvider } from '@zero-bits/kernel/react'
|
|
38
|
+
|
|
39
|
+
const kernel = createKernel({
|
|
40
|
+
timeout: 5000,
|
|
41
|
+
retryCount: 3,
|
|
42
|
+
namespace: '__MY_APP__' // 隔离多内核实例冲突
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// 注入共享依赖供子插件使用
|
|
46
|
+
kernel.regSharedModule('react', React)
|
|
47
|
+
|
|
48
|
+
export function App() {
|
|
49
|
+
return (
|
|
50
|
+
<KernelProvider kernel={kernel}>
|
|
51
|
+
<MainLayout />
|
|
52
|
+
</KernelProvider>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. 在业务组件中加载远端插件
|
|
58
|
+
|
|
59
|
+
直接使用 `PluginSlot` 占位,它会自动从上下文中获取 Kernel 并渲染远端组件。
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { PluginSlot } from '@zero-bits/kernel/react'
|
|
63
|
+
import { Spin } from 'antd'
|
|
64
|
+
|
|
65
|
+
export default function Dashboard() {
|
|
66
|
+
return (
|
|
67
|
+
<PluginSlot
|
|
68
|
+
pluginName="user-dashboard"
|
|
69
|
+
url="https://cdn.example.com/plugins/user-dashboard.js"
|
|
70
|
+
styleUrls={["https://cdn.example.com/plugins/user-dashboard.css"]}
|
|
71
|
+
fallback={<Spin spinning>Loading Plugin...</Spin>}
|
|
72
|
+
renderError={(err, retry) => (
|
|
73
|
+
<div style={{ color: 'red' }}>
|
|
74
|
+
{err.message} <button onClick={retry}>重试</button>
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
/>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 原生使用 (非 React)
|
|
83
|
+
|
|
84
|
+
对于 Vue 或原生 JS 开发者,可以直接使用核心加载器:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { createKernel } from '@zero-bits/kernel'
|
|
88
|
+
|
|
89
|
+
const kernel = createKernel()
|
|
90
|
+
kernel.regSharedModule('vue', Vue)
|
|
91
|
+
|
|
92
|
+
kernel.loadPlugin('my-vue-plugin', {
|
|
93
|
+
url: 'https://...',
|
|
94
|
+
styleUrls: ['https://...']
|
|
95
|
+
}).then(mod => {
|
|
96
|
+
// mod.default 即为插件导出的根组件/函数
|
|
97
|
+
mod.default.mount('#app')
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 核心 API
|
|
102
|
+
|
|
103
|
+
### `createKernel(options?: KernelOptions)`
|
|
104
|
+
|
|
105
|
+
创建并返回一个新的内核实例。
|
|
106
|
+
|
|
107
|
+
| 参数名 | 类型 | 默认值 | 说明 |
|
|
108
|
+
| --- | --- | --- | --- |
|
|
109
|
+
| `timeout` | `number` | `10000` | 加载超时时间(毫秒) |
|
|
110
|
+
| `retryCount` | `number` | `2` | 网络失败自动重试次数 |
|
|
111
|
+
| `namespace` | `string` | `'__SMART_HUB__'` | 全局拦截器的命名空间前缀 |
|
|
112
|
+
| `debug` | `boolean` | `true` | 是否输出内核 warn/log |
|
|
113
|
+
|
|
114
|
+
### `KernelInstance`
|
|
115
|
+
|
|
116
|
+
- `regSharedModule(name, instance, options)`: 注册主应用的共享依赖。
|
|
117
|
+
- `loadPlugin(pluginName, options)`: 请求加载远端插件。
|
|
118
|
+
- `unloadPlugin(pluginName)`: 卸载并在内存中清除某插件记录。
|
|
119
|
+
- `destroy()`: 销毁整个内核实例并回收内存。
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
// src/core/types.ts
|
|
2
|
+
var PluginError = class extends Error {
|
|
3
|
+
constructor(pluginName, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "PluginError";
|
|
6
|
+
this.pluginName = pluginName;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var PluginNetworkError = class extends PluginError {
|
|
10
|
+
constructor(pluginName, url, cause) {
|
|
11
|
+
super(pluginName, `[Kernel] \u63D2\u4EF6 [${pluginName}] \u7F51\u7EDC\u52A0\u8F7D\u5931\u8D25\uFF0CURL: ${url}`);
|
|
12
|
+
this.name = "PluginNetworkError";
|
|
13
|
+
if (cause) this.cause = cause;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var PluginTimeoutError = class extends PluginError {
|
|
17
|
+
constructor(pluginName, timeout) {
|
|
18
|
+
super(pluginName, `[Kernel] \u63D2\u4EF6 [${pluginName}] \u52A0\u8F7D\u8D85\u65F6\uFF08>${timeout}ms\uFF09`);
|
|
19
|
+
this.name = "PluginTimeoutError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var PluginDependencyError = class extends PluginError {
|
|
23
|
+
constructor(pluginName, dependency) {
|
|
24
|
+
super(pluginName, `[Kernel] \u63D2\u4EF6 [${pluginName}] \u7F3A\u5C11\u4F9D\u8D56: [${dependency}]\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u8C03\u7528 regSharedModule \u6CE8\u5165`);
|
|
25
|
+
this.name = "PluginDependencyError";
|
|
26
|
+
this.dependency = dependency;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var PluginExecutionError = class extends PluginError {
|
|
30
|
+
constructor(pluginName, cause) {
|
|
31
|
+
super(pluginName, `[Kernel] \u63D2\u4EF6 [${pluginName}] \u6267\u884C\u521D\u59CB\u5316\u5931\u8D25`);
|
|
32
|
+
this.name = "PluginExecutionError";
|
|
33
|
+
if (cause) this.cause = cause;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var KernelNotInitializedError = class extends Error {
|
|
37
|
+
constructor() {
|
|
38
|
+
super("[Kernel] \u5185\u6838\u5F15\u64CE\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 initKernel() \u6216 createKernel()");
|
|
39
|
+
this.name = "KernelNotInitializedError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/core/core.ts
|
|
44
|
+
function toPluginError(pluginName, err) {
|
|
45
|
+
if (err instanceof PluginError) return err;
|
|
46
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
47
|
+
return new PluginError(pluginName, message);
|
|
48
|
+
}
|
|
49
|
+
function withTimeout(promise, ms, error) {
|
|
50
|
+
let timer;
|
|
51
|
+
const timeout = new Promise((_, reject) => {
|
|
52
|
+
timer = setTimeout(() => reject(error), ms);
|
|
53
|
+
});
|
|
54
|
+
promise.catch(() => {
|
|
55
|
+
});
|
|
56
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
57
|
+
}
|
|
58
|
+
function createDeferred() {
|
|
59
|
+
let resolve;
|
|
60
|
+
let reject;
|
|
61
|
+
const promise = new Promise((res, rej) => {
|
|
62
|
+
resolve = res;
|
|
63
|
+
reject = rej;
|
|
64
|
+
});
|
|
65
|
+
return { promise, resolve, reject };
|
|
66
|
+
}
|
|
67
|
+
var Kernel = class {
|
|
68
|
+
/**
|
|
69
|
+
* 构造函数
|
|
70
|
+
* @param options 内核配置
|
|
71
|
+
* @default timeout: 10000
|
|
72
|
+
* @default retryCount: 2
|
|
73
|
+
* @default nonce: ''
|
|
74
|
+
* @default debug: true
|
|
75
|
+
* @default namespace: ''
|
|
76
|
+
* @default logger: console
|
|
77
|
+
* @default onLoadStart: () => {}
|
|
78
|
+
* @default onLoadSuccess: () => {}
|
|
79
|
+
* @default onLoadError: () => {}
|
|
80
|
+
*/
|
|
81
|
+
constructor(options) {
|
|
82
|
+
this.modules = /* @__PURE__ */ new Map();
|
|
83
|
+
this.ownHandlers = /* @__PURE__ */ new Map();
|
|
84
|
+
this.initialized = false;
|
|
85
|
+
if (!options?.namespace) {
|
|
86
|
+
throw new Error("[SmartHub] \u5FC5\u987B\u914D\u7F6E namespace \u5168\u79F0 (\u4F8B\u5982: __SMART_HUB___DEFINE)");
|
|
87
|
+
}
|
|
88
|
+
this.logger = options?.logger || console;
|
|
89
|
+
this.config = {
|
|
90
|
+
timeout: options?.timeout ?? 1e4,
|
|
91
|
+
retryCount: options?.retryCount ?? 2,
|
|
92
|
+
nonce: options?.nonce ?? "",
|
|
93
|
+
debug: options?.debug ?? true,
|
|
94
|
+
namespace: options.namespace,
|
|
95
|
+
logger: this.logger,
|
|
96
|
+
onLoadStart: options?.onLoadStart ?? (() => {
|
|
97
|
+
}),
|
|
98
|
+
onLoadSuccess: options?.onLoadSuccess ?? (() => {
|
|
99
|
+
}),
|
|
100
|
+
onLoadError: options?.onLoadError ?? (() => {
|
|
101
|
+
})
|
|
102
|
+
};
|
|
103
|
+
this.initGlobalInterceptor();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 获取全局Pending Map
|
|
107
|
+
* @returns 全局Pending Map
|
|
108
|
+
*/
|
|
109
|
+
get globalPendingMap() {
|
|
110
|
+
if (typeof window === "undefined") return void 0;
|
|
111
|
+
const defineFn = window[this.config.namespace];
|
|
112
|
+
return defineFn ? defineFn._pendingMap : void 0;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 获取全局资源并发锁
|
|
116
|
+
*/
|
|
117
|
+
get globalLocks() {
|
|
118
|
+
if (typeof window === "undefined") return /* @__PURE__ */ new Map();
|
|
119
|
+
const nsLocks = `${this.config.namespace}_LOCKS`;
|
|
120
|
+
if (!window[nsLocks]) {
|
|
121
|
+
window[nsLocks] = /* @__PURE__ */ new Map();
|
|
122
|
+
}
|
|
123
|
+
return window[nsLocks];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 初始化全局拦截器
|
|
127
|
+
*/
|
|
128
|
+
initGlobalInterceptor() {
|
|
129
|
+
if (typeof window === "undefined") return;
|
|
130
|
+
const ns = this.config.namespace;
|
|
131
|
+
if (!window[ns]) {
|
|
132
|
+
const defineFn = (pluginName, deps, factory) => {
|
|
133
|
+
const handlers = defineFn._pendingMap?.get(pluginName);
|
|
134
|
+
if (handlers && handlers.size > 0) {
|
|
135
|
+
handlers.forEach((handler) => handler(deps, factory));
|
|
136
|
+
handlers.clear();
|
|
137
|
+
} else {
|
|
138
|
+
console.warn(`[SmartHub] \u6536\u5230\u672A\u77E5\u7684\u63D2\u4EF6 [${pluginName}] define \u8C03\u7528`);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
defineFn._pendingMap = /* @__PURE__ */ new Map();
|
|
142
|
+
window[ns] = defineFn;
|
|
143
|
+
}
|
|
144
|
+
this.initialized = true;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 判断内核是否已初始化
|
|
148
|
+
* @returns true 如果内核已初始化,否则 false
|
|
149
|
+
*/
|
|
150
|
+
isInitialized() {
|
|
151
|
+
return this.initialized;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 注册共享模块
|
|
155
|
+
* @param name 模块名称
|
|
156
|
+
* @param instance 模块实例
|
|
157
|
+
* @param options 模块配置
|
|
158
|
+
* @default force: false
|
|
159
|
+
*/
|
|
160
|
+
regSharedModule(name, instance, options) {
|
|
161
|
+
if (this.modules.has(name) && !options?.force) {
|
|
162
|
+
if (this.config.debug) {
|
|
163
|
+
this.logger.warn(`[SmartHub] \u5171\u4EAB\u6A21\u5757 [${name}] \u5DF2\u6CE8\u518C\uFF0C\u5982\u9700\u8986\u76D6\u8BF7\u4F20\u5165 { force: true }`);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this.modules.set(name, {
|
|
168
|
+
module: instance,
|
|
169
|
+
_promise: Promise.resolve(instance),
|
|
170
|
+
status: "resolved"
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 注入脚本(带自毁机制与并发锁)
|
|
175
|
+
* @param url 脚本地址
|
|
176
|
+
* @param pluginName 插件名称
|
|
177
|
+
* @returns Promise对象
|
|
178
|
+
*/
|
|
179
|
+
injectScript(url, pluginName) {
|
|
180
|
+
const locks = this.globalLocks;
|
|
181
|
+
if (locks.has(url)) return locks.get(url);
|
|
182
|
+
const promise = new Promise((resolve, reject) => {
|
|
183
|
+
const script = document.createElement("script");
|
|
184
|
+
script.src = url;
|
|
185
|
+
script.async = true;
|
|
186
|
+
if (this.config.nonce) script.nonce = this.config.nonce;
|
|
187
|
+
const cleanup = () => {
|
|
188
|
+
script.onload = null;
|
|
189
|
+
script.onerror = null;
|
|
190
|
+
locks.delete(url);
|
|
191
|
+
if (script.parentNode) script.parentNode.removeChild(script);
|
|
192
|
+
};
|
|
193
|
+
script.onload = () => {
|
|
194
|
+
cleanup();
|
|
195
|
+
resolve();
|
|
196
|
+
};
|
|
197
|
+
script.onerror = (event) => {
|
|
198
|
+
cleanup();
|
|
199
|
+
reject(new PluginNetworkError(pluginName, url, event));
|
|
200
|
+
};
|
|
201
|
+
document.head.appendChild(script);
|
|
202
|
+
});
|
|
203
|
+
locks.set(url, promise);
|
|
204
|
+
return promise;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* 注入样式(带并发锁,防止 FOUC)
|
|
208
|
+
* @param url 样式地址
|
|
209
|
+
* @param pluginName 插件名称
|
|
210
|
+
* @returns Promise对象
|
|
211
|
+
*/
|
|
212
|
+
injectStyle(url, pluginName) {
|
|
213
|
+
const locks = this.globalLocks;
|
|
214
|
+
if (locks.has(url)) return locks.get(url);
|
|
215
|
+
const existing = document.querySelector(`link[href="${url}"][rel="stylesheet"]`);
|
|
216
|
+
if (existing) return Promise.resolve();
|
|
217
|
+
const promise = new Promise((resolve, reject) => {
|
|
218
|
+
const link = document.createElement("link");
|
|
219
|
+
link.rel = "stylesheet";
|
|
220
|
+
link.href = url;
|
|
221
|
+
if (this.config.nonce) link.nonce = this.config.nonce;
|
|
222
|
+
const cleanup = () => {
|
|
223
|
+
link.onload = null;
|
|
224
|
+
link.onerror = null;
|
|
225
|
+
locks.delete(url);
|
|
226
|
+
};
|
|
227
|
+
link.onload = () => {
|
|
228
|
+
cleanup();
|
|
229
|
+
resolve();
|
|
230
|
+
};
|
|
231
|
+
link.onerror = (event) => {
|
|
232
|
+
cleanup();
|
|
233
|
+
if (link.parentNode) link.parentNode.removeChild(link);
|
|
234
|
+
reject(new PluginNetworkError(pluginName, url, event));
|
|
235
|
+
};
|
|
236
|
+
document.head.appendChild(link);
|
|
237
|
+
});
|
|
238
|
+
locks.set(url, promise);
|
|
239
|
+
return promise;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* 带重试的函数执行器
|
|
243
|
+
* @param fn 需要执行的函数
|
|
244
|
+
* @param pluginName 插件名称
|
|
245
|
+
* @returns Promise对象
|
|
246
|
+
*/
|
|
247
|
+
async withRetry(fn, pluginName) {
|
|
248
|
+
let lastError;
|
|
249
|
+
for (let attempt = 0; attempt <= this.config.retryCount; attempt++) {
|
|
250
|
+
try {
|
|
251
|
+
return await fn();
|
|
252
|
+
} catch (err) {
|
|
253
|
+
lastError = err;
|
|
254
|
+
if (attempt < this.config.retryCount) {
|
|
255
|
+
await new Promise((r) => setTimeout(r, 100 * Math.pow(2, attempt)));
|
|
256
|
+
if (this.config.debug) {
|
|
257
|
+
this.logger.warn(`[SmartHub] \u63D2\u4EF6 [${pluginName}] \u7B2C ${attempt + 1} \u6B21\u91CD\u8BD5\u52A0\u8F7D...`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
throw lastError;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* 解析工厂函数(支持异步依赖树)
|
|
266
|
+
* @param pluginName 插件名称
|
|
267
|
+
* @param deps 依赖项
|
|
268
|
+
* @param factory 工厂函数
|
|
269
|
+
* @returns Promise对象
|
|
270
|
+
*/
|
|
271
|
+
async resolveFactory(pluginName, deps, factory) {
|
|
272
|
+
let resolvedDeps;
|
|
273
|
+
try {
|
|
274
|
+
resolvedDeps = await Promise.all(deps.map((dep) => {
|
|
275
|
+
const entry = this.modules.get(dep);
|
|
276
|
+
if (!entry) throw new PluginDependencyError(pluginName, dep);
|
|
277
|
+
return entry._promise;
|
|
278
|
+
}));
|
|
279
|
+
} catch (err) {
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
return factory(...resolvedDeps);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
throw new PluginExecutionError(pluginName, err);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 注册待处理函数
|
|
290
|
+
* @param pluginName 插件名称
|
|
291
|
+
* @returns Promise对象
|
|
292
|
+
*/
|
|
293
|
+
registerPendingHandler(pluginName) {
|
|
294
|
+
const handler = (deps, factory) => {
|
|
295
|
+
const entry = this.modules.get(pluginName);
|
|
296
|
+
if (entry) entry._defineCalled = true;
|
|
297
|
+
this.resolveFactory(pluginName, deps, factory).then((mod) => {
|
|
298
|
+
const entry2 = this.modules.get(pluginName);
|
|
299
|
+
if (entry2) {
|
|
300
|
+
entry2.module = mod;
|
|
301
|
+
entry2.status = "resolved";
|
|
302
|
+
entry2._resolve?.(mod);
|
|
303
|
+
}
|
|
304
|
+
}).catch((err) => {
|
|
305
|
+
const entry2 = this.modules.get(pluginName);
|
|
306
|
+
if (entry2) {
|
|
307
|
+
entry2.status = "rejected";
|
|
308
|
+
entry2._reject?.(err);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
this.ownHandlers.delete(pluginName);
|
|
312
|
+
};
|
|
313
|
+
const ns = this.config.namespace;
|
|
314
|
+
const defineFn = window[ns];
|
|
315
|
+
if (!defineFn) throw new Error(`[SmartHub] \u5168\u5C40\u62E6\u622A\u5668 ${ns} \u672A\u521D\u59CB\u5316`);
|
|
316
|
+
if (!defineFn._pendingMap) {
|
|
317
|
+
defineFn._pendingMap = /* @__PURE__ */ new Map();
|
|
318
|
+
}
|
|
319
|
+
const globalMap = defineFn._pendingMap;
|
|
320
|
+
if (!globalMap.has(pluginName)) {
|
|
321
|
+
globalMap.set(pluginName, /* @__PURE__ */ new Set());
|
|
322
|
+
}
|
|
323
|
+
globalMap.get(pluginName).add(handler);
|
|
324
|
+
this.ownHandlers.set(pluginName, handler);
|
|
325
|
+
return handler;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* 加载插件
|
|
329
|
+
* @param pluginName 插件名称
|
|
330
|
+
* @param options 插件配置
|
|
331
|
+
* @returns Promise对象
|
|
332
|
+
*/
|
|
333
|
+
async loadPlugin(pluginName, options) {
|
|
334
|
+
if (!this.initialized) throw new KernelNotInitializedError();
|
|
335
|
+
const url = typeof options === "string" ? options : options.url;
|
|
336
|
+
const styleUrls = typeof options === "string" ? [] : options.styleUrls || [];
|
|
337
|
+
const cached = this.modules.get(pluginName);
|
|
338
|
+
if (cached && (cached.status === "resolved" || cached.status === "pending") && cached.url === url) {
|
|
339
|
+
return cached._promise;
|
|
340
|
+
}
|
|
341
|
+
if (cached && cached.url !== url) this.unloadPlugin(pluginName);
|
|
342
|
+
this.config.onLoadStart(pluginName);
|
|
343
|
+
const deferred = createDeferred();
|
|
344
|
+
this.modules.set(pluginName, {
|
|
345
|
+
module: null,
|
|
346
|
+
_promise: deferred.promise,
|
|
347
|
+
_resolve: deferred.resolve,
|
|
348
|
+
_reject: deferred.reject,
|
|
349
|
+
status: "pending",
|
|
350
|
+
_defineCalled: false,
|
|
351
|
+
url
|
|
352
|
+
});
|
|
353
|
+
let handler;
|
|
354
|
+
try {
|
|
355
|
+
handler = this.registerPendingHandler(pluginName);
|
|
356
|
+
await this.withRetry(
|
|
357
|
+
() => withTimeout(
|
|
358
|
+
Promise.all([
|
|
359
|
+
this.injectScript(url, pluginName),
|
|
360
|
+
...styleUrls.map((styleUrl) => this.injectStyle(styleUrl, pluginName))
|
|
361
|
+
]),
|
|
362
|
+
this.config.timeout,
|
|
363
|
+
new PluginTimeoutError(pluginName, this.config.timeout)
|
|
364
|
+
),
|
|
365
|
+
pluginName
|
|
366
|
+
);
|
|
367
|
+
const entry = this.modules.get(pluginName);
|
|
368
|
+
if (!entry._defineCalled) {
|
|
369
|
+
throw new PluginExecutionError(pluginName, `Plugin script loaded but ${this.config.namespace} was not called.`);
|
|
370
|
+
}
|
|
371
|
+
const mod = await withTimeout(
|
|
372
|
+
entry._promise,
|
|
373
|
+
this.config.timeout,
|
|
374
|
+
new PluginTimeoutError(pluginName, this.config.timeout)
|
|
375
|
+
);
|
|
376
|
+
this.config.onLoadSuccess(pluginName);
|
|
377
|
+
return mod;
|
|
378
|
+
} catch (err) {
|
|
379
|
+
this.globalPendingMap?.get(pluginName)?.delete(handler);
|
|
380
|
+
this.ownHandlers.delete(pluginName);
|
|
381
|
+
const entry = this.modules.get(pluginName);
|
|
382
|
+
if (entry) {
|
|
383
|
+
entry.status = "rejected";
|
|
384
|
+
entry._reject?.(err);
|
|
385
|
+
}
|
|
386
|
+
const pluginErr = toPluginError(pluginName, err);
|
|
387
|
+
this.config.onLoadError(pluginName, pluginErr);
|
|
388
|
+
throw pluginErr;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* 卸载插件
|
|
393
|
+
* @param pluginName 插件名称
|
|
394
|
+
*/
|
|
395
|
+
unloadPlugin(pluginName) {
|
|
396
|
+
this.modules.delete(pluginName);
|
|
397
|
+
const handler = this.ownHandlers.get(pluginName);
|
|
398
|
+
if (handler) {
|
|
399
|
+
this.globalPendingMap?.get(pluginName)?.delete(handler);
|
|
400
|
+
this.ownHandlers.delete(pluginName);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* 预加载远端插件资源到浏览器缓存(不执行)
|
|
405
|
+
* @param options 插件地址或配置
|
|
406
|
+
*/
|
|
407
|
+
preloadPlugin(options) {
|
|
408
|
+
if (typeof document === "undefined") return;
|
|
409
|
+
const url = typeof options === "string" ? options : options.url;
|
|
410
|
+
const styleUrls = typeof options === "string" ? [] : options.styleUrls || [];
|
|
411
|
+
const preloadResource = (href, as) => {
|
|
412
|
+
if (document.querySelector(`link[href="${href}"][rel="preload"]`)) return;
|
|
413
|
+
const link = document.createElement("link");
|
|
414
|
+
link.rel = "preload";
|
|
415
|
+
link.href = href;
|
|
416
|
+
link.as = as;
|
|
417
|
+
if (this.config.nonce) link.nonce = this.config.nonce;
|
|
418
|
+
document.head.appendChild(link);
|
|
419
|
+
};
|
|
420
|
+
if (url) preloadResource(url, "script");
|
|
421
|
+
styleUrls.forEach((styleUrl) => preloadResource(styleUrl, "style"));
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* 销毁内核实例
|
|
425
|
+
*/
|
|
426
|
+
destroy() {
|
|
427
|
+
this.ownHandlers.forEach((handler, pluginName) => {
|
|
428
|
+
this.globalPendingMap?.get(pluginName)?.delete(handler);
|
|
429
|
+
});
|
|
430
|
+
this.ownHandlers.clear();
|
|
431
|
+
this.modules.clear();
|
|
432
|
+
this.initialized = false;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
function createKernel(options) {
|
|
436
|
+
return new Kernel(options);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/core/index.ts
|
|
440
|
+
var globalKernel = null;
|
|
441
|
+
function initKernel(options) {
|
|
442
|
+
if (globalKernel) return console.warn("[SmartHub] initKernel() \u5DF2\u6267\u884C\u8FC7\uFF0C\u8BF7\u52FF\u91CD\u590D\u8C03\u7528");
|
|
443
|
+
globalKernel = createKernel(options);
|
|
444
|
+
}
|
|
445
|
+
function regSharedModule(name, instance, options) {
|
|
446
|
+
if (!globalKernel) {
|
|
447
|
+
console.warn(`[SmartHub] \u5168\u5C40\u5185\u6838\u672A\u521D\u59CB\u5316\uFF0C\u81EA\u52A8\u4EE5\u9ED8\u8BA4\u914D\u7F6E\u521D\u59CB\u5316...`);
|
|
448
|
+
globalKernel = createKernel();
|
|
449
|
+
}
|
|
450
|
+
globalKernel.regSharedModule(name, instance, options);
|
|
451
|
+
}
|
|
452
|
+
async function loadRemotePlugin(pluginName, options) {
|
|
453
|
+
if (!globalKernel) {
|
|
454
|
+
throw new Error("[SmartHub] \u5185\u6838\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 initKernel()");
|
|
455
|
+
}
|
|
456
|
+
return globalKernel.loadPlugin(pluginName, options);
|
|
457
|
+
}
|
|
458
|
+
function preloadRemotePlugin(options) {
|
|
459
|
+
if (!globalKernel) {
|
|
460
|
+
console.warn("[SmartHub] \u5168\u5C40\u5185\u6838\u672A\u521D\u59CB\u5316\uFF0C\u81EA\u52A8\u4EE5\u9ED8\u8BA4\u914D\u7F6E\u521D\u59CB\u5316...");
|
|
461
|
+
globalKernel = createKernel();
|
|
462
|
+
}
|
|
463
|
+
globalKernel.preloadPlugin(options);
|
|
464
|
+
}
|
|
465
|
+
function getGlobalKernel() {
|
|
466
|
+
return globalKernel;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export {
|
|
470
|
+
PluginError,
|
|
471
|
+
PluginNetworkError,
|
|
472
|
+
PluginTimeoutError,
|
|
473
|
+
PluginDependencyError,
|
|
474
|
+
PluginExecutionError,
|
|
475
|
+
KernelNotInitializedError,
|
|
476
|
+
createKernel,
|
|
477
|
+
initKernel,
|
|
478
|
+
regSharedModule,
|
|
479
|
+
loadRemotePlugin,
|
|
480
|
+
preloadRemotePlugin,
|
|
481
|
+
getGlobalKernel
|
|
482
|
+
};
|