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.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
 
@@ -411,6 +386,9 @@ function resolveRuntimeOptions$1(user = {}) {
411
386
  ...(typeof user.adaptRouteDeclarations === 'function'
412
387
  ? { adaptRouteDeclarations: user.adaptRouteDeclarations }
413
388
  : {}),
389
+ ...(typeof user.onPluginRoutesContributed === 'function'
390
+ ? { onPluginRoutesContributed: user.onPluginRoutesContributed }
391
+ : {}),
414
392
  ...(user.hostContext !== undefined &&
415
393
  user.hostContext !== null &&
416
394
  typeof user.hostContext === 'object' &&
@@ -3202,7 +3180,7 @@ var semverExports = requireSemver();
3202
3180
  var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
3203
3181
 
3204
3182
  /**
3205
- * 引导时注入的 router 引用,供 disposeWebPlugin 按插件批量 removeRoute。
3183
+ * 引导时注入的 router 引用,供 disposeWebPlugin 卸载插件动态路由。
3206
3184
  */
3207
3185
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
3208
3186
  let bootstrapRouter;
@@ -3216,6 +3194,206 @@ function getPluginBootstrapRouter() {
3216
3194
  return bootstrapRouter;
3217
3195
  }
3218
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)`。
3230
+ */
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);
3255
+ }
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) {
3288
+ try {
3289
+ fn();
3290
+ }
3291
+ catch (e) {
3292
+ console.warn('[wep] teardown failed', pluginId, e);
3293
+ }
3294
+ }
3295
+ }
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
+
3219
3397
  /**
3220
3398
  * 开发模式插件 URL 映射(显式 JSON + 隐式 dev 探测)。
3221
3399
  */
@@ -3378,49 +3556,6 @@ function startPluginDevSseForMap(devMap, isDev, hostSet, ssePath) {
3378
3556
  }
3379
3557
  }
3380
3558
 
3381
- /**
3382
- * 动态加载脚本(去重与并发合并)。
3383
- */
3384
- const loadScriptMemo = new Map();
3385
- function loadScript(src) {
3386
- if (typeof document === 'undefined') {
3387
- return Promise.reject(new Error('loadScript: no document'));
3388
- }
3389
- if (loadScriptMemo.has(src)) {
3390
- return loadScriptMemo.get(src);
3391
- }
3392
- const p = new Promise((resolve, reject) => {
3393
- const scripts = document.getElementsByTagName('script');
3394
- for (let i = 0; i < scripts.length; i++) {
3395
- const el = scripts[i];
3396
- if (el.src === src) {
3397
- if (el.getAttribute('data-wep-loaded') === 'true') {
3398
- resolve();
3399
- return;
3400
- }
3401
- el.addEventListener('load', () => {
3402
- el.setAttribute('data-wep-loaded', 'true');
3403
- resolve();
3404
- }, { once: true });
3405
- el.addEventListener('error', () => reject(new Error('loadScript failed: ' + src)), { once: true });
3406
- return;
3407
- }
3408
- }
3409
- const s = document.createElement('script');
3410
- s.async = true;
3411
- s.src = src;
3412
- s.onload = () => {
3413
- s.setAttribute('data-wep-loaded', 'true');
3414
- resolve();
3415
- };
3416
- s.onerror = () => reject(new Error('loadScript failed: ' + src));
3417
- document.head.appendChild(s);
3418
- });
3419
- loadScriptMemo.set(src, p);
3420
- p.catch(() => loadScriptMemo.delete(src));
3421
- return p;
3422
- }
3423
-
3424
3559
  let _printed = false;
3425
3560
  /** 在首次引导插件时打印一行运行时标识(非大块 ASCII art)。 */
3426
3561
  function printRuntimeBannerOnce() {
@@ -3569,6 +3704,29 @@ function resolveStaticManifestUrlForFetch(url, origin) {
3569
3704
  }
3570
3705
  }
3571
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
+
3572
3730
  /**
3573
3731
  * 拉取插件清单、加载入口脚本并调用各插件 `activator`。
3574
3732
  */
@@ -3604,7 +3762,7 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
3604
3762
  catch (e) {
3605
3763
  console.warn('[wep] dev import failed, trying manifest entryUrl', p.id, e);
3606
3764
  if (entryUrl && isScriptHostAllowed(entryUrl, hostSet)) {
3607
- await loadScript(entryUrl);
3765
+ await loadScript(entryUrl, p.id);
3608
3766
  }
3609
3767
  return;
3610
3768
  }
@@ -3614,7 +3772,40 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
3614
3772
  console.warn('[wep] skip (entryUrl not allowed)', p.id, entryUrl);
3615
3773
  return;
3616
3774
  }
3617
- 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);
3618
3809
  }
3619
3810
  async function bootstrapPlugins$1(
3620
3811
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -3626,6 +3817,7 @@ createHostApiFactory, runtimeOptions) {
3626
3817
  return;
3627
3818
  }
3628
3819
  printRuntimeBannerOnce();
3820
+ clearActivatedPluginIds();
3629
3821
  const opts = resolveRuntimeOptions$1(runtimeOptions || {});
3630
3822
  setPluginBootstrapRouter(router);
3631
3823
  ensurePluginHostRoute$1(router, opts);
@@ -3704,6 +3896,9 @@ createHostApiFactory, runtimeOptions) {
3704
3896
  : {}),
3705
3897
  ...(typeof opts.adaptRouteDeclarations === 'function'
3706
3898
  ? { adaptRouteDeclarations: opts.adaptRouteDeclarations }
3899
+ : {}),
3900
+ ...(typeof opts.onPluginRoutesContributed === 'function'
3901
+ ? { onPluginRoutesContributed: opts.onPluginRoutesContributed }
3707
3902
  : {})
3708
3903
  };
3709
3904
  if (!manifestResult.ok) {
@@ -3732,22 +3927,30 @@ createHostApiFactory, runtimeOptions) {
3732
3927
  const maj = coerced ? coerced.major : 0;
3733
3928
  const range = `^${maj}.0.0`;
3734
3929
  if (!semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
3735
- console.warn('[wep] host API version mismatch', {
3930
+ console.warn('[wep] manifest host API version mismatch; skip bootstrap', {
3736
3931
  host: HOST_PLUGIN_API_VERSION,
3737
3932
  manifest: apiVer
3738
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;
3739
3941
  }
3740
3942
  }
3741
3943
  window.__PLUGIN_ACTIVATORS__ = window.__PLUGIN_ACTIVATORS__ || {};
3742
- const plugins = data.plugins || [];
3944
+ const originalPlugins = data.plugins || [];
3945
+ const plugins = sortByPriority(originalPlugins);
3743
3946
  if (plugins.length === 0) {
3744
3947
  const hint = isStatic ? 'check static JSON file and plugins[]' : 'check backend and URL';
3745
3948
  console.info('[wep] empty plugin manifest — ' + hint, manifestUrlUsed);
3746
3949
  }
3747
3950
  const summary = {
3748
- manifestCount: plugins.length,
3951
+ manifestCount: originalPlugins.length,
3749
3952
  activated: 0,
3750
- skipEngines: 0,
3953
+ skipApiVersion: 0,
3751
3954
  skipLoad: 0,
3752
3955
  skipNoActivator: 0,
3753
3956
  activateFail: 0
@@ -3755,8 +3958,10 @@ createHostApiFactory, runtimeOptions) {
3755
3958
  for (const p of plugins) {
3756
3959
  const range = p.engines && p.engines.host;
3757
3960
  if (range && !semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
3758
- console.warn('[wep] skip plugin (engines.host)', p.id, range);
3759
- summary.skipEngines++;
3961
+ console.warn('[wep] skip plugin (engines.host)', p.id, range, {
3962
+ host: HOST_PLUGIN_API_VERSION
3963
+ });
3964
+ summary.skipApiVersion++;
3760
3965
  continue;
3761
3966
  }
3762
3967
  const entryUrl = p.entryUrl;
@@ -3792,6 +3997,11 @@ createHostApiFactory, runtimeOptions) {
3792
3997
  const hostApi = createHostApiFactory(p.id, router, hostKit);
3793
3998
  try {
3794
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
+ }
3795
4005
  summary.activated++;
3796
4006
  if (typeof opts.onAfterPluginActivate === 'function') {
3797
4007
  await Promise.resolve(opts.onAfterPluginActivate({
@@ -3804,6 +4014,12 @@ createHostApiFactory, runtimeOptions) {
3804
4014
  }
3805
4015
  catch (e) {
3806
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
+ }
3807
4023
  summary.activateFail++;
3808
4024
  if (typeof opts.onPluginActivateError === 'function') {
3809
4025
  try {
@@ -3834,6 +4050,7 @@ var pluginRuntime = /*#__PURE__*/Object.freeze({
3834
4050
  bootstrapPlugins: bootstrapPlugins$1,
3835
4051
  defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
3836
4052
  ensurePluginHostRoute: ensurePluginHostRoute$1,
4053
+ getActivatedPluginIds: getActivatedPluginIds$1,
3837
4054
  resolveRuntimeOptions: resolveRuntimeOptions$1
3838
4055
  });
3839
4056
 
@@ -3992,48 +4209,40 @@ var manifestComposer = /*#__PURE__*/Object.freeze({
3992
4209
  });
3993
4210
 
3994
4211
  /**
3995
- * 宿主侧响应式注册表:扩展点槽位(供布局与 `ExtensionPoint` 消费)。
3996
- * 菜单由宿主从路由 `meta` 推导(见 `buildMenuDescriptorsFromRoutes`),框架不维护平行菜单列表。
3997
- */
3998
- const registries = Vue.observable({
3999
- slots: {},
4000
- slotRevision: 0
4001
- });
4002
-
4003
- /**
4004
- * 按插件 id 收集 `onTeardown` 回调,供 `disposeWebPlugin` 统一执行。
4212
+ * 插件访问后端的受控通道:`fetch` 仅允许落在配置的路径前缀下(默认 `/api/`),默认 `credentials: 'same-origin'`。
4005
4213
  */
4006
- const _byPlugin = new Map();
4007
- function registerPluginTeardown(pluginId, fn) {
4008
- if (typeof fn !== 'function') {
4009
- return;
4214
+ function normalizeBridgePath(input) {
4215
+ if (typeof window === 'undefined') {
4216
+ throw new Error('[wep:bridge] window is unavailable');
4010
4217
  }
4011
- let list = _byPlugin.get(pluginId);
4012
- if (!list) {
4013
- list = [];
4014
- _byPlugin.set(pluginId, list);
4218
+ if (typeof input !== 'string' || !input.startsWith('/')) {
4219
+ throw new Error('[wep:bridge] path must start with /');
4015
4220
  }
4016
- list.push(fn);
4017
- }
4018
- function runPluginTeardowns(pluginId) {
4019
- const list = _byPlugin.get(pluginId);
4020
- if (!list) {
4021
- return;
4221
+ if (input.includes('\\')) {
4222
+ throw new Error('[wep:bridge] path must not contain backslashes');
4022
4223
  }
4023
- _byPlugin.delete(pluginId);
4024
- 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 = (() => {
4025
4230
  try {
4026
- fn();
4231
+ return decodeURIComponent(normalized);
4027
4232
  }
4028
- catch (e) {
4029
- console.warn('[wep] teardown failed', pluginId, e);
4233
+ catch {
4234
+ return normalized;
4030
4235
  }
4031
- }
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');
4243
+ }
4244
+ return normalized + url.search;
4032
4245
  }
4033
-
4034
- /**
4035
- * 插件访问后端的受控通道:`fetch` 仅允许落在配置的路径前缀下(默认 `/api/`),默认 `credentials: 'same-origin'`。
4036
- */
4037
4246
  function createRequestBridge(config = {}) {
4038
4247
  const raw = Array.isArray(config.allowedPathPrefixes) && config.allowedPathPrefixes.length > 0
4039
4248
  ? config.allowedPathPrefixes
@@ -4041,14 +4250,13 @@ function createRequestBridge(config = {}) {
4041
4250
  const allowedPathPrefixes = raw.map((p) => ensureLeadingPath(p));
4042
4251
  return {
4043
4252
  async request(path, init = {}) {
4044
- if (typeof path !== 'string' || !path.startsWith('/')) {
4045
- throw new Error('[wep:bridge] path must start with /');
4046
- }
4047
- 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(/\/$/, '')}/`));
4048
4256
  if (!allowed) {
4049
- throw new Error('[wep:bridge] path not allowed: ' + path);
4257
+ throw new Error('[wep:bridge] path not allowed: ' + normalizedPath);
4050
4258
  }
