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