web-extend-plugin-vue2 0.3.0 → 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 +73 -200
- package/dist/index.cjs +468 -406
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +467 -399
- package/dist/index.mjs.map +1 -1
- package/index.d.ts +18 -180
- 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
|
|
|
@@ -417,6 +392,9 @@ function resolveRuntimeOptions$1(user = {}) {
|
|
|
417
392
|
...(typeof user.adaptRouteDeclarations === 'function'
|
|
418
393
|
? { adaptRouteDeclarations: user.adaptRouteDeclarations }
|
|
419
394
|
: {}),
|
|
395
|
+
...(typeof user.onPluginRoutesContributed === 'function'
|
|
396
|
+
? { onPluginRoutesContributed: user.onPluginRoutesContributed }
|
|
397
|
+
: {}),
|
|
420
398
|
...(user.hostContext !== undefined &&
|
|
421
399
|
user.hostContext !== null &&
|
|
422
400
|
typeof user.hostContext === 'object' &&
|
|
@@ -3208,7 +3186,7 @@ var semverExports = requireSemver();
|
|
|
3208
3186
|
var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
|
|
3209
3187
|
|
|
3210
3188
|
/**
|
|
3211
|
-
* 引导时注入的 router 引用,供 disposeWebPlugin
|
|
3189
|
+
* 引导时注入的 router 引用,供 disposeWebPlugin 卸载插件动态路由。
|
|
3212
3190
|
*/
|
|
3213
3191
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3214
3192
|
let bootstrapRouter;
|
|
@@ -3222,6 +3200,206 @@ function getPluginBootstrapRouter() {
|
|
|
3222
3200
|
return bootstrapRouter;
|
|
3223
3201
|
}
|
|
3224
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)`。
|
|
3236
|
+
*/
|
|
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);
|
|
3261
|
+
}
|
|
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) {
|
|
3294
|
+
try {
|
|
3295
|
+
fn();
|
|
3296
|
+
}
|
|
3297
|
+
catch (e) {
|
|
3298
|
+
console.warn('[wep] teardown failed', pluginId, e);
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
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
|
+
|
|
3225
3403
|
/**
|
|
3226
3404
|
* 开发模式插件 URL 映射(显式 JSON + 隐式 dev 探测)。
|
|
3227
3405
|
*/
|
|
@@ -3384,49 +3562,6 @@ function startPluginDevSseForMap(devMap, isDev, hostSet, ssePath) {
|
|
|
3384
3562
|
}
|
|
3385
3563
|
}
|
|
3386
3564
|
|
|
3387
|
-
/**
|
|
3388
|
-
* 动态加载脚本(去重与并发合并)。
|
|
3389
|
-
*/
|
|
3390
|
-
const loadScriptMemo = new Map();
|
|
3391
|
-
function loadScript(src) {
|
|
3392
|
-
if (typeof document === 'undefined') {
|
|
3393
|
-
return Promise.reject(new Error('loadScript: no document'));
|
|
3394
|
-
}
|
|
3395
|
-
if (loadScriptMemo.has(src)) {
|
|
3396
|
-
return loadScriptMemo.get(src);
|
|
3397
|
-
}
|
|
3398
|
-
const p = new Promise((resolve, reject) => {
|
|
3399
|
-
const scripts = document.getElementsByTagName('script');
|
|
3400
|
-
for (let i = 0; i < scripts.length; i++) {
|
|
3401
|
-
const el = scripts[i];
|
|
3402
|
-
if (el.src === src) {
|
|
3403
|
-
if (el.getAttribute('data-wep-loaded') === 'true') {
|
|
3404
|
-
resolve();
|
|
3405
|
-
return;
|
|
3406
|
-
}
|
|
3407
|
-
el.addEventListener('load', () => {
|
|
3408
|
-
el.setAttribute('data-wep-loaded', 'true');
|
|
3409
|
-
resolve();
|
|
3410
|
-
}, { once: true });
|
|
3411
|
-
el.addEventListener('error', () => reject(new Error('loadScript failed: ' + src)), { once: true });
|
|
3412
|
-
return;
|
|
3413
|
-
}
|
|
3414
|
-
}
|
|
3415
|
-
const s = document.createElement('script');
|
|
3416
|
-
s.async = true;
|
|
3417
|
-
s.src = src;
|
|
3418
|
-
s.onload = () => {
|
|
3419
|
-
s.setAttribute('data-wep-loaded', 'true');
|
|
3420
|
-
resolve();
|
|
3421
|
-
};
|
|
3422
|
-
s.onerror = () => reject(new Error('loadScript failed: ' + src));
|
|
3423
|
-
document.head.appendChild(s);
|
|
3424
|
-
});
|
|
3425
|
-
loadScriptMemo.set(src, p);
|
|
3426
|
-
p.catch(() => loadScriptMemo.delete(src));
|
|
3427
|
-
return p;
|
|
3428
|
-
}
|
|
3429
|
-
|
|
3430
3565
|
let _printed = false;
|
|
3431
3566
|
/** 在首次引导插件时打印一行运行时标识(非大块 ASCII art)。 */
|
|
3432
3567
|
function printRuntimeBannerOnce() {
|
|
@@ -3575,6 +3710,29 @@ function resolveStaticManifestUrlForFetch(url, origin) {
|
|
|
3575
3710
|
}
|
|
3576
3711
|
}
|
|
3577
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
|
+
|
|
3578
3736
|
/**
|
|
3579
3737
|
* 拉取插件清单、加载入口脚本并调用各插件 `activator`。
|
|
3580
3738
|
*/
|
|
@@ -3610,7 +3768,7 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
|
|
|
3610
3768
|
catch (e) {
|
|
3611
3769
|
console.warn('[wep] dev import failed, trying manifest entryUrl', p.id, e);
|
|
3612
3770
|
if (entryUrl && isScriptHostAllowed(entryUrl, hostSet)) {
|
|
3613
|
-
await loadScript(entryUrl);
|
|
3771
|
+
await loadScript(entryUrl, p.id);
|
|
3614
3772
|
}
|
|
3615
3773
|
return;
|
|
3616
3774
|
}
|
|
@@ -3620,7 +3778,40 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
|
|
|
3620
3778
|
console.warn('[wep] skip (entryUrl not allowed)', p.id, entryUrl);
|
|
3621
3779
|
return;
|
|
3622
3780
|
}
|
|
3623
|
-
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);
|
|
3624
3815
|
}
|
|
3625
3816
|
async function bootstrapPlugins$1(
|
|
3626
3817
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -3632,6 +3823,7 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3632
3823
|
return;
|
|
3633
3824
|
}
|
|
3634
3825
|
printRuntimeBannerOnce();
|
|
3826
|
+
clearActivatedPluginIds();
|
|
3635
3827
|
const opts = resolveRuntimeOptions$1(runtimeOptions || {});
|
|
3636
3828
|
setPluginBootstrapRouter(router);
|
|
3637
3829
|
ensurePluginHostRoute$1(router, opts);
|
|
@@ -3710,6 +3902,9 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3710
3902
|
: {}),
|
|
3711
3903
|
...(typeof opts.adaptRouteDeclarations === 'function'
|
|
3712
3904
|
? { adaptRouteDeclarations: opts.adaptRouteDeclarations }
|
|
3905
|
+
: {}),
|
|
3906
|
+
...(typeof opts.onPluginRoutesContributed === 'function'
|
|
3907
|
+
? { onPluginRoutesContributed: opts.onPluginRoutesContributed }
|
|
3713
3908
|
: {})
|
|
3714
3909
|
};
|
|
3715
3910
|
if (!manifestResult.ok) {
|
|
@@ -3738,22 +3933,30 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3738
3933
|
const maj = coerced ? coerced.major : 0;
|
|
3739
3934
|
const range = `^${maj}.0.0`;
|
|
3740
3935
|
if (!semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
|
|
3741
|
-
console.warn('[wep] host API version mismatch', {
|
|
3936
|
+
console.warn('[wep] manifest host API version mismatch; skip bootstrap', {
|
|
3742
3937
|
host: HOST_PLUGIN_API_VERSION,
|
|
3743
3938
|
manifest: apiVer
|
|
3744
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;
|
|
3745
3947
|
}
|
|
3746
3948
|
}
|
|
3747
3949
|
window.__PLUGIN_ACTIVATORS__ = window.__PLUGIN_ACTIVATORS__ || {};
|
|
3748
|
-
const
|
|
3950
|
+
const originalPlugins = data.plugins || [];
|
|
3951
|
+
const plugins = sortByPriority(originalPlugins);
|
|
3749
3952
|
if (plugins.length === 0) {
|
|
3750
3953
|
const hint = isStatic ? 'check static JSON file and plugins[]' : 'check backend and URL';
|
|
3751
3954
|
console.info('[wep] empty plugin manifest — ' + hint, manifestUrlUsed);
|
|
3752
3955
|
}
|
|
3753
3956
|
const summary = {
|
|
3754
|
-
manifestCount:
|
|
3957
|
+
manifestCount: originalPlugins.length,
|
|
3755
3958
|
activated: 0,
|
|
3756
|
-
|
|
3959
|
+
skipApiVersion: 0,
|
|
3757
3960
|
skipLoad: 0,
|
|
3758
3961
|
skipNoActivator: 0,
|
|
3759
3962
|
activateFail: 0
|
|
@@ -3761,8 +3964,10 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3761
3964
|
for (const p of plugins) {
|
|
3762
3965
|
const range = p.engines && p.engines.host;
|
|
3763
3966
|
if (range && !semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
|
|
3764
|
-
console.warn('[wep] skip plugin (engines.host)', p.id, range
|
|
3765
|
-
|
|
3967
|
+
console.warn('[wep] skip plugin (engines.host)', p.id, range, {
|
|
3968
|
+
host: HOST_PLUGIN_API_VERSION
|
|
3969
|
+
});
|
|
3970
|
+
summary.skipApiVersion++;
|
|
3766
3971
|
continue;
|
|
3767
3972
|
}
|
|
3768
3973
|
const entryUrl = p.entryUrl;
|
|
@@ -3798,6 +4003,11 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3798
4003
|
const hostApi = createHostApiFactory(p.id, router, hostKit);
|
|
3799
4004
|
try {
|
|
3800
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
|
+
}
|
|
3801
4011
|
summary.activated++;
|
|
3802
4012
|
if (typeof opts.onAfterPluginActivate === 'function') {
|
|
3803
4013
|
await Promise.resolve(opts.onAfterPluginActivate({
|
|
@@ -3810,6 +4020,12 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3810
4020
|
}
|
|
3811
4021
|
catch (e) {
|
|
3812
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
|
+
}
|
|
3813
4029
|
summary.activateFail++;
|
|
3814
4030
|
if (typeof opts.onPluginActivateError === 'function') {
|
|
3815
4031
|
try {
|
|
@@ -3840,6 +4056,7 @@ var pluginRuntime = /*#__PURE__*/Object.freeze({
|
|
|
3840
4056
|
bootstrapPlugins: bootstrapPlugins$1,
|
|
3841
4057
|
defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
|
|
3842
4058
|
ensurePluginHostRoute: ensurePluginHostRoute$1,
|
|
4059
|
+
getActivatedPluginIds: getActivatedPluginIds$1,
|
|
3843
4060
|
resolveRuntimeOptions: resolveRuntimeOptions$1
|
|
3844
4061
|
});
|
|
3845
4062
|
|
|
@@ -3998,48 +4215,40 @@ var manifestComposer = /*#__PURE__*/Object.freeze({
|
|
|
3998
4215
|
});
|
|
3999
4216
|
|
|
4000
4217
|
/**
|
|
4001
|
-
*
|
|
4002
|
-
* 菜单由宿主从路由 `meta` 推导(见 `buildMenuDescriptorsFromRoutes`),框架不维护平行菜单列表。
|
|
4003
|
-
*/
|
|
4004
|
-
const registries = Vue__default.default.observable({
|
|
4005
|
-
slots: {},
|
|
4006
|
-
slotRevision: 0
|
|
4007
|
-
});
|
|
4008
|
-
|
|
4009
|
-
/**
|
|
4010
|
-
* 按插件 id 收集 `onTeardown` 回调,供 `disposeWebPlugin` 统一执行。
|
|
4218
|
+
* 插件访问后端的受控通道:`fetch` 仅允许落在配置的路径前缀下(默认 `/api/`),默认 `credentials: 'same-origin'`。
|
|
4011
4219
|
*/
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
return;
|
|
4220
|
+
function normalizeBridgePath(input) {
|
|
4221
|
+
if (typeof window === 'undefined') {
|
|
4222
|
+
throw new Error('[wep:bridge] window is unavailable');
|
|
4016
4223
|
}
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
list = [];
|
|
4020
|
-
_byPlugin.set(pluginId, list);
|
|
4224
|
+
if (typeof input !== 'string' || !input.startsWith('/')) {
|
|
4225
|
+
throw new Error('[wep:bridge] path must start with /');
|
|
4021
4226
|
}
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
function runPluginTeardowns(pluginId) {
|
|
4025
|
-
const list = _byPlugin.get(pluginId);
|
|
4026
|
-
if (!list) {
|
|
4027
|
-
return;
|
|
4227
|
+
if (input.includes('\\')) {
|
|
4228
|
+
throw new Error('[wep:bridge] path must not contain backslashes');
|
|
4028
4229
|
}
|
|
4029
|
-
|
|
4030
|
-
|
|
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 = (() => {
|
|
4031
4236
|
try {
|
|
4032
|
-
|
|
4237
|
+
return decodeURIComponent(normalized);
|
|
4033
4238
|
}
|
|
4034
|
-
catch
|
|
4035
|
-
|
|
4239
|
+
catch {
|
|
4240
|
+
return normalized;
|
|
4036
4241
|
}
|
|
4037
|
-
}
|
|
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');
|
|
4249
|
+
}
|
|
4250
|
+
return normalized + url.search;
|
|
4038
4251
|
}
|
|
4039
|
-
|
|
4040
|
-
/**
|
|
4041
|
-
* 插件访问后端的受控通道:`fetch` 仅允许落在配置的路径前缀下(默认 `/api/`),默认 `credentials: 'same-origin'`。
|
|
4042
|
-
*/
|
|
4043
4252
|
function createRequestBridge(config = {}) {
|
|
4044
4253
|
const raw = Array.isArray(config.allowedPathPrefixes) && config.allowedPathPrefixes.length > 0
|
|
4045
4254
|
? config.allowedPathPrefixes
|
|
@@ -4047,14 +4256,13 @@ function createRequestBridge(config = {}) {
|
|
|
4047
4256
|
const allowedPathPrefixes = raw.map((p) => ensureLeadingPath(p));
|
|
4048
4257
|
return {
|
|
4049
4258
|
async request(path, init = {}) {
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
}
|
|
4053
|
-
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(/\/$/, '')}/`));
|
|
4054
4262
|
if (!allowed) {
|
|
4055
|
-
throw new Error('[wep:bridge] path not allowed: ' +
|
|
4263
|
+
throw new Error('[wep:bridge] path not allowed: ' + normalizedPath);
|
|
4056
4264
|
}
|
|
4057
|
-
return fetch(
|
|
4265
|
+
return fetch(normalizedPath, {
|
|
4058
4266
|
credentials: 'same-origin',
|
|
4059
4267
|
...init
|
|
4060
4268
|
});
|
|
@@ -4063,51 +4271,83 @@ function createRequestBridge(config = {}) {
|
|
|
4063
4271
|
}
|
|
4064
4272
|
|
|
4065
4273
|
/**
|
|
4066
|
-
*
|
|
4274
|
+
* 按插件记录已贡献路由的可序列化快照,避免宿主依赖 router 内部 matcher 形态。
|
|
4067
4275
|
*/
|
|
4068
|
-
const
|
|
4069
|
-
function
|
|
4070
|
-
if (
|
|
4071
|
-
return;
|
|
4276
|
+
const contributedRoutesByPlugin = new Map();
|
|
4277
|
+
function sanitizeValue(value) {
|
|
4278
|
+
if (value == null) {
|
|
4279
|
+
return value;
|
|
4072
4280
|
}
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
}
|
|
4076
|
-
/**
|
|
4077
|
-
* 从 matcher 移除该插件登记过的顶层路由(含其子树)。
|
|
4078
|
-
* 需 vue-router ≥3.5 的 removeRoute;否则静默跳过。
|
|
4079
|
-
*/
|
|
4080
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4081
|
-
function removeRegisteredRoutesForPlugin(router, pluginId) {
|
|
4082
|
-
const names = pluginIdToRouteNames.get(pluginId);
|
|
4083
|
-
if (!names || names.length === 0) {
|
|
4084
|
-
return;
|
|
4281
|
+
if (typeof value === 'function') {
|
|
4282
|
+
return undefined;
|
|
4085
4283
|
}
|
|
4086
|
-
if (
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4284
|
+
if (Array.isArray(value)) {
|
|
4285
|
+
return value
|
|
4286
|
+
.map((item) => sanitizeValue(item))
|
|
4287
|
+
.filter((item) => item !== undefined);
|
|
4090
4288
|
}
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
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;
|
|
4094
4296
|
}
|
|
4095
|
-
|
|
4096
|
-
|
|
4297
|
+
const sanitized = sanitizeValue(raw);
|
|
4298
|
+
if (sanitized !== undefined) {
|
|
4299
|
+
out[key] = sanitized;
|
|
4097
4300
|
}
|
|
4098
4301
|
}
|
|
4099
|
-
|
|
4302
|
+
return out;
|
|
4100
4303
|
}
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
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);
|
|
4104
4322
|
}
|
|
4105
4323
|
|
|
4106
4324
|
/**
|
|
4107
|
-
* 构造插件 `activator(hostApi)` 使用的宿主 API
|
|
4325
|
+
* 构造插件 `activator(hostApi)` 使用的宿主 API:路由、扩展点、资源与受控请求桥。
|
|
4108
4326
|
*/
|
|
4109
4327
|
let slotItemKeySeq = 0;
|
|
4110
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
|
+
}
|
|
4111
4351
|
function analyzeRouteInputTree(nodes) {
|
|
4112
4352
|
let hasDecl = false;
|
|
4113
4353
|
let hasCfg = false;
|
|
@@ -4149,26 +4389,71 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4149
4389
|
const parentName = typeof hostKitOptions.pluginRoutesParentName === 'string'
|
|
4150
4390
|
? hostKitOptions.pluginRoutesParentName.trim()
|
|
4151
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
|
+
}
|
|
4152
4414
|
function applyInternalRegister(rawRouteConfigs) {
|
|
4153
4415
|
const wrapped = rawRouteConfigs.map((r) => ({
|
|
4154
|
-
...r,
|
|
4155
|
-
name: r.name || `${routeSynthNamePrefix}${pluginId}_${routeSynthSeq++}
|
|
4156
|
-
meta: { ...(r.meta || {}), pluginId }
|
|
4416
|
+
...decorateRouteTreeWithPluginMeta(pluginId, r),
|
|
4417
|
+
name: r.name || `${routeSynthNamePrefix}${pluginId}_${routeSynthSeq++}`
|
|
4157
4418
|
}));
|
|
4158
4419
|
if (typeof router.addRoute !== 'function') {
|
|
4159
4420
|
throw new Error('[wep] vue-router 3.5+ 必需:请使用 router.addRoute(不再支持 addRoutes)');
|
|
4160
4421
|
}
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
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
|
+
}
|
|
4165
4432
|
}
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
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
|
+
}
|
|
4170
4441
|
}
|
|
4171
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
|
+
}
|
|
4172
4457
|
}
|
|
4173
4458
|
function injectStylesheet(href) {
|
|
4174
4459
|
const link = document.createElement('link');
|
|
@@ -4191,6 +4476,9 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4191
4476
|
const hostContext = hostKitOptions.hostContext != null && typeof hostKitOptions.hostContext === 'object'
|
|
4192
4477
|
? hostKitOptions.hostContext
|
|
4193
4478
|
: Object.freeze({});
|
|
4479
|
+
registerPluginTeardown(pluginId, () => {
|
|
4480
|
+
clearContributedRoutesForPlugin(pluginId);
|
|
4481
|
+
});
|
|
4194
4482
|
return {
|
|
4195
4483
|
hostPluginApiVersion: HOST_PLUGIN_API_VERSION,
|
|
4196
4484
|
/** 宿主注入的只读依赖(store、router、开放能力等),见 `resolveRuntimeOptions.hostContext` */
|
|
@@ -4271,6 +4559,7 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4271
4559
|
registerSanitizedHtmlSnippet() {
|
|
4272
4560
|
throw new Error('registerSanitizedHtmlSnippet is not enabled');
|
|
4273
4561
|
},
|
|
4562
|
+
getContributedRoutes: () => getContributedRoutesForPlugin(pluginId),
|
|
4274
4563
|
getBridge: () => bridge,
|
|
4275
4564
|
onTeardown(_pluginId, fn) {
|
|
4276
4565
|
if (typeof fn === 'function') {
|
|
@@ -4280,112 +4569,6 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4280
4569
|
};
|
|
4281
4570
|
}
|
|
4282
4571
|
|
|
4283
|
-
/**
|
|
4284
|
-
* 卸载单个插件:执行 teardown、按登记 name 批量 removeRoute、清理注册表与 activator、移除带 `data-plugin-asset` 的 DOM。
|
|
4285
|
-
* 依赖 vue-router ≥3.5 的 removeRoute;引导时由 bootstrapPlugins 注入 router。
|
|
4286
|
-
*/
|
|
4287
|
-
function disposeWebPlugin(pluginId) {
|
|
4288
|
-
if (!pluginId || typeof pluginId !== 'string') {
|
|
4289
|
-
return;
|
|
4290
|
-
}
|
|
4291
|
-
runPluginTeardowns(pluginId);
|
|
4292
|
-
removeRegisteredRoutesForPlugin(getPluginBootstrapRouter(), pluginId);
|
|
4293
|
-
const slots = registries.slots;
|
|
4294
|
-
for (const pointId of Object.keys(slots)) {
|
|
4295
|
-
const list = slots[pointId];
|
|
4296
|
-
if (!Array.isArray(list)) {
|
|
4297
|
-
continue;
|
|
4298
|
-
}
|
|
4299
|
-
const next = list.filter((x) => x.pluginId !== pluginId);
|
|
4300
|
-
if (next.length === 0) {
|
|
4301
|
-
Vue__default.default.delete(slots, pointId);
|
|
4302
|
-
}
|
|
4303
|
-
else if (next.length !== list.length) {
|
|
4304
|
-
Vue__default.default.set(slots, pointId, next);
|
|
4305
|
-
}
|
|
4306
|
-
}
|
|
4307
|
-
registries.slotRevision++;
|
|
4308
|
-
if (typeof window !== 'undefined' && window.__PLUGIN_ACTIVATORS__) {
|
|
4309
|
-
delete window.__PLUGIN_ACTIVATORS__[pluginId];
|
|
4310
|
-
}
|
|
4311
|
-
if (typeof document !== 'undefined') {
|
|
4312
|
-
document.querySelectorAll('[data-plugin-asset]').forEach((el) => {
|
|
4313
|
-
if (el.getAttribute('data-plugin-asset') === pluginId) {
|
|
4314
|
-
el.remove();
|
|
4315
|
-
}
|
|
4316
|
-
});
|
|
4317
|
-
}
|
|
4318
|
-
}
|
|
4319
|
-
|
|
4320
|
-
/**
|
|
4321
|
-
* 从路由配置(含 meta)推导侧栏/目录用菜单描述,与 registerRoutes 入参同源。
|
|
4322
|
-
*
|
|
4323
|
-
* 约定:
|
|
4324
|
-
* - `meta.menu === false`:该节点及其子树不参与菜单
|
|
4325
|
-
* - 节点出现条件:`meta.title` 或 `meta.menuTitle` 非空字符串,或存在非空的子菜单列表
|
|
4326
|
-
* - `meta.order` 数字越小越靠前;缺省为 0
|
|
4327
|
-
* - 可选:`meta.icon`、`meta.permission`、`meta.hidden`、`meta.external`
|
|
4328
|
-
*/
|
|
4329
|
-
function pickTitle(meta, route) {
|
|
4330
|
-
if (typeof meta.menuTitle === 'string' && meta.menuTitle) {
|
|
4331
|
-
return meta.menuTitle;
|
|
4332
|
-
}
|
|
4333
|
-
if (typeof meta.title === 'string' && meta.title) {
|
|
4334
|
-
return meta.title;
|
|
4335
|
-
}
|
|
4336
|
-
if (route.name != null && String(route.name)) {
|
|
4337
|
-
return String(route.name);
|
|
4338
|
-
}
|
|
4339
|
-
return String(route.path || '');
|
|
4340
|
-
}
|
|
4341
|
-
function sortDescriptors(items) {
|
|
4342
|
-
items.sort((a, b) => a.order - b.order || a.path.localeCompare(b.path));
|
|
4343
|
-
for (const x of items) {
|
|
4344
|
-
if (x.children && x.children.length) {
|
|
4345
|
-
sortDescriptors(x.children);
|
|
4346
|
-
}
|
|
4347
|
-
}
|
|
4348
|
-
}
|
|
4349
|
-
/**
|
|
4350
|
-
* 从插件侧 RouteConfig 树构建菜单描述(不含 component,便于并入宿主菜单 state)。
|
|
4351
|
-
*/
|
|
4352
|
-
function buildMenuDescriptorsFromRoutes(routes, pluginId) {
|
|
4353
|
-
const out = [];
|
|
4354
|
-
function walk(route) {
|
|
4355
|
-
const meta = (route.meta || {});
|
|
4356
|
-
if (meta.menu === false) {
|
|
4357
|
-
return null;
|
|
4358
|
-
}
|
|
4359
|
-
const chIn = Array.isArray(route.children) ? route.children : [];
|
|
4360
|
-
const childMenus = chIn.map(walk).filter(Boolean);
|
|
4361
|
-
const hasTitle = typeof meta.title === 'string' || typeof meta.menuTitle === 'string';
|
|
4362
|
-
if (!hasTitle && childMenus.length === 0) {
|
|
4363
|
-
return null;
|
|
4364
|
-
}
|
|
4365
|
-
const item = {
|
|
4366
|
-
path: String(route.path || ''),
|
|
4367
|
-
name: route.name,
|
|
4368
|
-
title: pickTitle(meta, route),
|
|
4369
|
-
order: meta.order != null ? Number(meta.order) : 0,
|
|
4370
|
-
...(pluginId ? { pluginId } : {}),
|
|
4371
|
-
...(meta.icon !== undefined ? { icon: meta.icon } : {}),
|
|
4372
|
-
...(meta.permission !== undefined ? { permission: meta.permission } : {}),
|
|
4373
|
-
...(meta.hidden === true ? { hidden: true } : {}),
|
|
4374
|
-
...(meta.external === true ? { external: true } : {}),
|
|
4375
|
-
...(childMenus.length ? { children: childMenus } : {})
|
|
4376
|
-
};
|
|
4377
|
-
return item;
|
|
4378
|
-
}
|
|
4379
|
-
for (const r of routes) {
|
|
4380
|
-
const m = walk(r);
|
|
4381
|
-
if (m) {
|
|
4382
|
-
out.push(m);
|
|
4383
|
-
}
|
|
4384
|
-
}
|
|
4385
|
-
sortDescriptors(out);
|
|
4386
|
-
return out;
|
|
4387
|
-
}
|
|
4388
|
-
|
|
4389
4572
|
/**
|
|
4390
4573
|
* 布局中的扩展点占位;插件通过 `hostApi.registerSlotComponents(pointId, ...)` 注入组件。
|
|
4391
4574
|
* 样式由宿主全局 CSS 覆盖 `.extension-point` / `.plugin-point-error`。
|
|
@@ -4438,31 +4621,15 @@ var ExtensionPoint = Vue__default.default.extend({
|
|
|
4438
4621
|
}
|
|
4439
4622
|
});
|
|
4440
4623
|
|
|
4441
|
-
/**
|
|
4442
|
-
* 注册全局 `ExtensionPoint` 并异步拉取清单、激活插件。
|
|
4443
|
-
*/
|
|
4444
|
-
/**
|
|
4445
|
-
* @param Vue Vue 构造函数
|
|
4446
|
-
* @param router vue-router 实例
|
|
4447
|
-
* @param options 传给 `resolveRuntimeOptions`;可含 `env` 以读取 `VITE_*`
|
|
4448
|
-
*/
|
|
4449
4624
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4450
4625
|
function installWebExtendPluginVue2(Vue, router, options) {
|
|
4451
|
-
const opts = options || {};
|
|
4452
|
-
const { env: injectedEnv, ...runtimeUser } = opts;
|
|
4453
|
-
if (injectedEnv && typeof injectedEnv === 'object') {
|
|
4454
|
-
setWebExtendPluginEnv(injectedEnv);
|
|
4455
|
-
}
|
|
4456
4626
|
if (Vue && ExtensionPoint) {
|
|
4457
4627
|
Vue.component('ExtensionPoint', ExtensionPoint);
|
|
4458
4628
|
}
|
|
4459
|
-
const runtime = resolveRuntimeOptions$1(
|
|
4629
|
+
const runtime = resolveRuntimeOptions$1(options || {});
|
|
4460
4630
|
return bootstrapPlugins$1(router, (id, r, kit) => createHostApi(id, r, kit || {}), runtime);
|
|
4461
4631
|
}
|
|
4462
4632
|
|
|
4463
|
-
/**
|
|
4464
|
-
* Vue CLI + 统一 axios(如 RuoYi `utils/request`)场景的 `install` 预设。
|
|
4465
|
-
*/
|
|
4466
4633
|
function resolveManifestPathUnderApiBase(manifestUrl, apiBase) {
|
|
4467
4634
|
const base = String(apiBase !== undefined
|
|
4468
4635
|
? apiBase
|
|
@@ -4472,18 +4639,13 @@ function resolveManifestPathUnderApiBase(manifestUrl, apiBase) {
|
|
|
4472
4639
|
if (typeof window === 'undefined') {
|
|
4473
4640
|
return '/api/frontend-plugins';
|
|
4474
4641
|
}
|
|
4475
|
-
const
|
|
4476
|
-
let path =
|
|
4642
|
+
const url = new URL(manifestUrl, window.location.origin);
|
|
4643
|
+
let path = url.pathname + url.search;
|
|
4477
4644
|
if (base && path.startsWith(base)) {
|
|
4478
4645
|
path = path.slice(base.length) || '/';
|
|
4479
4646
|
}
|
|
4480
4647
|
return path;
|
|
4481
4648
|
}
|
|
4482
|
-
/**
|
|
4483
|
-
* 常见 Java 清单路径:与 `VUE_APP_BASE_API` 拼接为 `${base}/frontend-plugins`。
|
|
4484
|
-
* 若后端使用 `/api/frontend-plugins` 段,请在 extra 中显式传入 `manifestListPath`。
|
|
4485
|
-
*/
|
|
4486
|
-
const defaultVueCliJavaManifestListPath = '/frontend-plugins';
|
|
4487
4649
|
function bridgePrefixesFromVueCliEnv() {
|
|
4488
4650
|
const base = (typeof process !== 'undefined' && process.env && process.env.VUE_APP_BASE_API
|
|
4489
4651
|
? String(process.env.VUE_APP_BASE_API)
|
|
@@ -4504,13 +4666,10 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
|
|
|
4504
4666
|
? String(extra.manifestBase).replace(/\/$/, '')
|
|
4505
4667
|
: '';
|
|
4506
4668
|
const stripBase = userBase || envBase;
|
|
4507
|
-
const manifestMode = resolveManifestModeFromInputs(extraManifestMode);
|
|
4508
|
-
const staticManifestUrl = resolveStaticManifestUrlFromInputs(extraStaticManifestUrl);
|
|
4509
4669
|
const fetchManifestApi = async (ctx) => {
|
|
4510
4670
|
try {
|
|
4511
|
-
const url = resolveManifestPathUnderApiBase(ctx.manifestUrl, stripBase);
|
|
4512
4671
|
const body = await request({
|
|
4513
|
-
url,
|
|
4672
|
+
url: resolveManifestPathUnderApiBase(ctx.manifestUrl, stripBase),
|
|
4514
4673
|
method: 'get'
|
|
4515
4674
|
});
|
|
4516
4675
|
const data = unwrapNestedManifestBody(body);
|
|
@@ -4523,17 +4682,18 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
|
|
|
4523
4682
|
}
|
|
4524
4683
|
return { ok: true, data };
|
|
4525
4684
|
}
|
|
4526
|
-
catch (
|
|
4527
|
-
return { ok: false, error
|
|
4685
|
+
catch (error) {
|
|
4686
|
+
return { ok: false, error, data: null };
|
|
4528
4687
|
}
|
|
4529
4688
|
};
|
|
4530
|
-
const
|
|
4689
|
+
const manifestMode = resolveManifestModeFromInputs(extraManifestMode);
|
|
4690
|
+
const staticManifestUrl = resolveStaticManifestUrlFromInputs(extraStaticManifestUrl);
|
|
4531
4691
|
const fetchManifest = typeof userFetchManifest === 'function'
|
|
4532
4692
|
? userFetchManifest
|
|
4533
4693
|
: manifestMode === 'static'
|
|
4534
|
-
?
|
|
4694
|
+
? fetchStaticManifestViaHttp
|
|
4535
4695
|
: fetchManifestApi;
|
|
4536
|
-
const
|
|
4696
|
+
const options = {
|
|
4537
4697
|
manifestBase: stripBase || undefined,
|
|
4538
4698
|
bridgeAllowedPathPrefixes: bridgePrefixesFromVueCliEnv(),
|
|
4539
4699
|
manifestMode,
|
|
@@ -4544,137 +4704,39 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
|
|
|
4544
4704
|
const listPath = typeof process !== 'undefined' &&
|
|
4545
4705
|
process.env &&
|
|
4546
4706
|
process.env[webExtendPluginEnvKeys.manifestPathAlt];
|
|
4547
|
-
if (listPath &&
|
|
4548
|
-
|
|
4707
|
+
if (listPath && options.manifestListPath === undefined && extra.manifestListPath === undefined) {
|
|
4708
|
+
options.manifestListPath = String(process.env[webExtendPluginEnvKeys.manifestPathAlt]);
|
|
4549
4709
|
}
|
|
4550
|
-
return
|
|
4551
|
-
}
|
|
4552
|
-
/**
|
|
4553
|
-
* 少样板接入:`hostContext` 自动含 `router`(及可选 `store`)、`isDev` 与常见 `manifestListPath` 默认值。
|
|
4554
|
-
* 更多运行时字段仍可通过 `extra` 覆盖。
|
|
4555
|
-
*/
|
|
4556
|
-
function createVueCliAxiosQuickInstallOptions(
|
|
4557
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4558
|
-
router, deps, extra = {}) {
|
|
4559
|
-
const { request, hostLayoutComponent, store, hostContext: depHostContext } = deps;
|
|
4560
|
-
const { hostContext: extraHostContext, isDev: extraIsDev, manifestListPath: extraListPath, ...restExtra } = extra;
|
|
4561
|
-
const hostContext = {
|
|
4562
|
-
router,
|
|
4563
|
-
...(store !== undefined ? { store } : {}),
|
|
4564
|
-
...(depHostContext && typeof depHostContext === 'object' ? depHostContext : {}),
|
|
4565
|
-
...(extraHostContext && typeof extraHostContext === 'object' ? extraHostContext : {})
|
|
4566
|
-
};
|
|
4567
|
-
const manifestListPath = extraListPath !== undefined && String(extraListPath).trim() !== ''
|
|
4568
|
-
? String(extraListPath)
|
|
4569
|
-
: defaultVueCliJavaManifestListPath;
|
|
4570
|
-
return createVueCliAxiosInstallOptions({ request }, {
|
|
4571
|
-
hostLayoutComponent,
|
|
4572
|
-
hostContext,
|
|
4573
|
-
isDev: extraIsDev !== undefined ? Boolean(extraIsDev) : resolveBundledIsDev(),
|
|
4574
|
-
manifestListPath,
|
|
4575
|
-
...restExtra
|
|
4576
|
-
});
|
|
4577
|
-
}
|
|
4578
|
-
const presetVueCliAxios = Object.freeze({
|
|
4579
|
-
id: 'vue-cli-axios',
|
|
4580
|
-
description: 'Vue CLI + axios request for API manifest; optional manifestMode=static uses fetch',
|
|
4581
|
-
createInstallOptions: createVueCliAxiosInstallOptions,
|
|
4582
|
-
createQuickInstallOptions: createVueCliAxiosQuickInstallOptions,
|
|
4583
|
-
defaultJavaManifestListPath: defaultVueCliJavaManifestListPath,
|
|
4584
|
-
manifestPathForApiBase: resolveManifestPathUnderApiBase,
|
|
4585
|
-
unwrapManifestBody: unwrapNestedManifestBody
|
|
4586
|
-
});
|
|
4587
|
-
|
|
4588
|
-
/**
|
|
4589
|
-
* Vue CLI + axios 场景的一键安装:合并 hostContext、默认清单路径与 IIFE 全局 Vue。
|
|
4590
|
-
*/
|
|
4591
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4592
|
-
function installVueCliAxiosWebPlugins(Vue, router, deps, extra) {
|
|
4593
|
-
/** 避免输出 `?.`:Vue CLI 4 / Webpack 4 默认不转译 node_modules */
|
|
4594
|
-
const exposeGlobalVue = extra == null || extra.exposeGlobalVue !== false;
|
|
4595
|
-
if (exposeGlobalVue && typeof window !== 'undefined') {
|
|
4596
|
-
window.Vue = Vue;
|
|
4597
|
-
}
|
|
4598
|
-
const { exposeGlobalVue: _skip, ...restExtra } = extra || {};
|
|
4599
|
-
const opts = createVueCliAxiosQuickInstallOptions(router, deps, restExtra);
|
|
4600
|
-
return installWebExtendPluginVue2(Vue, router, opts);
|
|
4710
|
+
return options;
|
|
4601
4711
|
}
|
|
4602
4712
|
|
|
4603
|
-
|
|
4604
|
-
* 包对外稳定导出与 `WebExtendPluginVue2` 命名空间。
|
|
4605
|
-
*/
|
|
4606
|
-
const { bootstrapPlugins, defaultFetchWebPluginManifest, resolveRuntimeOptions, ensurePluginHostRoute } = pluginRuntime;
|
|
4713
|
+
const { bootstrapPlugins, defaultFetchWebPluginManifest, resolveRuntimeOptions, ensurePluginHostRoute, getActivatedPluginIds } = pluginRuntime;
|
|
4607
4714
|
const { composeManifestFetch, manifestFetchCacheMiddleware, wrapManifestFetchWithCache } = manifestComposer;
|
|
4608
|
-
const WebExtendPluginVue2 = Object.freeze({
|
|
4609
|
-
install: installWebExtendPluginVue2,
|
|
4610
|
-
runtime: Object.freeze({
|
|
4611
|
-
bootstrapPlugins: bootstrapPlugins$1,
|
|
4612
|
-
resolveRuntimeOptions: resolveRuntimeOptions$1,
|
|
4613
|
-
ensurePluginHostRoute: ensurePluginHostRoute$1,
|
|
4614
|
-
defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
|
|
4615
|
-
composeManifestFetch: composeManifestFetch$1,
|
|
4616
|
-
manifestFetchCacheMiddleware: manifestFetchCacheMiddleware$1,
|
|
4617
|
-
wrapManifestFetchWithCache: wrapManifestFetchWithCache$1
|
|
4618
|
-
}),
|
|
4619
|
-
host: Object.freeze({
|
|
4620
|
-
createHostApi,
|
|
4621
|
-
disposeWebPlugin,
|
|
4622
|
-
createRequestBridge,
|
|
4623
|
-
registries,
|
|
4624
|
-
buildMenuDescriptorsFromRoutes,
|
|
4625
|
-
getRegisteredTopRouteNamesForPlugin
|
|
4626
|
-
}),
|
|
4627
|
-
config: Object.freeze({
|
|
4628
|
-
defaultWebExtendPluginRuntime,
|
|
4629
|
-
setWebExtendPluginEnv,
|
|
4630
|
-
webExtendPluginEnvKeys,
|
|
4631
|
-
defaultManifestFetchCache,
|
|
4632
|
-
defaultManifestMode,
|
|
4633
|
-
routeSynthNamePrefix,
|
|
4634
|
-
peerMinimumVersions
|
|
4635
|
-
}),
|
|
4636
|
-
constants: Object.freeze({
|
|
4637
|
-
HOST_PLUGIN_API_VERSION,
|
|
4638
|
-
RUNTIME_CONSOLE_LABEL
|
|
4639
|
-
}),
|
|
4640
|
-
components: Object.freeze({
|
|
4641
|
-
ExtensionPoint
|
|
4642
|
-
}),
|
|
4643
|
-
presets: Object.freeze({
|
|
4644
|
-
vueCliAxios: presetVueCliAxios
|
|
4645
|
-
})
|
|
4646
|
-
});
|
|
4647
4715
|
|
|
4648
4716
|
exports.ExtensionPoint = ExtensionPoint;
|
|
4649
4717
|
exports.HOST_PLUGIN_API_VERSION = HOST_PLUGIN_API_VERSION;
|
|
4650
4718
|
exports.RUNTIME_CONSOLE_LABEL = RUNTIME_CONSOLE_LABEL;
|
|
4651
|
-
exports.WebExtendPluginVue2 = WebExtendPluginVue2;
|
|
4652
4719
|
exports.bootstrapPlugins = bootstrapPlugins;
|
|
4653
|
-
exports.buildMenuDescriptorsFromRoutes = buildMenuDescriptorsFromRoutes;
|
|
4654
4720
|
exports.composeManifestFetch = composeManifestFetch;
|
|
4655
4721
|
exports.createHostApi = createHostApi;
|
|
4656
4722
|
exports.createRequestBridge = createRequestBridge;
|
|
4657
4723
|
exports.createVueCliAxiosInstallOptions = createVueCliAxiosInstallOptions;
|
|
4658
|
-
exports.createVueCliAxiosQuickInstallOptions = createVueCliAxiosQuickInstallOptions;
|
|
4659
4724
|
exports.defaultFetchWebPluginManifest = defaultFetchWebPluginManifest;
|
|
4660
4725
|
exports.defaultManifestFetchCache = defaultManifestFetchCache;
|
|
4661
4726
|
exports.defaultManifestMode = defaultManifestMode;
|
|
4662
|
-
exports.defaultVueCliJavaManifestListPath = defaultVueCliJavaManifestListPath;
|
|
4663
4727
|
exports.defaultWebExtendPluginRuntime = defaultWebExtendPluginRuntime;
|
|
4664
4728
|
exports.disposeWebPlugin = disposeWebPlugin;
|
|
4665
4729
|
exports.ensurePluginHostRoute = ensurePluginHostRoute;
|
|
4730
|
+
exports.getActivatedPluginIds = getActivatedPluginIds;
|
|
4731
|
+
exports.getContributedRoutesForPlugin = getContributedRoutesForPlugin;
|
|
4666
4732
|
exports.getRegisteredTopRouteNamesForPlugin = getRegisteredTopRouteNamesForPlugin;
|
|
4667
|
-
exports.installVueCliAxiosWebPlugins = installVueCliAxiosWebPlugins;
|
|
4668
4733
|
exports.installWebExtendPluginVue2 = installWebExtendPluginVue2;
|
|
4669
4734
|
exports.manifestFetchCacheMiddleware = manifestFetchCacheMiddleware;
|
|
4670
4735
|
exports.peerMinimumVersions = peerMinimumVersions;
|
|
4671
|
-
exports.presetVueCliAxios = presetVueCliAxios;
|
|
4672
4736
|
exports.registries = registries;
|
|
4673
|
-
exports.resolveManifestPathUnderApiBase = resolveManifestPathUnderApiBase;
|
|
4674
4737
|
exports.resolveRuntimeOptions = resolveRuntimeOptions;
|
|
4675
4738
|
exports.routeSynthNamePrefix = routeSynthNamePrefix;
|
|
4676
4739
|
exports.setWebExtendPluginEnv = setWebExtendPluginEnv;
|
|
4677
|
-
exports.unwrapNestedManifestBody = unwrapNestedManifestBody;
|
|
4678
4740
|
exports.webExtendPluginEnvKeys = webExtendPluginEnvKeys;
|
|
4679
4741
|
exports.wrapManifestFetchWithCache = wrapManifestFetchWithCache;
|
|
4680
4742
|
//# sourceMappingURL=index.cjs.map
|