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/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
- * 对外配置单一入口:`resolveRuntimeOptions` / 文档 / 其它模块的默认值与环境键名均由此引用。
11
- * 宿主通过 `resolveRuntimeOptions(partial)` 覆盖的对象形状见 `defaultWebExtendPluginRuntime`。
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.14',
16
- vueRouter: '3.5.4'
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
- if (user.pluginRoutesParentName !== undefined) {
349
- return String(user.pluginRoutesParentName).trim();
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 !== false;
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.applyPluginMenuItems === 'function'
427
- ? { applyPluginMenuItems: user.applyPluginMenuItems }
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
- * `disposeWebPlugin` 使用的菜单撤销钩子:在 `bootstrapPlugins` 中用 `resolveRuntimeOptions` 结果注册。
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
- let revokePluginMenuItems;
3226
- function setRevokePluginMenuItems(fn) {
3227
- revokePluginMenuItems = fn;
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
- function revokePluginMenusIfConfigured(pluginId) {
3230
- if (typeof revokePluginMenuItems === 'function') {
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
- revokePluginMenuItems(pluginId);
3295
+ fn();
3233
3296
  }
3234
3297
  catch (e) {
3235
- console.warn('[wep] revokePluginMenuItems failed', pluginId, e);
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
- * 开箱:在未手工配置时,注册 `/plugin` + 宿主 Layout 的命名父路由,供 `router.addRoute(parentName, child)` 挂载插件页。
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 === false) {
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
- setRevokePluginMenuItems(typeof opts.revokePluginMenuItems === 'function' ? opts.revokePluginMenuItems : undefined);
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.applyPluginMenuItems === 'function'
3729
- ? { applyPluginMenuItems: opts.applyPluginMenuItems }
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 plugins = data.plugins || [];
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: plugins.length,
3957
+ manifestCount: originalPlugins.length,
3775
3958
  activated: 0,
3776
- skipEngines: 0,
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
- summary.skipEngines++;
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
- * 宿主侧响应式注册表:扩展点槽位(供布局与 `ExtensionPoint` 消费)。
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
- const _byPlugin = new Map();
4033
- function registerPluginTeardown(pluginId, fn) {
4034
- if (typeof fn !== 'function') {
4035
- return;
4220
+ function normalizeBridgePath(input) {
4221
+ if (typeof window === 'undefined') {
4222
+ throw new Error('[wep:bridge] window is unavailable');
4036
4223
  }
4037
- let list = _byPlugin.get(pluginId);
4038
- if (!list) {
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
- list.push(fn);
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
- _byPlugin.delete(pluginId);
4050
- for (const fn of list) {
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
- fn();
4237
+ return decodeURIComponent(normalized);
4053
4238
  }
4054
- catch (e) {
4055
- console.warn('[wep] teardown failed', pluginId, e);
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
- if (typeof path !== 'string' || !path.startsWith('/')) {
4071
- throw new Error('[wep:bridge] path must start with /');
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: ' + path);
4263
+ throw new Error('[wep:bridge] path not allowed: ' + normalizedPath);
4076
4264
  }
4077
- return fetch(path, {
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
- * 构造插件 `activator(hostApi)` 使用的宿主 API:路由、菜单、扩展点、资源与受控请求桥。
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
- if (parentName) {
4141
- for (const r of wrapped) {
4142
- router.addRoute(parentName, r);
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
- else {
4146
- for (const r of wrapped) {
4147
- router.addRoute(r);
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(runtimeUser);
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 u = new URL(manifestUrl, window.location.origin);
4395
- let path = u.pathname + u.search;
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 (e) {
4446
- return { ok: false, error: e, data: null };
4685
+ catch (error) {
4686
+ return { ok: false, error, data: null };
4447
4687
  }
4448
4688
  };
4449
- const fetchManifestStatic = (ctx) => fetchStaticManifestViaHttp(ctx);
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
- ? fetchManifestStatic
4694
+ ? fetchStaticManifestViaHttp
4454
4695
  : fetchManifestApi;
4455
- const opts = {
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 && opts.manifestListPath === undefined && extra.manifestListPath === undefined) {
4467
- opts.manifestListPath = String(process.env[webExtendPluginEnvKeys.manifestPathAlt]);
4707
+ if (listPath && options.manifestListPath === undefined && extra.manifestListPath === undefined) {
4708
+ options.manifestListPath = String(process.env[webExtendPluginEnvKeys.manifestPathAlt]);
4468
4709
  }
4469
- return opts;
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.installVueCliAxiosWebPlugins = installVueCliAxiosWebPlugins;
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