web-extend-plugin-vue2 0.2.5 → 0.3.1
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 +79 -189
- package/dist/index.cjs +489 -343
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +487 -337
- package/dist/index.mjs.map +1 -1
- package/index.d.ts +19 -177
- package/package.json +3 -6
package/dist/index.cjs
CHANGED
|
@@ -7,23 +7,15 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
7
7
|
var Vue__default = /*#__PURE__*/_interopDefault(Vue);
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Public constants and runtime defaults consumed by resolveRuntimeOptions and
|
|
11
|
+
* other runtime modules.
|
|
12
12
|
*/
|
|
13
|
-
/** 与 `package.json` 的 peer 下限一致;`npm run test:peer-min` / CI matrix 须与此保持同步 */
|
|
14
13
|
const peerMinimumVersions = {
|
|
15
|
-
vue: '2.6.
|
|
16
|
-
vueRouter: '3.5.
|
|
14
|
+
vue: '2.6.0',
|
|
15
|
+
vueRouter: '3.5.0'
|
|
17
16
|
};
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// 协议与品牌(发布契约;与清单 `hostPluginApiVersion` 对齐 semver 主版本,一般不随宿主覆盖)
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
17
|
const HOST_PLUGIN_API_VERSION = '1.0.0';
|
|
22
|
-
/** 控制台日志与首次引导横幅使用的短名称 */
|
|
23
18
|
const RUNTIME_CONSOLE_LABEL = 'web-extend-plugin-vue2';
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// 与 `build-env` / `resolveBundledEnv` 配套的键名(单一事实来源,便于检索与文档)
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
19
|
const webExtendPluginEnvKeys = {
|
|
28
20
|
manifestBase: 'VITE_FRONTEND_PLUGIN_BASE',
|
|
29
21
|
manifestListPath: 'VITE_WEB_PLUGIN_MANIFEST_PATH',
|
|
@@ -44,27 +36,14 @@ const webExtendPluginEnvKeys = {
|
|
|
44
36
|
webPluginDevIds: 'VITE_WEB_PLUGIN_DEV_IDS',
|
|
45
37
|
webPluginDevMap: 'VITE_WEB_PLUGIN_DEV_MAP',
|
|
46
38
|
pluginsBootstrapSummary: 'VITE_PLUGINS_BOOTSTRAP_SUMMARY',
|
|
47
|
-
/** 清单路径备选:部分宿主单独使用 */
|
|
48
39
|
manifestPathAlt: 'VUE_APP_WEB_PLUGIN_MANIFEST_PATH'
|
|
49
40
|
};
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// `manifestMode` 未配置且环境未指定时的默认值
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
41
|
const defaultManifestMode = 'api';
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
// `manifest-fetch-composer` 中间件默认(中间件 options 可逐项覆盖)
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
42
|
const defaultManifestFetchCache = {
|
|
58
43
|
storageKeyPrefix: 'wep.manifestFetch.v1',
|
|
59
44
|
maxEntries: 50
|
|
60
45
|
};
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// 路由合成名前缀(`createHostApi` 为无 name 的路由生成 `__wep_${pluginId}_${seq}`)
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
46
|
const routeSynthNamePrefix = '__wep_';
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
// 宿主可通过 `resolveRuntimeOptions` 覆盖的运行时默认值(与 README / index.d.ts 描述一致)
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
47
|
const defaultWebExtendPluginRuntime = {
|
|
69
48
|
manifestBase: '/fp-api',
|
|
70
49
|
manifestListPath: '/api/frontend-plugins',
|
|
@@ -76,11 +55,7 @@ const defaultWebExtendPluginRuntime = {
|
|
|
76
55
|
defaultImplicitDevPluginIds: [],
|
|
77
56
|
allowedScriptHosts: ['localhost', '127.0.0.1', '::1'],
|
|
78
57
|
bridgeAllowedPathPrefixes: ['/api/'],
|
|
79
|
-
/** 与 `hostLayoutComponent` 同时使用时默认父路由 name */
|
|
80
|
-
pluginRoutesParentName: '__wepPluginHost',
|
|
81
|
-
/** 插件壳路径(与菜单、ensurePluginHostRoute 一致) */
|
|
82
58
|
pluginMountPath: '/plugin',
|
|
83
|
-
/** `manifestMode=api` 且开发环境下,API 失败或 plugins 为空时尝试的静态 JSON(可放于 `public/web-plugins/`) */
|
|
84
59
|
devFallbackStaticManifestUrl: '/web-plugins/plugins.manifest.json'
|
|
85
60
|
};
|
|
86
61
|
|
|
@@ -344,15 +319,9 @@ function resolveRuntimeOptions$1(user = {}) {
|
|
|
344
319
|
return String(DEF.devFallbackStaticManifestUrl).trim();
|
|
345
320
|
})();
|
|
346
321
|
const hostLayoutComponent = user.hostLayoutComponent;
|
|
347
|
-
const pluginRoutesParentName = (()
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
if (hostLayoutComponent != null) {
|
|
352
|
-
return String(DEF.pluginRoutesParentName).trim();
|
|
353
|
-
}
|
|
354
|
-
return '';
|
|
355
|
-
})();
|
|
322
|
+
const pluginRoutesParentName = user.pluginRoutesParentName !== undefined && String(user.pluginRoutesParentName).trim() !== ''
|
|
323
|
+
? String(user.pluginRoutesParentName).trim()
|
|
324
|
+
: '';
|
|
356
325
|
const pluginMountRaw = user.pluginMountPath !== undefined && String(user.pluginMountPath).trim() !== ''
|
|
357
326
|
? String(user.pluginMountPath).trim()
|
|
358
327
|
: String(resolveBundledEnv(EK.mountPath, '') || DEF.pluginMountPath).trim();
|
|
@@ -360,7 +329,7 @@ function resolveRuntimeOptions$1(user = {}) {
|
|
|
360
329
|
const pluginHostRouteMeta = user.pluginHostRouteMeta !== undefined && user.pluginHostRouteMeta !== null
|
|
361
330
|
? user.pluginHostRouteMeta
|
|
362
331
|
: undefined;
|
|
363
|
-
const ensurePluginHostRoute = user.ensurePluginHostRoute
|
|
332
|
+
const ensurePluginHostRoute = user.ensurePluginHostRoute === true;
|
|
364
333
|
const devManifestFallback = (() => {
|
|
365
334
|
if (manifestMode === 'static') {
|
|
366
335
|
return false;
|
|
@@ -423,11 +392,8 @@ function resolveRuntimeOptions$1(user = {}) {
|
|
|
423
392
|
...(typeof user.adaptRouteDeclarations === 'function'
|
|
424
393
|
? { adaptRouteDeclarations: user.adaptRouteDeclarations }
|
|
425
394
|
: {}),
|
|
426
|
-
...(typeof user.
|
|
427
|
-
? {
|
|
428
|
-
: {}),
|
|
429
|
-
...(typeof user.revokePluginMenuItems === 'function'
|
|
430
|
-
? { revokePluginMenuItems: user.revokePluginMenuItems }
|
|
395
|
+
...(typeof user.onPluginRoutesContributed === 'function'
|
|
396
|
+
? { onPluginRoutesContributed: user.onPluginRoutesContributed }
|
|
431
397
|
: {}),
|
|
432
398
|
...(user.hostContext !== undefined &&
|
|
433
399
|
user.hostContext !== null &&
|
|
@@ -3220,23 +3186,220 @@ var semverExports = requireSemver();
|
|
|
3220
3186
|
var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
|
|
3221
3187
|
|
|
3222
3188
|
/**
|
|
3223
|
-
*
|
|
3189
|
+
* 引导时注入的 router 引用,供 disposeWebPlugin 卸载插件动态路由。
|
|
3190
|
+
*/
|
|
3191
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3192
|
+
let bootstrapRouter;
|
|
3193
|
+
/** 由 bootstrapPlugins 在浏览器环境调用 */
|
|
3194
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3195
|
+
function setPluginBootstrapRouter(router) {
|
|
3196
|
+
bootstrapRouter = router;
|
|
3197
|
+
}
|
|
3198
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3199
|
+
function getPluginBootstrapRouter() {
|
|
3200
|
+
return bootstrapRouter;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
/**
|
|
3204
|
+
* 记录各插件通过 registerRoutes 挂到 router 上的顶层路由,
|
|
3205
|
+
* 优先使用官方 `addRoute()` 返回的 disposer 做卸载;仅在 disposer 不可用时回退到 name。
|
|
3206
|
+
*/
|
|
3207
|
+
const pluginIdToTopRoutes = new Map();
|
|
3208
|
+
function recordPluginTopRoutes(pluginId, routes) {
|
|
3209
|
+
if (!pluginId || routes.length === 0) {
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
const normalized = routes.filter((route) => route && route.name);
|
|
3213
|
+
if (normalized.length === 0) {
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
const cur = pluginIdToTopRoutes.get(pluginId) || [];
|
|
3217
|
+
pluginIdToTopRoutes.set(pluginId, cur.concat(normalized));
|
|
3218
|
+
}
|
|
3219
|
+
function tryRemoveRouteByName(
|
|
3220
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3221
|
+
router, pluginId, name) {
|
|
3222
|
+
if (!router || typeof router.removeRoute !== 'function') {
|
|
3223
|
+
console.warn('[wep] route disposer unavailable,且 router.removeRoute 不可用,动态路由可能残留', pluginId, name);
|
|
3224
|
+
return;
|
|
3225
|
+
}
|
|
3226
|
+
try {
|
|
3227
|
+
router.removeRoute(name);
|
|
3228
|
+
}
|
|
3229
|
+
catch (e) {
|
|
3230
|
+
console.warn('[wep] removeRoute fallback failed', name, e);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
/**
|
|
3234
|
+
* 从 matcher 移除该插件登记过的顶层路由(含其子树)。
|
|
3235
|
+
* 优先走 `router.addRoute()` 返回的 disposer;仅在 disposer 不可用时回退 `router.removeRoute(name)`。
|
|
3224
3236
|
*/
|
|
3225
|
-
|
|
3226
|
-
function
|
|
3227
|
-
|
|
3237
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3238
|
+
function removeRegisteredRoutesForPlugin(router, pluginId) {
|
|
3239
|
+
const routes = pluginIdToTopRoutes.get(pluginId);
|
|
3240
|
+
if (!routes || routes.length === 0) {
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
for (let i = routes.length - 1; i >= 0; i--) {
|
|
3244
|
+
const route = routes[i];
|
|
3245
|
+
if (typeof route.dispose === 'function') {
|
|
3246
|
+
try {
|
|
3247
|
+
route.dispose();
|
|
3248
|
+
continue;
|
|
3249
|
+
}
|
|
3250
|
+
catch (e) {
|
|
3251
|
+
console.warn('[wep] route disposer failed', route.name, e);
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
tryRemoveRouteByName(router, pluginId, route.name);
|
|
3255
|
+
}
|
|
3256
|
+
pluginIdToTopRoutes.delete(pluginId);
|
|
3257
|
+
}
|
|
3258
|
+
/** 调试或宿主高级场景:查询已登记 name(勿依赖顺序语义) */
|
|
3259
|
+
function getRegisteredTopRouteNamesForPlugin(pluginId) {
|
|
3260
|
+
return (pluginIdToTopRoutes.get(pluginId) || []).map((route) => route.name);
|
|
3228
3261
|
}
|
|
3229
|
-
|
|
3230
|
-
|
|
3262
|
+
|
|
3263
|
+
/**
|
|
3264
|
+
* 宿主侧响应式注册表:扩展点槽位(供布局与 `ExtensionPoint` 消费)。
|
|
3265
|
+
* 菜单/侧栏树完全由宿主基于路由快照自行映射,框架不维护平行菜单列表。
|
|
3266
|
+
*/
|
|
3267
|
+
const registries = Vue__default.default.observable({
|
|
3268
|
+
slots: {},
|
|
3269
|
+
slotRevision: 0
|
|
3270
|
+
});
|
|
3271
|
+
|
|
3272
|
+
/**
|
|
3273
|
+
* 按插件 id 收集 `onTeardown` 回调,供 `disposeWebPlugin` 统一执行。
|
|
3274
|
+
*/
|
|
3275
|
+
const _byPlugin = new Map();
|
|
3276
|
+
function registerPluginTeardown(pluginId, fn) {
|
|
3277
|
+
if (typeof fn !== 'function') {
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
let list = _byPlugin.get(pluginId);
|
|
3281
|
+
if (!list) {
|
|
3282
|
+
list = [];
|
|
3283
|
+
_byPlugin.set(pluginId, list);
|
|
3284
|
+
}
|
|
3285
|
+
list.push(fn);
|
|
3286
|
+
}
|
|
3287
|
+
function runPluginTeardowns(pluginId) {
|
|
3288
|
+
const list = _byPlugin.get(pluginId);
|
|
3289
|
+
if (!list) {
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
_byPlugin.delete(pluginId);
|
|
3293
|
+
for (const fn of list) {
|
|
3231
3294
|
try {
|
|
3232
|
-
|
|
3295
|
+
fn();
|
|
3233
3296
|
}
|
|
3234
3297
|
catch (e) {
|
|
3235
|
-
console.warn('[wep]
|
|
3298
|
+
console.warn('[wep] teardown failed', pluginId, e);
|
|
3236
3299
|
}
|
|
3237
3300
|
}
|
|
3238
3301
|
}
|
|
3239
3302
|
|
|
3303
|
+
/**
|
|
3304
|
+
* 动态加载脚本(去重与并发合并)。
|
|
3305
|
+
*/
|
|
3306
|
+
const loadScriptMemo = new Map();
|
|
3307
|
+
function markScriptOwnership(script, pluginId) {
|
|
3308
|
+
if (pluginId) {
|
|
3309
|
+
script.setAttribute('data-plugin-asset', pluginId);
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
function clearLoadedScriptMemo(src) {
|
|
3313
|
+
if (typeof src === 'string' && src) {
|
|
3314
|
+
loadScriptMemo.delete(src);
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
3317
|
+
loadScriptMemo.clear();
|
|
3318
|
+
}
|
|
3319
|
+
function loadScript(src, pluginId) {
|
|
3320
|
+
if (typeof document === 'undefined') {
|
|
3321
|
+
return Promise.reject(new Error('loadScript: no document'));
|
|
3322
|
+
}
|
|
3323
|
+
if (loadScriptMemo.has(src)) {
|
|
3324
|
+
return loadScriptMemo.get(src);
|
|
3325
|
+
}
|
|
3326
|
+
const p = new Promise((resolve, reject) => {
|
|
3327
|
+
const scripts = document.getElementsByTagName('script');
|
|
3328
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
3329
|
+
const el = scripts[i];
|
|
3330
|
+
if (el.src === src) {
|
|
3331
|
+
markScriptOwnership(el, pluginId);
|
|
3332
|
+
if (el.getAttribute('data-wep-loaded') === 'true') {
|
|
3333
|
+
resolve();
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
el.addEventListener('load', () => {
|
|
3337
|
+
el.setAttribute('data-wep-loaded', 'true');
|
|
3338
|
+
resolve();
|
|
3339
|
+
}, { once: true });
|
|
3340
|
+
el.addEventListener('error', () => reject(new Error('loadScript failed: ' + src)), { once: true });
|
|
3341
|
+
return;
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
const s = document.createElement('script');
|
|
3345
|
+
s.async = true;
|
|
3346
|
+
s.src = src;
|
|
3347
|
+
markScriptOwnership(s, pluginId);
|
|
3348
|
+
s.onload = () => {
|
|
3349
|
+
s.setAttribute('data-wep-loaded', 'true');
|
|
3350
|
+
resolve();
|
|
3351
|
+
};
|
|
3352
|
+
s.onerror = () => reject(new Error('loadScript failed: ' + src));
|
|
3353
|
+
document.head.appendChild(s);
|
|
3354
|
+
});
|
|
3355
|
+
loadScriptMemo.set(src, p);
|
|
3356
|
+
p.catch(() => loadScriptMemo.delete(src));
|
|
3357
|
+
return p;
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
/**
|
|
3361
|
+
* 卸载单个插件:执行 teardown、移除该插件登记的动态路由、清理注册表与 activator、移除带 `data-plugin-asset` 的 DOM。
|
|
3362
|
+
* 优先使用 `addRoute()` 返回的 disposer;必要时回退 `router.removeRoute(name)`。
|
|
3363
|
+
*/
|
|
3364
|
+
function disposeWebPlugin(pluginId) {
|
|
3365
|
+
if (!pluginId || typeof pluginId !== 'string') {
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
runPluginTeardowns(pluginId);
|
|
3369
|
+
removeRegisteredRoutesForPlugin(getPluginBootstrapRouter(), pluginId);
|
|
3370
|
+
const slots = registries.slots;
|
|
3371
|
+
for (const pointId of Object.keys(slots)) {
|
|
3372
|
+
const list = slots[pointId];
|
|
3373
|
+
if (!Array.isArray(list)) {
|
|
3374
|
+
continue;
|
|
3375
|
+
}
|
|
3376
|
+
const next = list.filter((x) => x.pluginId !== pluginId);
|
|
3377
|
+
if (next.length === 0) {
|
|
3378
|
+
Vue__default.default.delete(slots, pointId);
|
|
3379
|
+
}
|
|
3380
|
+
else if (next.length !== list.length) {
|
|
3381
|
+
Vue__default.default.set(slots, pointId, next);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
registries.slotRevision++;
|
|
3385
|
+
if (typeof window !== 'undefined' && window.__PLUGIN_ACTIVATORS__) {
|
|
3386
|
+
delete window.__PLUGIN_ACTIVATORS__[pluginId];
|
|
3387
|
+
}
|
|
3388
|
+
if (typeof document !== 'undefined') {
|
|
3389
|
+
document.querySelectorAll('[data-plugin-asset]').forEach((el) => {
|
|
3390
|
+
if (el.getAttribute('data-plugin-asset') === pluginId) {
|
|
3391
|
+
if (el.tagName === 'SCRIPT') {
|
|
3392
|
+
const src = el.src;
|
|
3393
|
+
if (src) {
|
|
3394
|
+
clearLoadedScriptMemo(src);
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
el.remove();
|
|
3398
|
+
}
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3240
3403
|
/**
|
|
3241
3404
|
* 开发模式插件 URL 映射(显式 JSON + 隐式 dev 探测)。
|
|
3242
3405
|
*/
|
|
@@ -3399,49 +3562,6 @@ function startPluginDevSseForMap(devMap, isDev, hostSet, ssePath) {
|
|
|
3399
3562
|
}
|
|
3400
3563
|
}
|
|
3401
3564
|
|
|
3402
|
-
/**
|
|
3403
|
-
* 动态加载脚本(去重与并发合并)。
|
|
3404
|
-
*/
|
|
3405
|
-
const loadScriptMemo = new Map();
|
|
3406
|
-
function loadScript(src) {
|
|
3407
|
-
if (typeof document === 'undefined') {
|
|
3408
|
-
return Promise.reject(new Error('loadScript: no document'));
|
|
3409
|
-
}
|
|
3410
|
-
if (loadScriptMemo.has(src)) {
|
|
3411
|
-
return loadScriptMemo.get(src);
|
|
3412
|
-
}
|
|
3413
|
-
const p = new Promise((resolve, reject) => {
|
|
3414
|
-
const scripts = document.getElementsByTagName('script');
|
|
3415
|
-
for (let i = 0; i < scripts.length; i++) {
|
|
3416
|
-
const el = scripts[i];
|
|
3417
|
-
if (el.src === src) {
|
|
3418
|
-
if (el.getAttribute('data-wep-loaded') === 'true') {
|
|
3419
|
-
resolve();
|
|
3420
|
-
return;
|
|
3421
|
-
}
|
|
3422
|
-
el.addEventListener('load', () => {
|
|
3423
|
-
el.setAttribute('data-wep-loaded', 'true');
|
|
3424
|
-
resolve();
|
|
3425
|
-
}, { once: true });
|
|
3426
|
-
el.addEventListener('error', () => reject(new Error('loadScript failed: ' + src)), { once: true });
|
|
3427
|
-
return;
|
|
3428
|
-
}
|
|
3429
|
-
}
|
|
3430
|
-
const s = document.createElement('script');
|
|
3431
|
-
s.async = true;
|
|
3432
|
-
s.src = src;
|
|
3433
|
-
s.onload = () => {
|
|
3434
|
-
s.setAttribute('data-wep-loaded', 'true');
|
|
3435
|
-
resolve();
|
|
3436
|
-
};
|
|
3437
|
-
s.onerror = () => reject(new Error('loadScript failed: ' + src));
|
|
3438
|
-
document.head.appendChild(s);
|
|
3439
|
-
});
|
|
3440
|
-
loadScriptMemo.set(src, p);
|
|
3441
|
-
p.catch(() => loadScriptMemo.delete(src));
|
|
3442
|
-
return p;
|
|
3443
|
-
}
|
|
3444
|
-
|
|
3445
3565
|
let _printed = false;
|
|
3446
3566
|
/** 在首次引导插件时打印一行运行时标识(非大块 ASCII art)。 */
|
|
3447
3567
|
function printRuntimeBannerOnce() {
|
|
@@ -3491,7 +3611,8 @@ async function fetchStaticManifestViaHttp(ctx) {
|
|
|
3491
3611
|
}
|
|
3492
3612
|
|
|
3493
3613
|
/**
|
|
3494
|
-
*
|
|
3614
|
+
* 当 `ensurePluginHostRoute === true` 且提供 `pluginRoutesParentName` + `hostLayoutComponent` 时,
|
|
3615
|
+
* 注册 `pluginMountPath` + Layout 的命名父路由,供 `router.addRoute(parentName, child)` 挂载插件页。
|
|
3495
3616
|
*/
|
|
3496
3617
|
function routeNameExists(router, name) {
|
|
3497
3618
|
if (!name) {
|
|
@@ -3518,7 +3639,7 @@ function walkRouteNames(routes, name) {
|
|
|
3518
3639
|
return false;
|
|
3519
3640
|
}
|
|
3520
3641
|
function ensurePluginHostRoute$1(router, opts) {
|
|
3521
|
-
if (opts.ensurePluginHostRoute
|
|
3642
|
+
if (opts.ensurePluginHostRoute !== true) {
|
|
3522
3643
|
return;
|
|
3523
3644
|
}
|
|
3524
3645
|
if (!router || typeof router.addRoute !== 'function') {
|
|
@@ -3589,6 +3710,29 @@ function resolveStaticManifestUrlForFetch(url, origin) {
|
|
|
3589
3710
|
}
|
|
3590
3711
|
}
|
|
3591
3712
|
|
|
3713
|
+
/**
|
|
3714
|
+
* 记录当前已成功激活的插件 id,供宿主做只读观测。
|
|
3715
|
+
*/
|
|
3716
|
+
const activatedPluginIds = new Set();
|
|
3717
|
+
function clearActivatedPluginIds() {
|
|
3718
|
+
activatedPluginIds.clear();
|
|
3719
|
+
}
|
|
3720
|
+
function markPluginActivated(pluginId) {
|
|
3721
|
+
if (!pluginId) {
|
|
3722
|
+
return;
|
|
3723
|
+
}
|
|
3724
|
+
activatedPluginIds.add(pluginId);
|
|
3725
|
+
}
|
|
3726
|
+
function markPluginDeactivated(pluginId) {
|
|
3727
|
+
if (!pluginId) {
|
|
3728
|
+
return;
|
|
3729
|
+
}
|
|
3730
|
+
activatedPluginIds.delete(pluginId);
|
|
3731
|
+
}
|
|
3732
|
+
function getActivatedPluginIds$1() {
|
|
3733
|
+
return [...activatedPluginIds];
|
|
3734
|
+
}
|
|
3735
|
+
|
|
3592
3736
|
/**
|
|
3593
3737
|
* 拉取插件清单、加载入口脚本并调用各插件 `activator`。
|
|
3594
3738
|
*/
|
|
@@ -3624,7 +3768,7 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
|
|
|
3624
3768
|
catch (e) {
|
|
3625
3769
|
console.warn('[wep] dev import failed, trying manifest entryUrl', p.id, e);
|
|
3626
3770
|
if (entryUrl && isScriptHostAllowed(entryUrl, hostSet)) {
|
|
3627
|
-
await loadScript(entryUrl);
|
|
3771
|
+
await loadScript(entryUrl, p.id);
|
|
3628
3772
|
}
|
|
3629
3773
|
return;
|
|
3630
3774
|
}
|
|
@@ -3634,7 +3778,40 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
|
|
|
3634
3778
|
console.warn('[wep] skip (entryUrl not allowed)', p.id, entryUrl);
|
|
3635
3779
|
return;
|
|
3636
3780
|
}
|
|
3637
|
-
await loadScript(entryUrl);
|
|
3781
|
+
await loadScript(entryUrl, p.id);
|
|
3782
|
+
}
|
|
3783
|
+
function coercePriority(value) {
|
|
3784
|
+
if (value === null || value === undefined) {
|
|
3785
|
+
return null;
|
|
3786
|
+
}
|
|
3787
|
+
const n = Number(value);
|
|
3788
|
+
return Number.isFinite(n) ? n : null;
|
|
3789
|
+
}
|
|
3790
|
+
function sortByPriority(plugins) {
|
|
3791
|
+
return plugins
|
|
3792
|
+
.map((entry, index) => ({
|
|
3793
|
+
entry,
|
|
3794
|
+
priority: coercePriority(entry.priority),
|
|
3795
|
+
index
|
|
3796
|
+
}))
|
|
3797
|
+
.sort((a, b) => {
|
|
3798
|
+
const aHas = a.priority !== null;
|
|
3799
|
+
const bHas = b.priority !== null;
|
|
3800
|
+
if (!aHas && !bHas) {
|
|
3801
|
+
return a.index - b.index;
|
|
3802
|
+
}
|
|
3803
|
+
if (!aHas) {
|
|
3804
|
+
return 1;
|
|
3805
|
+
}
|
|
3806
|
+
if (!bHas) {
|
|
3807
|
+
return -1;
|
|
3808
|
+
}
|
|
3809
|
+
if (a.priority !== b.priority) {
|
|
3810
|
+
return a.priority - b.priority;
|
|
3811
|
+
}
|
|
3812
|
+
return a.index - b.index;
|
|
3813
|
+
})
|
|
3814
|
+
.map((decorated) => decorated.entry);
|
|
3638
3815
|
}
|
|
3639
3816
|
async function bootstrapPlugins$1(
|
|
3640
3817
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -3646,8 +3823,9 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3646
3823
|
return;
|
|
3647
3824
|
}
|
|
3648
3825
|
printRuntimeBannerOnce();
|
|
3826
|
+
clearActivatedPluginIds();
|
|
3649
3827
|
const opts = resolveRuntimeOptions$1(runtimeOptions || {});
|
|
3650
|
-
|
|
3828
|
+
setPluginBootstrapRouter(router);
|
|
3651
3829
|
ensurePluginHostRoute$1(router, opts);
|
|
3652
3830
|
const base = String(opts.manifestBase).replace(/\/$/, '');
|
|
3653
3831
|
const isStatic = opts.manifestMode === 'static';
|
|
@@ -3725,11 +3903,8 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3725
3903
|
...(typeof opts.adaptRouteDeclarations === 'function'
|
|
3726
3904
|
? { adaptRouteDeclarations: opts.adaptRouteDeclarations }
|
|
3727
3905
|
: {}),
|
|
3728
|
-
...(typeof opts.
|
|
3729
|
-
? {
|
|
3730
|
-
: {}),
|
|
3731
|
-
...(typeof opts.revokePluginMenuItems === 'function'
|
|
3732
|
-
? { revokePluginMenuItems: opts.revokePluginMenuItems }
|
|
3906
|
+
...(typeof opts.onPluginRoutesContributed === 'function'
|
|
3907
|
+
? { onPluginRoutesContributed: opts.onPluginRoutesContributed }
|
|
3733
3908
|
: {})
|
|
3734
3909
|
};
|
|
3735
3910
|
if (!manifestResult.ok) {
|
|
@@ -3758,22 +3933,30 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3758
3933
|
const maj = coerced ? coerced.major : 0;
|
|
3759
3934
|
const range = `^${maj}.0.0`;
|
|
3760
3935
|
if (!semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
|
|
3761
|
-
console.warn('[wep] host API version mismatch', {
|
|
3936
|
+
console.warn('[wep] manifest host API version mismatch; skip bootstrap', {
|
|
3762
3937
|
host: HOST_PLUGIN_API_VERSION,
|
|
3763
3938
|
manifest: apiVer
|
|
3764
3939
|
});
|
|
3940
|
+
if (shouldShowBootstrapSummary(opts)) {
|
|
3941
|
+
console.info('[wep] bootstrap_summary', {
|
|
3942
|
+
ok: false,
|
|
3943
|
+
reason: 'manifest_host_api_version_mismatch'
|
|
3944
|
+
});
|
|
3945
|
+
}
|
|
3946
|
+
return;
|
|
3765
3947
|
}
|
|
3766
3948
|
}
|
|
3767
3949
|
window.__PLUGIN_ACTIVATORS__ = window.__PLUGIN_ACTIVATORS__ || {};
|
|
3768
|
-
const
|
|
3950
|
+
const originalPlugins = data.plugins || [];
|
|
3951
|
+
const plugins = sortByPriority(originalPlugins);
|
|
3769
3952
|
if (plugins.length === 0) {
|
|
3770
3953
|
const hint = isStatic ? 'check static JSON file and plugins[]' : 'check backend and URL';
|
|
3771
3954
|
console.info('[wep] empty plugin manifest — ' + hint, manifestUrlUsed);
|
|
3772
3955
|
}
|
|
3773
3956
|
const summary = {
|
|
3774
|
-
manifestCount:
|
|
3957
|
+
manifestCount: originalPlugins.length,
|
|
3775
3958
|
activated: 0,
|
|
3776
|
-
|
|
3959
|
+
skipApiVersion: 0,
|
|
3777
3960
|
skipLoad: 0,
|
|
3778
3961
|
skipNoActivator: 0,
|
|
3779
3962
|
activateFail: 0
|
|
@@ -3781,8 +3964,10 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3781
3964
|
for (const p of plugins) {
|
|
3782
3965
|
const range = p.engines && p.engines.host;
|
|
3783
3966
|
if (range && !semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
|
|
3784
|
-
console.warn('[wep] skip plugin (engines.host)', p.id, range
|
|
3785
|
-
|
|
3967
|
+
console.warn('[wep] skip plugin (engines.host)', p.id, range, {
|
|
3968
|
+
host: HOST_PLUGIN_API_VERSION
|
|
3969
|
+
});
|
|
3970
|
+
summary.skipApiVersion++;
|
|
3786
3971
|
continue;
|
|
3787
3972
|
}
|
|
3788
3973
|
const entryUrl = p.entryUrl;
|
|
@@ -3818,6 +4003,11 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3818
4003
|
const hostApi = createHostApiFactory(p.id, router, hostKit);
|
|
3819
4004
|
try {
|
|
3820
4005
|
await Promise.resolve(activator(hostApi, { pluginRecord }));
|
|
4006
|
+
markPluginActivated(p.id);
|
|
4007
|
+
const teardownCapableHostApi = hostApi;
|
|
4008
|
+
if (typeof teardownCapableHostApi.onTeardown === 'function') {
|
|
4009
|
+
teardownCapableHostApi.onTeardown(p.id, () => markPluginDeactivated(p.id));
|
|
4010
|
+
}
|
|
3821
4011
|
summary.activated++;
|
|
3822
4012
|
if (typeof opts.onAfterPluginActivate === 'function') {
|
|
3823
4013
|
await Promise.resolve(opts.onAfterPluginActivate({
|
|
@@ -3830,6 +4020,12 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3830
4020
|
}
|
|
3831
4021
|
catch (e) {
|
|
3832
4022
|
console.error('[wep] activate failed', p.id, e);
|
|
4023
|
+
try {
|
|
4024
|
+
disposeWebPlugin(p.id);
|
|
4025
|
+
}
|
|
4026
|
+
catch (disposeErr) {
|
|
4027
|
+
console.warn('[wep] rollback failed after activation error', p.id, disposeErr);
|
|
4028
|
+
}
|
|
3833
4029
|
summary.activateFail++;
|
|
3834
4030
|
if (typeof opts.onPluginActivateError === 'function') {
|
|
3835
4031
|
try {
|
|
@@ -3860,6 +4056,7 @@ var pluginRuntime = /*#__PURE__*/Object.freeze({
|
|
|
3860
4056
|
bootstrapPlugins: bootstrapPlugins$1,
|
|
3861
4057
|
defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
|
|
3862
4058
|
ensurePluginHostRoute: ensurePluginHostRoute$1,
|
|
4059
|
+
getActivatedPluginIds: getActivatedPluginIds$1,
|
|
3863
4060
|
resolveRuntimeOptions: resolveRuntimeOptions$1
|
|
3864
4061
|
});
|
|
3865
4062
|
|
|
@@ -4018,48 +4215,40 @@ var manifestComposer = /*#__PURE__*/Object.freeze({
|
|
|
4018
4215
|
});
|
|
4019
4216
|
|
|
4020
4217
|
/**
|
|
4021
|
-
*
|
|
4022
|
-
* 菜单数据由宿主在 `applyPluginMenuItems` 中自行并入其路由/菜单 state,框架不维护平行菜单列表。
|
|
4023
|
-
*/
|
|
4024
|
-
const registries = Vue__default.default.observable({
|
|
4025
|
-
slots: {},
|
|
4026
|
-
slotRevision: 0
|
|
4027
|
-
});
|
|
4028
|
-
|
|
4029
|
-
/**
|
|
4030
|
-
* 按插件 id 收集 `onTeardown` 回调,供 `disposeWebPlugin` 统一执行。
|
|
4218
|
+
* 插件访问后端的受控通道:`fetch` 仅允许落在配置的路径前缀下(默认 `/api/`),默认 `credentials: 'same-origin'`。
|
|
4031
4219
|
*/
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
return;
|
|
4220
|
+
function normalizeBridgePath(input) {
|
|
4221
|
+
if (typeof window === 'undefined') {
|
|
4222
|
+
throw new Error('[wep:bridge] window is unavailable');
|
|
4036
4223
|
}
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
list = [];
|
|
4040
|
-
_byPlugin.set(pluginId, list);
|
|
4224
|
+
if (typeof input !== 'string' || !input.startsWith('/')) {
|
|
4225
|
+
throw new Error('[wep:bridge] path must start with /');
|
|
4041
4226
|
}
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
function runPluginTeardowns(pluginId) {
|
|
4045
|
-
const list = _byPlugin.get(pluginId);
|
|
4046
|
-
if (!list) {
|
|
4047
|
-
return;
|
|
4227
|
+
if (input.includes('\\')) {
|
|
4228
|
+
throw new Error('[wep:bridge] path must not contain backslashes');
|
|
4048
4229
|
}
|
|
4049
|
-
|
|
4050
|
-
|
|
4230
|
+
const url = new URL(input, window.location.origin);
|
|
4231
|
+
if (url.origin !== window.location.origin) {
|
|
4232
|
+
throw new Error('[wep:bridge] cross-origin path is not allowed');
|
|
4233
|
+
}
|
|
4234
|
+
const normalized = url.pathname;
|
|
4235
|
+
const decodedPath = (() => {
|
|
4051
4236
|
try {
|
|
4052
|
-
|
|
4237
|
+
return decodeURIComponent(normalized);
|
|
4053
4238
|
}
|
|
4054
|
-
catch
|
|
4055
|
-
|
|
4239
|
+
catch {
|
|
4240
|
+
return normalized;
|
|
4056
4241
|
}
|
|
4242
|
+
})();
|
|
4243
|
+
const hasTraversalSegment = decodedPath
|
|
4244
|
+
.split('/')
|
|
4245
|
+
.filter(Boolean)
|
|
4246
|
+
.some((segment) => segment === '.' || segment === '..');
|
|
4247
|
+
if (hasTraversalSegment) {
|
|
4248
|
+
throw new Error('[wep:bridge] path traversal is not allowed');
|
|
4057
4249
|
}
|
|
4250
|
+
return normalized + url.search;
|
|
4058
4251
|
}
|
|
4059
|
-
|
|
4060
|
-
/**
|
|
4061
|
-
* 插件访问后端的受控通道:`fetch` 仅允许落在配置的路径前缀下(默认 `/api/`),默认 `credentials: 'same-origin'`。
|
|
4062
|
-
*/
|
|
4063
4252
|
function createRequestBridge(config = {}) {
|
|
4064
4253
|
const raw = Array.isArray(config.allowedPathPrefixes) && config.allowedPathPrefixes.length > 0
|
|
4065
4254
|
? config.allowedPathPrefixes
|
|
@@ -4067,14 +4256,13 @@ function createRequestBridge(config = {}) {
|
|
|
4067
4256
|
const allowedPathPrefixes = raw.map((p) => ensureLeadingPath(p));
|
|
4068
4257
|
return {
|
|
4069
4258
|
async request(path, init = {}) {
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
}
|
|
4073
|
-
const allowed = allowedPathPrefixes.some((p) => path.startsWith(p));
|
|
4259
|
+
const normalizedPath = normalizeBridgePath(path);
|
|
4260
|
+
const pathnameOnly = normalizedPath.split('?')[0];
|
|
4261
|
+
const allowed = allowedPathPrefixes.some((p) => pathnameOnly === p || pathnameOnly.startsWith(`${p.replace(/\/$/, '')}/`));
|
|
4074
4262
|
if (!allowed) {
|
|
4075
|
-
throw new Error('[wep:bridge] path not allowed: ' +
|
|
4263
|
+
throw new Error('[wep:bridge] path not allowed: ' + normalizedPath);
|
|
4076
4264
|
}
|
|
4077
|
-
return fetch(
|
|
4265
|
+
return fetch(normalizedPath, {
|
|
4078
4266
|
credentials: 'same-origin',
|
|
4079
4267
|
...init
|
|
4080
4268
|
});
|
|
@@ -4083,10 +4271,83 @@ function createRequestBridge(config = {}) {
|
|
|
4083
4271
|
}
|
|
4084
4272
|
|
|
4085
4273
|
/**
|
|
4086
|
-
*
|
|
4274
|
+
* 按插件记录已贡献路由的可序列化快照,避免宿主依赖 router 内部 matcher 形态。
|
|
4275
|
+
*/
|
|
4276
|
+
const contributedRoutesByPlugin = new Map();
|
|
4277
|
+
function sanitizeValue(value) {
|
|
4278
|
+
if (value == null) {
|
|
4279
|
+
return value;
|
|
4280
|
+
}
|
|
4281
|
+
if (typeof value === 'function') {
|
|
4282
|
+
return undefined;
|
|
4283
|
+
}
|
|
4284
|
+
if (Array.isArray(value)) {
|
|
4285
|
+
return value
|
|
4286
|
+
.map((item) => sanitizeValue(item))
|
|
4287
|
+
.filter((item) => item !== undefined);
|
|
4288
|
+
}
|
|
4289
|
+
if (typeof value !== 'object') {
|
|
4290
|
+
return value;
|
|
4291
|
+
}
|
|
4292
|
+
const out = {};
|
|
4293
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
4294
|
+
if (key === 'component' || key === 'components') {
|
|
4295
|
+
continue;
|
|
4296
|
+
}
|
|
4297
|
+
const sanitized = sanitizeValue(raw);
|
|
4298
|
+
if (sanitized !== undefined) {
|
|
4299
|
+
out[key] = sanitized;
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4302
|
+
return out;
|
|
4303
|
+
}
|
|
4304
|
+
function sanitizeContributedRoutes(routes) {
|
|
4305
|
+
return routes
|
|
4306
|
+
.map((route) => sanitizeValue(route))
|
|
4307
|
+
.filter((route) => !!route && typeof route === 'object');
|
|
4308
|
+
}
|
|
4309
|
+
function recordContributedRoutesForPlugin(pluginId, routes) {
|
|
4310
|
+
const sanitized = sanitizeContributedRoutes(routes);
|
|
4311
|
+
const current = contributedRoutesByPlugin.get(pluginId) || [];
|
|
4312
|
+
const next = current.concat(sanitized);
|
|
4313
|
+
contributedRoutesByPlugin.set(pluginId, next);
|
|
4314
|
+
return next.map((route) => sanitizeValue(route));
|
|
4315
|
+
}
|
|
4316
|
+
function getContributedRoutesForPlugin(pluginId) {
|
|
4317
|
+
const routes = contributedRoutesByPlugin.get(pluginId) || [];
|
|
4318
|
+
return routes.map((route) => sanitizeValue(route));
|
|
4319
|
+
}
|
|
4320
|
+
function clearContributedRoutesForPlugin(pluginId) {
|
|
4321
|
+
contributedRoutesByPlugin.delete(pluginId);
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4324
|
+
/**
|
|
4325
|
+
* 构造插件 `activator(hostApi)` 使用的宿主 API:路由、扩展点、资源与受控请求桥。
|
|
4087
4326
|
*/
|
|
4088
4327
|
let slotItemKeySeq = 0;
|
|
4089
4328
|
let routeSynthSeq = 0;
|
|
4329
|
+
function decorateRouteTreeWithPluginMeta(pluginId, route) {
|
|
4330
|
+
const meta = route.meta && typeof route.meta === 'object' && !Array.isArray(route.meta)
|
|
4331
|
+
? route.meta
|
|
4332
|
+
: {};
|
|
4333
|
+
const wepMeta = meta.__wep && typeof meta.__wep === 'object' && !Array.isArray(meta.__wep)
|
|
4334
|
+
? meta.__wep
|
|
4335
|
+
: {};
|
|
4336
|
+
const nextChildren = Array.isArray(route.children)
|
|
4337
|
+
? route.children.map((child) => decorateRouteTreeWithPluginMeta(pluginId, child))
|
|
4338
|
+
: route.children;
|
|
4339
|
+
return {
|
|
4340
|
+
...route,
|
|
4341
|
+
meta: {
|
|
4342
|
+
...meta,
|
|
4343
|
+
__wep: {
|
|
4344
|
+
...wepMeta,
|
|
4345
|
+
pluginId
|
|
4346
|
+
}
|
|
4347
|
+
},
|
|
4348
|
+
...(nextChildren ? { children: nextChildren } : {})
|
|
4349
|
+
};
|
|
4350
|
+
}
|
|
4090
4351
|
function analyzeRouteInputTree(nodes) {
|
|
4091
4352
|
let hasDecl = false;
|
|
4092
4353
|
let hasCfg = false;
|
|
@@ -4128,25 +4389,71 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4128
4389
|
const parentName = typeof hostKitOptions.pluginRoutesParentName === 'string'
|
|
4129
4390
|
? hostKitOptions.pluginRoutesParentName.trim()
|
|
4130
4391
|
: '';
|
|
4392
|
+
function rollbackRegisteredTopRoutes(routes) {
|
|
4393
|
+
for (let i = routes.length - 1; i >= 0; i--) {
|
|
4394
|
+
const route = routes[i];
|
|
4395
|
+
if (typeof route.dispose === 'function') {
|
|
4396
|
+
try {
|
|
4397
|
+
route.dispose();
|
|
4398
|
+
continue;
|
|
4399
|
+
}
|
|
4400
|
+
catch (e) {
|
|
4401
|
+
console.warn('[wep] rollback route disposer failed', route.name, e);
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
if (typeof router.removeRoute === 'function') {
|
|
4405
|
+
try {
|
|
4406
|
+
router.removeRoute(route.name);
|
|
4407
|
+
}
|
|
4408
|
+
catch (e) {
|
|
4409
|
+
console.warn('[wep] rollback removeRoute failed', route.name, e);
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4131
4414
|
function applyInternalRegister(rawRouteConfigs) {
|
|
4132
4415
|
const wrapped = rawRouteConfigs.map((r) => ({
|
|
4133
|
-
...r,
|
|
4134
|
-
name: r.name || `${routeSynthNamePrefix}${pluginId}_${routeSynthSeq++}
|
|
4135
|
-
meta: { ...(r.meta || {}), pluginId }
|
|
4416
|
+
...decorateRouteTreeWithPluginMeta(pluginId, r),
|
|
4417
|
+
name: r.name || `${routeSynthNamePrefix}${pluginId}_${routeSynthSeq++}`
|
|
4136
4418
|
}));
|
|
4137
4419
|
if (typeof router.addRoute !== 'function') {
|
|
4138
4420
|
throw new Error('[wep] vue-router 3.5+ 必需:请使用 router.addRoute(不再支持 addRoutes)');
|
|
4139
4421
|
}
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4422
|
+
const registeredTopRoutes = [];
|
|
4423
|
+
try {
|
|
4424
|
+
if (parentName) {
|
|
4425
|
+
for (const r of wrapped) {
|
|
4426
|
+
const dispose = router.addRoute(parentName, r);
|
|
4427
|
+
registeredTopRoutes.push({
|
|
4428
|
+
name: String(r.name),
|
|
4429
|
+
dispose: typeof dispose === 'function' ? dispose : undefined
|
|
4430
|
+
});
|
|
4431
|
+
}
|
|
4143
4432
|
}
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4433
|
+
else {
|
|
4434
|
+
for (const r of wrapped) {
|
|
4435
|
+
const dispose = router.addRoute(r);
|
|
4436
|
+
registeredTopRoutes.push({
|
|
4437
|
+
name: String(r.name),
|
|
4438
|
+
dispose: typeof dispose === 'function' ? dispose : undefined
|
|
4439
|
+
});
|
|
4440
|
+
}
|
|
4148
4441
|
}
|
|
4149
4442
|
}
|
|
4443
|
+
catch (e) {
|
|
4444
|
+
rollbackRegisteredTopRoutes(registeredTopRoutes);
|
|
4445
|
+
throw e;
|
|
4446
|
+
}
|
|
4447
|
+
recordPluginTopRoutes(pluginId, registeredTopRoutes);
|
|
4448
|
+
const contributedRoutes = recordContributedRoutesForPlugin(pluginId, wrapped);
|
|
4449
|
+
if (contributedRoutes.length > 0 && typeof hostKitOptions.onPluginRoutesContributed === 'function') {
|
|
4450
|
+
hostKitOptions.onPluginRoutesContributed({
|
|
4451
|
+
pluginId,
|
|
4452
|
+
router,
|
|
4453
|
+
routes: wrapped,
|
|
4454
|
+
contributedRoutes
|
|
4455
|
+
});
|
|
4456
|
+
}
|
|
4150
4457
|
}
|
|
4151
4458
|
function injectStylesheet(href) {
|
|
4152
4459
|
const link = document.createElement('link');
|
|
@@ -4169,6 +4476,9 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4169
4476
|
const hostContext = hostKitOptions.hostContext != null && typeof hostKitOptions.hostContext === 'object'
|
|
4170
4477
|
? hostKitOptions.hostContext
|
|
4171
4478
|
: Object.freeze({});
|
|
4479
|
+
registerPluginTeardown(pluginId, () => {
|
|
4480
|
+
clearContributedRoutesForPlugin(pluginId);
|
|
4481
|
+
});
|
|
4172
4482
|
return {
|
|
4173
4483
|
hostPluginApiVersion: HOST_PLUGIN_API_VERSION,
|
|
4174
4484
|
/** 宿主注入的只读依赖(store、router、开放能力等),见 `resolveRuntimeOptions.hostContext` */
|
|
@@ -4216,16 +4526,6 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4216
4526
|
applyInternalRegister(configs);
|
|
4217
4527
|
}
|
|
4218
4528
|
},
|
|
4219
|
-
registerMenuItems(items) {
|
|
4220
|
-
const apply = hostKitOptions.applyPluginMenuItems;
|
|
4221
|
-
if (typeof apply !== 'function') {
|
|
4222
|
-
throw new Error('[wep] registerMenuItems 需要宿主在 resolveRuntimeOptions 中提供 applyPluginMenuItems,将菜单数据并入宿主侧栏/目录 state(框架不再维护 registries.menus)');
|
|
4223
|
-
}
|
|
4224
|
-
const list = Array.isArray(items) ? items : [];
|
|
4225
|
-
const enriched = list.map((item) => ({ ...item, pluginId }));
|
|
4226
|
-
enriched.sort((a, b) => (a.order != null ? Number(a.order) : 0) - (b.order != null ? Number(b.order) : 0));
|
|
4227
|
-
apply({ pluginId, items: enriched });
|
|
4228
|
-
},
|
|
4229
4529
|
registerSlotComponents(pointId, components) {
|
|
4230
4530
|
if (!pointId) {
|
|
4231
4531
|
return;
|
|
@@ -4259,6 +4559,7 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4259
4559
|
registerSanitizedHtmlSnippet() {
|
|
4260
4560
|
throw new Error('registerSanitizedHtmlSnippet is not enabled');
|
|
4261
4561
|
},
|
|
4562
|
+
getContributedRoutes: () => getContributedRoutesForPlugin(pluginId),
|
|
4262
4563
|
getBridge: () => bridge,
|
|
4263
4564
|
onTeardown(_pluginId, fn) {
|
|
4264
4565
|
if (typeof fn === 'function') {
|
|
@@ -4268,43 +4569,6 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4268
4569
|
};
|
|
4269
4570
|
}
|
|
4270
4571
|
|
|
4271
|
-
/**
|
|
4272
|
-
* 卸载单个插件:执行 teardown、清理注册表与 activator、移除带 `data-plugin-asset` 的 DOM。
|
|
4273
|
-
* 注意:Vue Router 3 无公开 `removeRoute`,动态路由通常需整页刷新或宿主自行维护。
|
|
4274
|
-
*/
|
|
4275
|
-
function disposeWebPlugin(pluginId) {
|
|
4276
|
-
if (!pluginId || typeof pluginId !== 'string') {
|
|
4277
|
-
return;
|
|
4278
|
-
}
|
|
4279
|
-
runPluginTeardowns(pluginId);
|
|
4280
|
-
revokePluginMenusIfConfigured(pluginId);
|
|
4281
|
-
const slots = registries.slots;
|
|
4282
|
-
for (const pointId of Object.keys(slots)) {
|
|
4283
|
-
const list = slots[pointId];
|
|
4284
|
-
if (!Array.isArray(list)) {
|
|
4285
|
-
continue;
|
|
4286
|
-
}
|
|
4287
|
-
const next = list.filter((x) => x.pluginId !== pluginId);
|
|
4288
|
-
if (next.length === 0) {
|
|
4289
|
-
Vue__default.default.delete(slots, pointId);
|
|
4290
|
-
}
|
|
4291
|
-
else if (next.length !== list.length) {
|
|
4292
|
-
Vue__default.default.set(slots, pointId, next);
|
|
4293
|
-
}
|
|
4294
|
-
}
|
|
4295
|
-
registries.slotRevision++;
|
|
4296
|
-
if (typeof window !== 'undefined' && window.__PLUGIN_ACTIVATORS__) {
|
|
4297
|
-
delete window.__PLUGIN_ACTIVATORS__[pluginId];
|
|
4298
|
-
}
|
|
4299
|
-
if (typeof document !== 'undefined') {
|
|
4300
|
-
document.querySelectorAll('[data-plugin-asset]').forEach((el) => {
|
|
4301
|
-
if (el.getAttribute('data-plugin-asset') === pluginId) {
|
|
4302
|
-
el.remove();
|
|
4303
|
-
}
|
|
4304
|
-
});
|
|
4305
|
-
}
|
|
4306
|
-
}
|
|
4307
|
-
|
|
4308
4572
|
/**
|
|
4309
4573
|
* 布局中的扩展点占位;插件通过 `hostApi.registerSlotComponents(pointId, ...)` 注入组件。
|
|
4310
4574
|
* 样式由宿主全局 CSS 覆盖 `.extension-point` / `.plugin-point-error`。
|
|
@@ -4357,31 +4621,15 @@ var ExtensionPoint = Vue__default.default.extend({
|
|
|
4357
4621
|
}
|
|
4358
4622
|
});
|
|
4359
4623
|
|
|
4360
|
-
/**
|
|
4361
|
-
* 注册全局 `ExtensionPoint` 并异步拉取清单、激活插件。
|
|
4362
|
-
*/
|
|
4363
|
-
/**
|
|
4364
|
-
* @param Vue Vue 构造函数
|
|
4365
|
-
* @param router vue-router 实例
|
|
4366
|
-
* @param options 传给 `resolveRuntimeOptions`;可含 `env` 以读取 `VITE_*`
|
|
4367
|
-
*/
|
|
4368
4624
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4369
4625
|
function installWebExtendPluginVue2(Vue, router, options) {
|
|
4370
|
-
const opts = options || {};
|
|
4371
|
-
const { env: injectedEnv, ...runtimeUser } = opts;
|
|
4372
|
-
if (injectedEnv && typeof injectedEnv === 'object') {
|
|
4373
|
-
setWebExtendPluginEnv(injectedEnv);
|
|
4374
|
-
}
|
|
4375
4626
|
if (Vue && ExtensionPoint) {
|
|
4376
4627
|
Vue.component('ExtensionPoint', ExtensionPoint);
|
|
4377
4628
|
}
|
|
4378
|
-
const runtime = resolveRuntimeOptions$1(
|
|
4629
|
+
const runtime = resolveRuntimeOptions$1(options || {});
|
|
4379
4630
|
return bootstrapPlugins$1(router, (id, r, kit) => createHostApi(id, r, kit || {}), runtime);
|
|
4380
4631
|
}
|
|
4381
4632
|
|
|
4382
|
-
/**
|
|
4383
|
-
* Vue CLI + 统一 axios(如 RuoYi `utils/request`)场景的 `install` 预设。
|
|
4384
|
-
*/
|
|
4385
4633
|
function resolveManifestPathUnderApiBase(manifestUrl, apiBase) {
|
|
4386
4634
|
const base = String(apiBase !== undefined
|
|
4387
4635
|
? apiBase
|
|
@@ -4391,18 +4639,13 @@ function resolveManifestPathUnderApiBase(manifestUrl, apiBase) {
|
|
|
4391
4639
|
if (typeof window === 'undefined') {
|
|
4392
4640
|
return '/api/frontend-plugins';
|
|
4393
4641
|
}
|
|
4394
|
-
const
|
|
4395
|
-
let path =
|
|
4642
|
+
const url = new URL(manifestUrl, window.location.origin);
|
|
4643
|
+
let path = url.pathname + url.search;
|
|
4396
4644
|
if (base && path.startsWith(base)) {
|
|
4397
4645
|
path = path.slice(base.length) || '/';
|
|
4398
4646
|
}
|
|
4399
4647
|
return path;
|
|
4400
4648
|
}
|
|
4401
|
-
/**
|
|
4402
|
-
* 常见 Java 清单路径:与 `VUE_APP_BASE_API` 拼接为 `${base}/frontend-plugins`。
|
|
4403
|
-
* 若后端使用 `/api/frontend-plugins` 段,请在 extra 中显式传入 `manifestListPath`。
|
|
4404
|
-
*/
|
|
4405
|
-
const defaultVueCliJavaManifestListPath = '/frontend-plugins';
|
|
4406
4649
|
function bridgePrefixesFromVueCliEnv() {
|
|
4407
4650
|
const base = (typeof process !== 'undefined' && process.env && process.env.VUE_APP_BASE_API
|
|
4408
4651
|
? String(process.env.VUE_APP_BASE_API)
|
|
@@ -4423,13 +4666,10 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
|
|
|
4423
4666
|
? String(extra.manifestBase).replace(/\/$/, '')
|
|
4424
4667
|
: '';
|
|
4425
4668
|
const stripBase = userBase || envBase;
|
|
4426
|
-
const manifestMode = resolveManifestModeFromInputs(extraManifestMode);
|
|
4427
|
-
const staticManifestUrl = resolveStaticManifestUrlFromInputs(extraStaticManifestUrl);
|
|
4428
4669
|
const fetchManifestApi = async (ctx) => {
|
|
4429
4670
|
try {
|
|
4430
|
-
const url = resolveManifestPathUnderApiBase(ctx.manifestUrl, stripBase);
|
|
4431
4671
|
const body = await request({
|
|
4432
|
-
url,
|
|
4672
|
+
url: resolveManifestPathUnderApiBase(ctx.manifestUrl, stripBase),
|
|
4433
4673
|
method: 'get'
|
|
4434
4674
|
});
|
|
4435
4675
|
const data = unwrapNestedManifestBody(body);
|
|
@@ -4442,17 +4682,18 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
|
|
|
4442
4682
|
}
|
|
4443
4683
|
return { ok: true, data };
|
|
4444
4684
|
}
|
|
4445
|
-
catch (
|
|
4446
|
-
return { ok: false, error
|
|
4685
|
+
catch (error) {
|
|
4686
|
+
return { ok: false, error, data: null };
|
|
4447
4687
|
}
|
|
4448
4688
|
};
|
|
4449
|
-
const
|
|
4689
|
+
const manifestMode = resolveManifestModeFromInputs(extraManifestMode);
|
|
4690
|
+
const staticManifestUrl = resolveStaticManifestUrlFromInputs(extraStaticManifestUrl);
|
|
4450
4691
|
const fetchManifest = typeof userFetchManifest === 'function'
|
|
4451
4692
|
? userFetchManifest
|
|
4452
4693
|
: manifestMode === 'static'
|
|
4453
|
-
?
|
|
4694
|
+
? fetchStaticManifestViaHttp
|
|
4454
4695
|
: fetchManifestApi;
|
|
4455
|
-
const
|
|
4696
|
+
const options = {
|
|
4456
4697
|
manifestBase: stripBase || undefined,
|
|
4457
4698
|
bridgeAllowedPathPrefixes: bridgePrefixesFromVueCliEnv(),
|
|
4458
4699
|
manifestMode,
|
|
@@ -4463,134 +4704,39 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
|
|
|
4463
4704
|
const listPath = typeof process !== 'undefined' &&
|
|
4464
4705
|
process.env &&
|
|
4465
4706
|
process.env[webExtendPluginEnvKeys.manifestPathAlt];
|
|
4466
|
-
if (listPath &&
|
|
4467
|
-
|
|
4707
|
+
if (listPath && options.manifestListPath === undefined && extra.manifestListPath === undefined) {
|
|
4708
|
+
options.manifestListPath = String(process.env[webExtendPluginEnvKeys.manifestPathAlt]);
|
|
4468
4709
|
}
|
|
4469
|
-
return
|
|
4710
|
+
return options;
|
|
4470
4711
|
}
|
|
4471
|
-
/**
|
|
4472
|
-
* 少样板接入:`hostContext` 自动含 `router`(及可选 `store`)、`isDev` 与常见 `manifestListPath` 默认值。
|
|
4473
|
-
* 更多运行时字段仍可通过 `extra` 覆盖。
|
|
4474
|
-
*/
|
|
4475
|
-
function createVueCliAxiosQuickInstallOptions(
|
|
4476
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4477
|
-
router, deps, extra = {}) {
|
|
4478
|
-
const { request, hostLayoutComponent, store, hostContext: depHostContext, applyPluginMenuItems, revokePluginMenuItems } = deps;
|
|
4479
|
-
const { hostContext: extraHostContext, isDev: extraIsDev, manifestListPath: extraListPath, ...restExtra } = extra;
|
|
4480
|
-
const hostContext = {
|
|
4481
|
-
router,
|
|
4482
|
-
...(store !== undefined ? { store } : {}),
|
|
4483
|
-
...(depHostContext && typeof depHostContext === 'object' ? depHostContext : {}),
|
|
4484
|
-
...(extraHostContext && typeof extraHostContext === 'object' ? extraHostContext : {})
|
|
4485
|
-
};
|
|
4486
|
-
const manifestListPath = extraListPath !== undefined && String(extraListPath).trim() !== ''
|
|
4487
|
-
? String(extraListPath)
|
|
4488
|
-
: defaultVueCliJavaManifestListPath;
|
|
4489
|
-
return createVueCliAxiosInstallOptions({ request }, {
|
|
4490
|
-
hostLayoutComponent,
|
|
4491
|
-
hostContext,
|
|
4492
|
-
applyPluginMenuItems,
|
|
4493
|
-
revokePluginMenuItems,
|
|
4494
|
-
isDev: extraIsDev !== undefined ? Boolean(extraIsDev) : resolveBundledIsDev(),
|
|
4495
|
-
manifestListPath,
|
|
4496
|
-
...restExtra
|
|
4497
|
-
});
|
|
4498
|
-
}
|
|
4499
|
-
const presetVueCliAxios = Object.freeze({
|
|
4500
|
-
id: 'vue-cli-axios',
|
|
4501
|
-
description: 'Vue CLI + axios request for API manifest; optional manifestMode=static uses fetch',
|
|
4502
|
-
createInstallOptions: createVueCliAxiosInstallOptions,
|
|
4503
|
-
createQuickInstallOptions: createVueCliAxiosQuickInstallOptions,
|
|
4504
|
-
defaultJavaManifestListPath: defaultVueCliJavaManifestListPath,
|
|
4505
|
-
manifestPathForApiBase: resolveManifestPathUnderApiBase,
|
|
4506
|
-
unwrapManifestBody: unwrapNestedManifestBody
|
|
4507
|
-
});
|
|
4508
4712
|
|
|
4509
|
-
|
|
4510
|
-
* Vue CLI + axios 场景的一键安装:合并 hostContext、默认清单路径与 IIFE 全局 Vue。
|
|
4511
|
-
*/
|
|
4512
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4513
|
-
function installVueCliAxiosWebPlugins(Vue, router, deps, extra) {
|
|
4514
|
-
const exposeGlobalVue = extra?.exposeGlobalVue !== false;
|
|
4515
|
-
if (exposeGlobalVue && typeof window !== 'undefined') {
|
|
4516
|
-
window.Vue = Vue;
|
|
4517
|
-
}
|
|
4518
|
-
const { exposeGlobalVue: _skip, ...restExtra } = extra || {};
|
|
4519
|
-
const opts = createVueCliAxiosQuickInstallOptions(router, deps, restExtra);
|
|
4520
|
-
return installWebExtendPluginVue2(Vue, router, opts);
|
|
4521
|
-
}
|
|
4522
|
-
|
|
4523
|
-
/**
|
|
4524
|
-
* 包对外稳定导出与 `WebExtendPluginVue2` 命名空间。
|
|
4525
|
-
*/
|
|
4526
|
-
const { bootstrapPlugins, defaultFetchWebPluginManifest, resolveRuntimeOptions, ensurePluginHostRoute } = pluginRuntime;
|
|
4713
|
+
const { bootstrapPlugins, defaultFetchWebPluginManifest, resolveRuntimeOptions, ensurePluginHostRoute, getActivatedPluginIds } = pluginRuntime;
|
|
4527
4714
|
const { composeManifestFetch, manifestFetchCacheMiddleware, wrapManifestFetchWithCache } = manifestComposer;
|
|
4528
|
-
const WebExtendPluginVue2 = Object.freeze({
|
|
4529
|
-
install: installWebExtendPluginVue2,
|
|
4530
|
-
runtime: Object.freeze({
|
|
4531
|
-
bootstrapPlugins: bootstrapPlugins$1,
|
|
4532
|
-
resolveRuntimeOptions: resolveRuntimeOptions$1,
|
|
4533
|
-
ensurePluginHostRoute: ensurePluginHostRoute$1,
|
|
4534
|
-
defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
|
|
4535
|
-
composeManifestFetch: composeManifestFetch$1,
|
|
4536
|
-
manifestFetchCacheMiddleware: manifestFetchCacheMiddleware$1,
|
|
4537
|
-
wrapManifestFetchWithCache: wrapManifestFetchWithCache$1
|
|
4538
|
-
}),
|
|
4539
|
-
host: Object.freeze({
|
|
4540
|
-
createHostApi,
|
|
4541
|
-
disposeWebPlugin,
|
|
4542
|
-
createRequestBridge,
|
|
4543
|
-
registries
|
|
4544
|
-
}),
|
|
4545
|
-
config: Object.freeze({
|
|
4546
|
-
defaultWebExtendPluginRuntime,
|
|
4547
|
-
setWebExtendPluginEnv,
|
|
4548
|
-
webExtendPluginEnvKeys,
|
|
4549
|
-
defaultManifestFetchCache,
|
|
4550
|
-
defaultManifestMode,
|
|
4551
|
-
routeSynthNamePrefix,
|
|
4552
|
-
peerMinimumVersions
|
|
4553
|
-
}),
|
|
4554
|
-
constants: Object.freeze({
|
|
4555
|
-
HOST_PLUGIN_API_VERSION,
|
|
4556
|
-
RUNTIME_CONSOLE_LABEL
|
|
4557
|
-
}),
|
|
4558
|
-
components: Object.freeze({
|
|
4559
|
-
ExtensionPoint
|
|
4560
|
-
}),
|
|
4561
|
-
presets: Object.freeze({
|
|
4562
|
-
vueCliAxios: presetVueCliAxios
|
|
4563
|
-
})
|
|
4564
|
-
});
|
|
4565
4715
|
|
|
4566
4716
|
exports.ExtensionPoint = ExtensionPoint;
|
|
4567
4717
|
exports.HOST_PLUGIN_API_VERSION = HOST_PLUGIN_API_VERSION;
|
|
4568
4718
|
exports.RUNTIME_CONSOLE_LABEL = RUNTIME_CONSOLE_LABEL;
|
|
4569
|
-
exports.WebExtendPluginVue2 = WebExtendPluginVue2;
|
|
4570
4719
|
exports.bootstrapPlugins = bootstrapPlugins;
|
|
4571
4720
|
exports.composeManifestFetch = composeManifestFetch;
|
|
4572
4721
|
exports.createHostApi = createHostApi;
|
|
4573
4722
|
exports.createRequestBridge = createRequestBridge;
|
|
4574
4723
|
exports.createVueCliAxiosInstallOptions = createVueCliAxiosInstallOptions;
|
|
4575
|
-
exports.createVueCliAxiosQuickInstallOptions = createVueCliAxiosQuickInstallOptions;
|
|
4576
4724
|
exports.defaultFetchWebPluginManifest = defaultFetchWebPluginManifest;
|
|
4577
4725
|
exports.defaultManifestFetchCache = defaultManifestFetchCache;
|
|
4578
4726
|
exports.defaultManifestMode = defaultManifestMode;
|
|
4579
|
-
exports.defaultVueCliJavaManifestListPath = defaultVueCliJavaManifestListPath;
|
|
4580
4727
|
exports.defaultWebExtendPluginRuntime = defaultWebExtendPluginRuntime;
|
|
4581
4728
|
exports.disposeWebPlugin = disposeWebPlugin;
|
|
4582
4729
|
exports.ensurePluginHostRoute = ensurePluginHostRoute;
|
|
4583
|
-
exports.
|
|
4730
|
+
exports.getActivatedPluginIds = getActivatedPluginIds;
|
|
4731
|
+
exports.getContributedRoutesForPlugin = getContributedRoutesForPlugin;
|
|
4732
|
+
exports.getRegisteredTopRouteNamesForPlugin = getRegisteredTopRouteNamesForPlugin;
|
|
4584
4733
|
exports.installWebExtendPluginVue2 = installWebExtendPluginVue2;
|
|
4585
4734
|
exports.manifestFetchCacheMiddleware = manifestFetchCacheMiddleware;
|
|
4586
4735
|
exports.peerMinimumVersions = peerMinimumVersions;
|
|
4587
|
-
exports.presetVueCliAxios = presetVueCliAxios;
|
|
4588
4736
|
exports.registries = registries;
|
|
4589
|
-
exports.resolveManifestPathUnderApiBase = resolveManifestPathUnderApiBase;
|
|
4590
4737
|
exports.resolveRuntimeOptions = resolveRuntimeOptions;
|
|
4591
4738
|
exports.routeSynthNamePrefix = routeSynthNamePrefix;
|
|
4592
4739
|
exports.setWebExtendPluginEnv = setWebExtendPluginEnv;
|
|
4593
|
-
exports.unwrapNestedManifestBody = unwrapNestedManifestBody;
|
|
4594
4740
|
exports.webExtendPluginEnvKeys = webExtendPluginEnvKeys;
|
|
4595
4741
|
exports.wrapManifestFetchWithCache = wrapManifestFetchWithCache;
|
|
4596
4742
|
//# sourceMappingURL=index.cjs.map
|