web-extend-plugin-vue2 0.3.0 → 0.3.1

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