4051
- return fetch(path, {
4259
+ return fetch(normalizedPath, {
4052
4260
  credentials: 'same-origin',
4053
4261
  ...init
4054
4262
  });
@@ -4057,51 +4265,83 @@ function createRequestBridge(config = {}) {
4057
4265
  }
4058
4266
 
4059
4267
  /**
4060
- * 记录各插件通过 registerRoutes 挂到 router 上的顶层 route name,便于 dispose 时 removeRoute。
4268
+ * 按插件记录已贡献路由的可序列化快照,避免宿主依赖 router 内部 matcher 形态。
4061
4269
  */
4062
- const pluginIdToRouteNames = new Map();
4063
- function recordPluginTopRouteNames(pluginId, names) {
4064
- if (!pluginId || names.length === 0) {
4065
- return;
4270
+ const contributedRoutesByPlugin = new Map();
4271
+ function sanitizeValue(value) {
4272
+ if (value == null) {
4273
+ return value;
4066
4274
  }
4067
- const cur = pluginIdToRouteNames.get(pluginId) || [];
4068
- pluginIdToRouteNames.set(pluginId, cur.concat(names));
4069
- }
4070
- /**
4071
- * 从 matcher 移除该插件登记过的顶层路由(含其子树)。
4072
- * 需 vue-router ≥3.5 的 removeRoute;否则静默跳过。
4073
- */
4074
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
4075
- function removeRegisteredRoutesForPlugin(router, pluginId) {
4076
- const names = pluginIdToRouteNames.get(pluginId);
4077
- if (!names || names.length === 0) {
4078
- return;
4275
+ if (typeof value === 'function') {
4276
+ return undefined;
4079
4277
  }
4080
- if (!router || typeof router.removeRoute !== 'function') {
4081
- console.warn('[wep] router.removeRoute 不可用,跳过动态路由卸载', pluginId);
4082
- pluginIdToRouteNames.delete(pluginId);
4083
- return;
4278
+ if (Array.isArray(value)) {
4279
+ return value
4280
+ .map((item) => sanitizeValue(item))
4281
+ .filter((item) => item !== undefined);
4084
4282
  }
4085
- for (let i = names.length - 1; i >= 0; i--) {
4086
- try {
4087
- router.removeRoute(names[i]);
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;
4088
4290
  }
4089
- catch (e) {
4090
- console.warn('[wep] removeRoute failed', names[i], e);
4291
+ const sanitized = sanitizeValue(raw);
4292
+ if (sanitized !== undefined) {
4293
+ out[key] = sanitized;
4091
4294
  }
4092
4295
  }
4093
- pluginIdToRouteNames.delete(pluginId);
4296
+ return out;
4094
4297
  }
4095
- /** 测试或宿主高级场景:查询已登记 name(勿依赖顺序语义) */
4096
- function getRegisteredTopRouteNamesForPlugin(pluginId) {
4097
- return [...(pluginIdToRouteNames.get(pluginId) || [])];
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);
4098
4316
  }
4099
4317
 
4100
4318
  /**
4101
- * 构造插件 `activator(hostApi)` 使用的宿主 API:路由、菜单、扩展点、资源与受控请求桥。
4319
+ * 构造插件 `activator(hostApi)` 使用的宿主 API:路由、扩展点、资源与受控请求桥。
4102
4320
  */
4103
4321
  let slotItemKeySeq = 0;
4104
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
+ }
4105
4345
  function analyzeRouteInputTree(nodes) {
4106
4346
  let hasDecl = false;
4107
4347
  let hasCfg = false;
@@ -4143,26 +4383,71 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
4143
4383
  const parentName = typeof hostKitOptions.pluginRoutesParentName === 'string'
4144
4384
  ? hostKitOptions.pluginRoutesParentName.trim()
4145
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
+ }
4146
4408
  function applyInternalRegister(rawRouteConfigs) {
4147
4409
  const wrapped = rawRouteConfigs.map((r) => ({
4148
- ...r,
4149
- name: r.name || `${routeSynthNamePrefix}${pluginId}_${routeSynthSeq++}`,
4150
- meta: { ...(r.meta || {}), pluginId }
4410
+ ...decorateRouteTreeWithPluginMeta(pluginId, r),
4411
+ name: r.name || `${routeSynthNamePrefix}${pluginId}_${routeSynthSeq++}`
4151
4412
  }));
4152
4413
  if (typeof router.addRoute !== 'function') {
4153
4414
  throw new Error('[wep] vue-router 3.5+ 必需:请使用 router.addRoute(不再支持 addRoutes)');
4154
4415
  }
4155
- recordPluginTopRouteNames(pluginId, wrapped.map((r) => String(r.name)));
4156
- if (parentName) {
4157
- for (const r of wrapped) {
4158
- 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
+ }
4159
4426
  }
4160
- }
4161
- else {
4162
- for (const r of wrapped) {
4163
- 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
+ }
4164
4435
  }
4165
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
+ }
4166
4451
  }
4167
4452
  function injectStylesheet(href) {
4168
4453
  const link = document.createElement('link');
@@ -4185,6 +4470,9 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
4185
4470
  const hostContext = hostKitOptions.hostContext != null && typeof hostKitOptions.hostContext === 'object'
4186
4471
  ? hostKitOptions.hostContext
4187
4472
  : Object.freeze({});
4473
+ registerPluginTeardown(pluginId, () => {
4474
+ clearContributedRoutesForPlugin(pluginId);
4475
+ });
4188
4476
  return {
4189
4477
  hostPluginApiVersion: HOST_PLUGIN_API_VERSION,
4190
4478
  /** 宿主注入的只读依赖(store、router、开放能力等),见 `resolveRuntimeOptions.hostContext` */
@@ -4265,6 +4553,7 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
4265
4553
  registerSanitizedHtmlSnippet() {
4266
4554
  throw new Error('registerSanitizedHtmlSnippet is not enabled');
4267
4555
  },
4556
+ getContributedRoutes: () => getContributedRoutesForPlugin(pluginId),
4268
4557
  getBridge: () => bridge,
4269
4558
  onTeardown(_pluginId, fn) {
4270
4559
  if (typeof fn === 'function') {
@@ -4274,112 +4563,6 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
4274
4563
  };
4275
4564
  }
4276
4565
 
4277
- /**
4278
- * 卸载单个插件:执行 teardown、按登记 name 批量 removeRoute、清理注册表与 activator、移除带 `data-plugin-asset` 的 DOM。
4279
- * 依赖 vue-router ≥3.5 的 removeRoute;引导时由 bootstrapPlugins 注入 router。
4280
- */
4281
- function disposeWebPlugin(pluginId) {
4282
- if (!pluginId || typeof pluginId !== 'string') {
4283
- return;
4284
- }
4285
- runPluginTeardowns(pluginId);
4286
- removeRegisteredRoutesForPlugin(getPluginBootstrapRouter(), pluginId);
4287
- const slots = registries.slots;
4288
- for (const pointId of Object.keys(slots)) {
4289
- const list = slots[pointId];
4290
- if (!Array.isArray(list)) {
4291
- continue;
4292
- }
4293
- const next = list.filter((x) => x.pluginId !== pluginId);
4294
- if (next.length === 0) {
4295
- Vue.delete(slots, pointId);
4296
- }
4297
- else if (next.length !== list.length) {
4298
- Vue.set(slots, pointId, next);
4299
- }
4300
- }
4301
- registries.slotRevision++;
4302
- if (typeof window !== 'undefined' && window.__PLUGIN_ACTIVATORS__) {
4303
- delete window.__PLUGIN_ACTIVATORS__[pluginId];
4304
- }
4305
- if (typeof document !== 'undefined') {
4306
- document.querySelectorAll('[data-plugin-asset]').forEach((el) => {
4307
- if (el.getAttribute('data-plugin-asset') === pluginId) {
4308
- el.remove();
4309
- }
4310
- });
4311
- }
4312
- }
4313
-
4314
- /**
4315
- * 从路由配置(含 meta)推导侧栏/目录用菜单描述,与 registerRoutes 入参同源。
4316
- *
4317
- * 约定:
4318
- * - `meta.menu === false`:该节点及其子树不参与菜单
4319
- * - 节点出现条件:`meta.title` 或 `meta.menuTitle` 非空字符串,或存在非空的子菜单列表
4320
- * - `meta.order` 数字越小越靠前;缺省为 0
4321
- * - 可选:`meta.icon`、`meta.permission`、`meta.hidden`、`meta.external`
4322
- */
4323
- function pickTitle(meta, route) {
4324
- if (typeof meta.menuTitle === 'string' && meta.menuTitle) {
4325
- return meta.menuTitle;
4326
- }
4327
- if (typeof meta.title === 'string' && meta.title) {
4328
- return meta.title;
4329
- }
4330
- if (route.name != null && String(route.name)) {
4331
- return String(route.name);
4332
- }
4333
- return String(route.path || '');
4334
- }
4335
- function sortDescriptors(items) {
4336
- items.sort((a, b) => a.order - b.order || a.path.localeCompare(b.path));
4337
- for (const x of items) {
4338
- if (x.children && x.children.length) {
4339
- sortDescriptors(x.children);
4340
- }
4341
- }
4342
- }
4343
- /**
4344
- * 从插件侧 RouteConfig 树构建菜单描述(不含 component,便于并入宿主菜单 state)。
4345
- */
4346
- function buildMenuDescriptorsFromRoutes(routes, pluginId) {
4347
- const out = [];
4348
- function walk(route) {
4349
- const meta = (route.meta || {});
4350
- if (meta.menu === false) {
4351
- return null;
4352
- }
4353
- const chIn = Array.isArray(route.children) ? route.children : [];
4354
- const childMenus = chIn.map(walk).filter(Boolean);
4355
- const hasTitle = typeof meta.title === 'string' || typeof meta.menuTitle === 'string';
4356
- if (!hasTitle && childMenus.length === 0) {
4357
- return null;
4358
- }
4359
- const item = {
4360
- path: String(route.path || ''),
4361
- name: route.name,
4362
- title: pickTitle(meta, route),
4363
- order: meta.order != null ? Number(meta.order) : 0,
4364
- ...(pluginId ? { pluginId } : {}),
4365
- ...(meta.icon !== undefined ? { icon: meta.icon } : {}),
4366
- ...(meta.permission !== undefined ? { permission: meta.permission } : {}),
4367
- ...(meta.hidden === true ? { hidden: true } : {}),
4368
- ...(meta.external === true ? { external: true } : {}),
4369
- ...(childMenus.length ? { children: childMenus } : {})
4370
- };
4371
- return item;
4372
- }
4373
- for (const r of routes) {
4374
- const m = walk(r);
4375
- if (m) {
4376
- out.push(m);
4377
- }
4378
- }
4379
- sortDescriptors(out);
4380
- return out;
4381
- }
4382
-
4383
4566
  /**
4384
4567
  * 布局中的扩展点占位;插件通过 `hostApi.registerSlotComponents(pointId, ...)` 注入组件。
4385
4568
  * 样式由宿主全局 CSS 覆盖 `.extension-point` / `.plugin-point-error`。
@@ -4432,31 +4615,15 @@ var ExtensionPoint = Vue.extend({
4432
4615
  }
4433
4616
  });
4434
4617
 
4435
- /**
4436
- * 注册全局 `ExtensionPoint` 并异步拉取清单、激活插件。
4437
- */
4438
- /**
4439
- * @param Vue Vue 构造函数
4440
- * @param router vue-router 实例
4441
- * @param options 传给 `resolveRuntimeOptions`;可含 `env` 以读取 `VITE_*`
4442
- */
4443
4618
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4444
4619
  function installWebExtendPluginVue2(Vue, router, options) {
4445
- const opts = options || {};
4446
- const { env: injectedEnv, ...runtimeUser } = opts;
4447
- if (injectedEnv && typeof injectedEnv === 'object') {
4448
- setWebExtendPluginEnv(injectedEnv);
4449
- }
4450
4620
  if (Vue && ExtensionPoint) {
4451
4621
  Vue.component('ExtensionPoint', ExtensionPoint);
4452
4622
  }
4453
- const runtime = resolveRuntimeOptions$1(runtimeUser);
4623
+ const runtime = resolveRuntimeOptions$1(options || {});
4454
4624
  return bootstrapPlugins$1(router, (id, r, kit) => createHostApi(id, r, kit || {}), runtime);
4455
4625
  }
4456
4626
 
4457
- /**
4458
- * Vue CLI + 统一 axios(如 RuoYi `utils/request`)场景的 `install` 预设。
4459
- */
4460
4627
  function resolveManifestPathUnderApiBase(manifestUrl, apiBase) {
4461
4628
  const base = String(apiBase !== undefined
4462
4629
  ? apiBase
@@ -4466,18 +4633,13 @@ function resolveManifestPathUnderApiBase(manifestUrl, apiBase) {
4466
4633
  if (typeof window === 'undefined') {
4467
4634
  return '/api/frontend-plugins';
4468
4635
  }
4469
- const u = new URL(manifestUrl, window.location.origin);
4470
- let path = u.pathname + u.search;
4636
+ const url = new URL(manifestUrl, window.location.origin);
4637
+ let path = url.pathname + url.search;
4471
4638
  if (base && path.startsWith(base)) {
4472
4639
  path = path.slice(base.length) || '/';
4473
4640
  }
4474
4641
  return path;
4475
4642
  }
4476
- /**
4477
- * 常见 Java 清单路径:与 `VUE_APP_BASE_API` 拼接为 `${base}/frontend-plugins`。
4478
- * 若后端使用 `/api/frontend-plugins` 段,请在 extra 中显式传入 `manifestListPath`。
4479
- */
4480
- const defaultVueCliJavaManifestListPath = '/frontend-plugins';
4481
4643
  function bridgePrefixesFromVueCliEnv() {
4482
4644
  const base = (typeof process !== 'undefined' && process.env && process.env.VUE_APP_BASE_API
4483
4645
  ? String(process.env.VUE_APP_BASE_API)
@@ -4498,13 +4660,10 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
4498
4660
  ? String(extra.manifestBase).replace(/\/$/, '')
4499
4661
  : '';
4500
4662
  const stripBase = userBase || envBase;
4501
- const manifestMode = resolveManifestModeFromInputs(extraManifestMode);
4502
- const staticManifestUrl = resolveStaticManifestUrlFromInputs(extraStaticManifestUrl);
4503
4663
  const fetchManifestApi = async (ctx) => {
4504
4664
  try {
4505
- const url = resolveManifestPathUnderApiBase(ctx.manifestUrl, stripBase);
4506
4665
  const body = await request({
4507
- url,
4666
+ url: resolveManifestPathUnderApiBase(ctx.manifestUrl, stripBase),
4508
4667
  method: 'get'
4509
4668
  });
4510
4669
  const data = unwrapNestedManifestBody(body);
@@ -4517,17 +4676,18 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
4517
4676
  }
4518
4677
  return { ok: true, data };
4519
4678
  }
4520
- catch (e) {
4521
- return { ok: false, error: e, data: null };
4679
+ catch (error) {
4680
+ return { ok: false, error, data: null };
4522
4681
  }
4523
4682
  };
4524
- const fetchManifestStatic = (ctx) => fetchStaticManifestViaHttp(ctx);
4683
+ const manifestMode = resolveManifestModeFromInputs(extraManifestMode);
4684
+ const staticManifestUrl = resolveStaticManifestUrlFromInputs(extraStaticManifestUrl);
4525
4685
  const fetchManifest = typeof userFetchManifest === 'function'
4526
4686
  ? userFetchManifest
4527
4687
  : manifestMode === 'static'
4528
- ? fetchManifestStatic
4688
+ ? fetchStaticManifestViaHttp
4529
4689
  : fetchManifestApi;
4530
- const opts = {
4690
+ const options = {
4531
4691
  manifestBase: stripBase || undefined,
4532
4692
  bridgeAllowedPathPrefixes: bridgePrefixesFromVueCliEnv(),
4533
4693
  manifestMode,
@@ -4538,106 +4698,14 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
4538
4698
  const listPath = typeof process !== 'undefined' &&
4539
4699
  process.env &&
4540
4700
  process.env[webExtendPluginEnvKeys.manifestPathAlt];
4541
- if (listPath && opts.manifestListPath === undefined && extra.manifestListPath === undefined) {
4542
- opts.manifestListPath = String(process.env[webExtendPluginEnvKeys.manifestPathAlt]);
4701
+ if (listPath && options.manifestListPath === undefined && extra.manifestListPath === undefined) {
4702
+ options.manifestListPath = String(process.env[webExtendPluginEnvKeys.manifestPathAlt]);
4543
4703
  }
4544
- return opts;
4545
- }
4546
- /**
4547
- * 少样板接入:`hostContext` 自动含 `router`(及可选 `store`)、`isDev` 与常见 `manifestListPath` 默认值。
4548
- * 更多运行时字段仍可通过 `extra` 覆盖。
4549
- */
4550
- function createVueCliAxiosQuickInstallOptions(
4551
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
4552
- router, deps, extra = {}) {
4553
- const { request, hostLayoutComponent, store, hostContext: depHostContext } = deps;
4554
- const { hostContext: extraHostContext, isDev: extraIsDev, manifestListPath: extraListPath, ...restExtra } = extra;
4555
- const hostContext = {
4556
- router,
4557
- ...(store !== undefined ? { store } : {}),
4558
- ...(depHostContext && typeof depHostContext === 'object' ? depHostContext : {}),
4559
- ...(extraHostContext && typeof extraHostContext === 'object' ? extraHostContext : {})
4560
- };
4561
- const manifestListPath = extraListPath !== undefined && String(extraListPath).trim() !== ''
4562
- ? String(extraListPath)
4563
- : defaultVueCliJavaManifestListPath;
4564
- return createVueCliAxiosInstallOptions({ request }, {
4565
- hostLayoutComponent,
4566
- hostContext,
4567
- isDev: extraIsDev !== undefined ? Boolean(extraIsDev) : resolveBundledIsDev(),
4568
- manifestListPath,
4569
- ...restExtra
4570
- });
4571
- }
4572
- const presetVueCliAxios = Object.freeze({
4573
- id: 'vue-cli-axios',
4574
- description: 'Vue CLI + axios request for API manifest; optional manifestMode=static uses fetch',
4575
- createInstallOptions: createVueCliAxiosInstallOptions,
4576
- createQuickInstallOptions: createVueCliAxiosQuickInstallOptions,
4577
- defaultJavaManifestListPath: defaultVueCliJavaManifestListPath,
4578
- manifestPathForApiBase: resolveManifestPathUnderApiBase,
4579
- unwrapManifestBody: unwrapNestedManifestBody
4580
- });
4581
-
4582
- /**
4583
- * Vue CLI + axios 场景的一键安装:合并 hostContext、默认清单路径与 IIFE 全局 Vue。
4584
- */
4585
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
4586
- function installVueCliAxiosWebPlugins(Vue, router, deps, extra) {
4587
- /** 避免输出 `?.`:Vue CLI 4 / Webpack 4 默认不转译 node_modules */
4588
- const exposeGlobalVue = extra == null || extra.exposeGlobalVue !== false;
4589
- if (exposeGlobalVue && typeof window !== 'undefined') {
4590
- window.Vue = Vue;
4591
- }
4592
- const { exposeGlobalVue: _skip, ...restExtra } = extra || {};
4593
- const opts = createVueCliAxiosQuickInstallOptions(router, deps, restExtra);
4594
- return installWebExtendPluginVue2(Vue, router, opts);
4704
+ return options;
4595
4705
  }
4596
4706
 
4597
- /**
4598
- * 包对外稳定导出与 `WebExtendPluginVue2` 命名空间。
4599
- */
4600
- const { bootstrapPlugins, defaultFetchWebPluginManifest, resolveRuntimeOptions, ensurePluginHostRoute } = pluginRuntime;
4707
+ const { bootstrapPlugins, defaultFetchWebPluginManifest, resolveRuntimeOptions, ensurePluginHostRoute, getActivatedPluginIds } = pluginRuntime;
4601
4708
  const { composeManifestFetch, manifestFetchCacheMiddleware, wrapManifestFetchWithCache } = manifestComposer;
4602
- const WebExtendPluginVue2 = Object.freeze({
4603
- install: installWebExtendPluginVue2,
4604
- runtime: Object.freeze({
4605
- bootstrapPlugins: bootstrapPlugins$1,
4606
- resolveRuntimeOptions: resolveRuntimeOptions$1,
4607
- ensurePluginHostRoute: ensurePluginHostRoute$1,
4608
- defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
4609
- composeManifestFetch: composeManifestFetch$1,
4610
- manifestFetchCacheMiddleware: manifestFetchCacheMiddleware$1,
4611
- wrapManifestFetchWithCache: wrapManifestFetchWithCache$1
4612
- }),
4613
- host: Object.freeze({
4614
- createHostApi,
4615
- disposeWebPlugin,
4616
- createRequestBridge,
4617
- registries,
4618
- buildMenuDescriptorsFromRoutes,
4619
- getRegisteredTopRouteNamesForPlugin
4620
- }),
4621
- config: Object.freeze({
4622
- defaultWebExtendPluginRuntime,
4623
- setWebExtendPluginEnv,
4624
- webExtendPluginEnvKeys,
4625
- defaultManifestFetchCache,
4626
- defaultManifestMode,
4627
- routeSynthNamePrefix,
4628
- peerMinimumVersions
4629
- }),
4630
- constants: Object.freeze({
4631
- HOST_PLUGIN_API_VERSION,
4632
- RUNTIME_CONSOLE_LABEL
4633
- }),
4634
- components: Object.freeze({
4635
- ExtensionPoint
4636
- }),
4637
- presets: Object.freeze({
4638
- vueCliAxios: presetVueCliAxios
4639
- })
4640
- });
4641
4709
 
4642
- export { ExtensionPoint, HOST_PLUGIN_API_VERSION, RUNTIME_CONSOLE_LABEL, WebExtendPluginVue2, bootstrapPlugins, buildMenuDescriptorsFromRoutes, composeManifestFetch, createHostApi, createRequestBridge, createVueCliAxiosInstallOptions, createVueCliAxiosQuickInstallOptions, defaultFetchWebPluginManifest, defaultManifestFetchCache, defaultManifestMode, defaultVueCliJavaManifestListPath, defaultWebExtendPluginRuntime, disposeWebPlugin, ensurePluginHostRoute, getRegisteredTopRouteNamesForPlugin, 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 };
4643
4711
  //# sourceMappingURL=index.mjs.map