web-extend-plugin-vue2 0.2.2 → 0.2.3

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,107 +1,62 @@
1
1
  import Vue from 'vue';
2
2
 
3
3
  /**
4
- * 宿主可覆盖的默认运行时配置(路径、白名单、超时等)。
5
- * 使用方式:`resolveRuntimeOptions({ ...defaultWebExtendPluginRuntime, manifestListPath: '/api/my-plugins' })`
6
- * 或只传需要改动的字段:`resolveRuntimeOptions({ manifestListPath: '/api/my-plugins' })`。
7
- *
8
- * @module default-runtime-config
4
+ * `resolveRuntimeOptions` 的默认值来源;宿主可只覆盖部分字段。
9
5
  */
10
-
11
- /**
12
- * @typedef {typeof defaultWebExtendPluginRuntime} WebExtendPluginDefaultRuntime
13
- */
14
-
15
6
  const defaultWebExtendPluginRuntime = {
16
- /** 清单 HTTP 服务前缀(与后端 context-path 对齐),不含尾部 `/` */
17
7
  manifestBase: '/fp-api',
18
-
19
- /**
20
- * 拉取插件清单的 **路径段**(以 `/` 开头),拼在 `manifestBase` 之后。
21
- * 完整 URL:`{manifestBase}{manifestListPath}` → 默认 `/fp-api/api/frontend-plugins`
22
- */
23
8
  manifestListPath: '/api/frontend-plugins',
24
-
25
- /** 清单 `fetch` 的 `credentials`,需 Cookie 会话时用 `include` */
26
9
  manifestFetchCredentials: 'include',
27
-
28
- /** 插件 dev 服务存活探测路径(拼在 `webPluginDevOrigin` 后) */
29
10
  devPingPath: '/__web_plugin_dev_ping',
30
-
31
- /** 插件 dev 热更新 SSE 路径(拼在插件 dev 的 origin 后) */
32
11
  devReloadSsePath: '/__web_plugin_reload_stream',
33
-
34
- /** 隐式 dev 映射里,每个 id 对应的入口路径(相对插件 dev origin) */
35
12
  webPluginDevEntryPath: '/src/plugin-entry.js',
36
-
37
- /** `fetch(devPingUrl)` 超时毫秒数 */
38
13
  devPingTimeoutMs: 500,
39
-
40
- /**
41
- * **隐式 dev 映射**(可选):开发模式下若 `webPluginDevOrigin` 上 ping 成功,运行时会为若干插件 id 自动生成
42
- * `{ id → origin + webPluginDevEntryPath }`,从而用 dev 服务入口替代清单里的 dist,便于热更新联调。
43
- *
44
- * 插件 id 来源优先级:`webPluginDevIds`(或 `VITE_WEB_PLUGIN_DEV_IDS`)→ 本字段。**默认 `[]`**,
45
- * 避免把仓库示例 id 写进通用宿主;联调时在 `.env` 里设 `VITE_WEB_PLUGIN_DEV_IDS=com.xxx,com.yyy`,
46
- * 或 `resolveRuntimeOptions({ defaultImplicitDevPluginIds: ['com.xxx'] })` 即可。
47
- */
48
14
  defaultImplicitDevPluginIds: [],
49
-
50
- /**
51
- * 允许通过 `<script>` / 动态 `import()` 加载的插件脚本所在主机名(小写),防误配公网 URL。
52
- */
53
15
  allowedScriptHosts: ['localhost', '127.0.0.1', '::1'],
54
-
55
- /**
56
- * `hostApi.getBridge().request(path)` 允许的 path 前缀;须以 `/` 开头。
57
- * 需与后端实际 API 前缀一致。
58
- */
59
16
  bridgeAllowedPathPrefixes: ['/api/']
60
17
  };
61
18
 
62
19
  /**
63
- * 不依赖 `import.meta`,兼容 Webpack 4/5、Vue CLI、Vite、Rspack 等宿主。
64
- * - Webpack / Vue CLI:依赖构建时注入的 `process.env`(如 `VUE_APP_*`、`DefinePlugin` 注入的 `VITE_*`)。
65
- * - Vite:在入口调用 `setWebExtendPluginEnv(import.meta.env)`,或 `installWebExtendPluginVue2(..., { env: import.meta.env })`。
66
- * - 也可在入口前设置 `globalThis.__WEP_ENV__ = import.meta.env`(与 setWebExtendPluginEnv 二选一即可)。
67
- *
68
- * @module bundled-env
20
+ * 与具体打包器解耦的运行时环境读取:优先显式注入,其次 `globalThis.__WEP_ENV__`,再读 `process.env`。
21
+ * Vite 宿主建议在入口调用 `setWebExtendPluginEnv(import.meta.env)`。
69
22
  */
70
23
 
71
24
  /** @type {Record<string, unknown> | null} */
72
- let _explicitEnv = null;
25
+ let _injected = null;
73
26
 
74
27
  /**
75
- * 显式注入与 `import.meta.env` 同形态的对象(推荐 Vite 宿主在入口调用一次)。
28
+ * 注入与 `import.meta.env` 同形态的对象(键如 `VITE_*`、`DEV`)。
76
29
  * @param {Record<string, unknown> | null | undefined} env
77
30
  */
78
31
  function setWebExtendPluginEnv(env) {
79
- _explicitEnv = env && typeof env === 'object' ? env : null;
32
+ _injected = env && typeof env === 'object' ? env : null;
80
33
  }
81
34
 
82
35
  /**
83
36
  * @returns {Record<string, unknown> | null}
84
37
  */
85
- function getInjectedEnvObject() {
86
- if (_explicitEnv) {
87
- return _explicitEnv
38
+ function getEnvObject() {
39
+ if (_injected) {
40
+ return _injected
88
41
  }
89
42
  try {
90
43
  const g = typeof globalThis !== 'undefined' ? globalThis : undefined;
91
44
  if (g && g.__WEP_ENV__ && typeof g.__WEP_ENV__ === 'object') {
92
45
  return g.__WEP_ENV__
93
46
  }
94
- } catch (_) {}
47
+ } catch (_) {
48
+ /* ignore */
49
+ }
95
50
  return null
96
51
  }
97
52
 
98
53
  /**
99
- * 从注入环境读取字符串配置键(`VITE_*` / `PLUGIN_*` 等)。
54
+ * 读取注入环境中的字符串配置(`VITE_*` / `PLUGIN_*` 等价键由调用方传入)。
100
55
  * @param {string} key
101
56
  * @returns {string|undefined}
102
57
  */
103
58
  function readInjectedEnvKey(key) {
104
- const o = getInjectedEnvObject();
59
+ const o = getEnvObject();
105
60
  if (!o || !(key in o)) {
106
61
  return undefined
107
62
  }
@@ -112,17 +67,14 @@ function readInjectedEnvKey(key) {
112
67
  return String(v)
113
68
  }
114
69
 
115
- /**
116
- * @returns {boolean}
117
- */
70
+ /** @returns {boolean} */
118
71
  function readInjectedEnvDev() {
119
- const o = getInjectedEnvObject();
72
+ const o = getEnvObject();
120
73
  return !!(o && o.DEV === true)
121
74
  }
122
75
 
123
76
  /**
124
- * 从注入环境与 `process.env` 解析 VITE_/PLUGIN_ 键。
125
- * @module runtime/env-resolve
77
+ * 从注入环境与 `process.env` 解析 `VITE_*` / `PLUGIN_*` 键。
126
78
  */
127
79
 
128
80
  /**
@@ -137,7 +89,9 @@ function readProcessEnv(key) {
137
89
  return String(v)
138
90
  }
139
91
  }
140
- } catch (_) {}
92
+ } catch (_) {
93
+ /* ignore */
94
+ }
141
95
  return undefined
142
96
  }
143
97
 
@@ -168,20 +122,22 @@ function resolveBundledEnv(key, fallback = '') {
168
122
  return first === undefined || first === null ? fallback : first
169
123
  }
170
124
 
171
- /**
172
- * @returns {boolean}
173
- */
125
+ /** @returns {boolean} */
174
126
  function resolveBundledIsDev() {
175
127
  try {
176
128
  if (readInjectedEnvDev()) {
177
129
  return true
178
130
  }
179
- } catch (_) {}
131
+ } catch (_) {
132
+ /* ignore */
133
+ }
180
134
  try {
181
135
  if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'development') {
182
136
  return true
183
137
  }
184
- } catch (_) {}
138
+ } catch (_) {
139
+ /* ignore */
140
+ }
185
141
  return false
186
142
  }
187
143
 
@@ -386,39 +342,24 @@ function resolveRuntimeOptions$1(user = {}) {
386
342
  allowedScriptHosts,
387
343
  bridgeAllowedPathPrefixes,
388
344
  bootstrapSummary: user.bootstrapSummary,
389
- ...(typeof user.fetchManifest === 'function' ? { fetchManifest: user.fetchManifest } : {})
345
+ pluginRoutesParentName:
346
+ user.pluginRoutesParentName !== undefined && String(user.pluginRoutesParentName).trim() !== ''
347
+ ? String(user.pluginRoutesParentName).trim()
348
+ : '',
349
+ ...(typeof user.fetchManifest === 'function' ? { fetchManifest: user.fetchManifest } : {}),
350
+ ...(typeof user.transformRoutes === 'function' ? { transformRoutes: user.transformRoutes } : {}),
351
+ ...(typeof user.interceptRegisterRoutes === 'function'
352
+ ? { interceptRegisterRoutes: user.interceptRegisterRoutes }
353
+ : {}),
354
+ ...(typeof user.adaptRouteDeclarations === 'function'
355
+ ? { adaptRouteDeclarations: user.adaptRouteDeclarations }
356
+ : {})
390
357
  }
391
358
  }
392
359
 
393
360
  /**
394
- * 默认清单 HTTP 拉取(未配置 `fetchManifest` 时)。
395
- * @module runtime/default-fetch-manifest
396
- */
397
-
398
- /**
399
- * @typedef {object} FetchWebPluginManifestContext
400
- * @property {string} manifestUrl
401
- * @property {RequestCredentials} credentials
402
- */
403
-
404
- /**
405
- * @typedef {object} FetchWebPluginManifestResult
406
- * @property {boolean} ok
407
- * @property {number} [status]
408
- * @property {{ hostPluginApiVersion?: string, plugins?: object[] }|null} [data]
409
- * @property {unknown} [error]
410
- */
411
-
412
- /**
413
- * @callback FetchWebPluginManifestFn
414
- * @param {FetchWebPluginManifestContext} ctx
415
- * @returns {Promise<FetchWebPluginManifestResult>}
416
- */
417
-
418
- /**
419
- * 默认清单请求(未配置 `fetchManifest` 时)。
420
- * @param {FetchWebPluginManifestContext} ctx
421
- * @returns {Promise<FetchWebPluginManifestResult>}
361
+ * 未配置 `fetchManifest` 时使用的清单 `fetch` 实现。
362
+ * @param {{ manifestUrl: string, credentials: RequestCredentials }} ctx
422
363
  */
423
364
  async function defaultFetchWebPluginManifest$1(ctx) {
424
365
  const { manifestUrl, credentials } = ctx;
@@ -3159,11 +3100,11 @@ function requireSemver () {
3159
3100
  var semverExports = requireSemver();
3160
3101
  var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
3161
3102
 
3162
- /**
3163
- * `plugin-web-starter`(WebPluginsResponse)返回的 `hostPluginApiVersion` 保持一致,用于契约校验。
3164
- * @type {string}
3165
- */
3166
- const HOST_PLUGIN_API_VERSION = '1.0.0';
3103
+ /** 与清单服务 `hostPluginApiVersion` 对齐,用于 `semver` 校验。 */
3104
+ const HOST_PLUGIN_API_VERSION = '1.0.0';
3105
+
3106
+ /** 控制台日志与横幅使用的短名称。 */
3107
+ const RUNTIME_CONSOLE_LABEL = 'web-extend-plugin-vue2';
3167
3108
 
3168
3109
  /**
3169
3110
  * 开发模式插件 URL 映射(显式 JSON + 隐式 dev 探测)。
@@ -3185,7 +3126,7 @@ function parseWebPluginDevMapExplicit(opts) {
3185
3126
  const map = JSON.parse(String(raw));
3186
3127
  return map && typeof map === 'object' ? map : null
3187
3128
  } catch {
3188
- console.warn('[plugins] webPluginDevMapJson / VITE_WEB_PLUGIN_DEV_MAP is not valid JSON');
3129
+ console.warn('[wep] invalid webPluginDevMapJson / VITE_WEB_PLUGIN_DEV_MAP');
3189
3130
  return null
3190
3131
  }
3191
3132
  }
@@ -3267,14 +3208,7 @@ async function buildImplicitWebPluginDevMap(opts, hostSet) {
3267
3208
  map[id] = `${base}${pathPart}`;
3268
3209
  }
3269
3210
  if (ids.length) {
3270
- console.info(
3271
- '[plugins] 已检测到插件 dev 服务(',
3272
- base,
3273
- '),下列 id 将加载隐式 dev 入口(',
3274
- pathPart,
3275
- ')而非清单 dist:',
3276
- ids.join(', ')
3277
- );
3211
+ console.info('[wep] plugin dev server', base, '→ implicit entries', pathPart, ids.join(', '));
3278
3212
  }
3279
3213
  return map
3280
3214
  }
@@ -3342,10 +3276,10 @@ function startPluginDevReloadSse(origin, isDev, hostSet, ssePath) {
3342
3276
  window.location.reload();
3343
3277
  });
3344
3278
  es.onopen = () => {
3345
- console.info('[plugins] plugin dev reload SSE:', url);
3279
+ console.info('[wep] dev reload SSE', url);
3346
3280
  };
3347
3281
  } catch (e) {
3348
- console.warn('[plugins] EventSource failed', url, e);
3282
+ console.warn('[wep] EventSource failed', url, e);
3349
3283
  }
3350
3284
  }
3351
3285
 
@@ -3433,9 +3367,21 @@ function loadScript(src) {
3433
3367
  return p
3434
3368
  }
3435
3369
 
3370
+ let _printed = false;
3371
+
3372
+ /** 在首次引导插件时打印一行运行时标识(非大块 ASCII art)。 */
3373
+ function printRuntimeBannerOnce() {
3374
+ if (_printed) {
3375
+ return
3376
+ }
3377
+ _printed = true;
3378
+ if (typeof console !== 'undefined' && typeof console.info === 'function') {
3379
+ console.info(`[wep] ${RUNTIME_CONSOLE_LABEL} · host API ${HOST_PLUGIN_API_VERSION}`);
3380
+ }
3381
+ }
3382
+
3436
3383
  /**
3437
- * 拉取清单、合并 dev 映射、加载入口并执行 activator
3438
- * @module runtime/bootstrap-plugins
3384
+ * 拉取插件清单、加载入口脚本并调用各插件 `activator`。
3439
3385
  */
3440
3386
 
3441
3387
  /**
@@ -3468,7 +3414,7 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
3468
3414
  const devEntry = devMap && typeof devMap[p.id] === 'string' ? devMap[p.id].trim() : '';
3469
3415
  if (devEntry) {
3470
3416
  if (!isScriptHostAllowed(devEntry, hostSet)) {
3471
- console.warn('[plugins] dev entry URL not allowed', p.id, devEntry);
3417
+ console.warn('[wep] dev entry URL not allowed', p.id, devEntry);
3472
3418
  return
3473
3419
  }
3474
3420
  try {
@@ -3478,7 +3424,7 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
3478
3424
  devEntry
3479
3425
  );
3480
3426
  } catch (e) {
3481
- console.warn('[plugins] dev module import failed, try manifest entryUrl', p.id, e);
3427
+ console.warn('[wep] dev import failed, trying manifest entryUrl', p.id, e);
3482
3428
  if (entryUrl && isScriptHostAllowed(entryUrl, hostSet)) {
3483
3429
  await loadScript(entryUrl);
3484
3430
  }
@@ -3487,7 +3433,7 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
3487
3433
  return
3488
3434
  }
3489
3435
  if (!entryUrl || !isScriptHostAllowed(entryUrl, hostSet)) {
3490
- console.warn('[plugins] skip (entryUrl not allowed)', p.id, entryUrl);
3436
+ console.warn('[wep] skip (entryUrl not allowed)', p.id, entryUrl);
3491
3437
  return
3492
3438
  }
3493
3439
  await loadScript(entryUrl);
@@ -3495,14 +3441,15 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
3495
3441
 
3496
3442
  /**
3497
3443
  * @param {import('vue-router').default} router
3498
- * @param {(pluginId: string, router: import('vue-router').default, hostKit?: { bridgeAllowedPathPrefixes: string[] }) => object} createHostApiFactory
3444
+ * @param {(pluginId: string, router: import('vue-router').default, hostKit?: object) => object} createHostApiFactory
3499
3445
  * @param {import('./resolve-runtime-options.js').WebExtendPluginRuntimeOptions} [runtimeOptions]
3500
3446
  */
3501
3447
  async function bootstrapPlugins$1(router, createHostApiFactory, runtimeOptions) {
3502
3448
  if (typeof window === 'undefined') {
3503
- console.warn('[plugins] bootstrapPlugins skipped: requires browser (window)');
3449
+ console.warn('[wep] bootstrapPlugins skipped (no window)');
3504
3450
  return
3505
3451
  }
3452
+ printRuntimeBannerOnce();
3506
3453
  const opts = resolveRuntimeOptions$1(runtimeOptions || {});
3507
3454
  const base = String(opts.manifestBase).replace(/\/$/, '');
3508
3455
  const manifestUrl = `${base}${opts.manifestListPath}`;
@@ -3530,16 +3477,28 @@ async function bootstrapPlugins$1(router, createHostApiFactory, runtimeOptions)
3530
3477
  const devMap = mergeDevMaps(implicit, explicit);
3531
3478
  startPluginDevSseForMap(devMap, opts.isDev, hostSet, opts.devReloadSsePath);
3532
3479
 
3533
- const hostKit = { bridgeAllowedPathPrefixes: opts.bridgeAllowedPathPrefixes };
3480
+ const hostKit = {
3481
+ bridgeAllowedPathPrefixes: opts.bridgeAllowedPathPrefixes,
3482
+ ...(opts.pluginRoutesParentName
3483
+ ? { pluginRoutesParentName: opts.pluginRoutesParentName }
3484
+ : {}),
3485
+ ...(typeof opts.transformRoutes === 'function' ? { transformRoutes: opts.transformRoutes } : {}),
3486
+ ...(typeof opts.interceptRegisterRoutes === 'function'
3487
+ ? { interceptRegisterRoutes: opts.interceptRegisterRoutes }
3488
+ : {}),
3489
+ ...(typeof opts.adaptRouteDeclarations === 'function'
3490
+ ? { adaptRouteDeclarations: opts.adaptRouteDeclarations }
3491
+ : {})
3492
+ };
3534
3493
 
3535
3494
  if (!manifestResult.ok) {
3536
3495
  if (manifestResult.error) {
3537
- console.warn('[plugins] fetch manifest failed', manifestResult.error);
3496
+ console.warn('[wep] fetch manifest failed', manifestResult.error);
3538
3497
  } else {
3539
- console.warn('[plugins] manifest HTTP', manifestResult.status, manifestUrl);
3498
+ console.warn('[wep] manifest HTTP', manifestResult.status, manifestUrl);
3540
3499
  }
3541
3500
  if (shouldShowBootstrapSummary(opts)) {
3542
- console.info('[plugins] bootstrap_summary', { ok: false, reason: 'manifest_fetch' });
3501
+ console.info('[wep] bootstrap_summary', { ok: false, reason: 'manifest_fetch' });
3543
3502
  }
3544
3503
  return
3545
3504
  }
@@ -3547,7 +3506,7 @@ async function bootstrapPlugins$1(router, createHostApiFactory, runtimeOptions)
3547
3506
  const data = manifestResult.data;
3548
3507
  if (!data) {
3549
3508
  if (shouldShowBootstrapSummary(opts)) {
3550
- console.info('[plugins] bootstrap_summary', { ok: false, reason: 'manifest_empty_body' });
3509
+ console.info('[wep] bootstrap_summary', { ok: false, reason: 'manifest_empty_body' });
3551
3510
  }
3552
3511
  return
3553
3512
  }
@@ -3558,12 +3517,10 @@ async function bootstrapPlugins$1(router, createHostApiFactory, runtimeOptions)
3558
3517
  const maj = coerced ? coerced.major : 0;
3559
3518
  const range = `^${maj}.0.0`;
3560
3519
  if (!semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
3561
- console.warn(
3562
- '[plugins] host API version mismatch: host implements',
3563
- HOST_PLUGIN_API_VERSION,
3564
- 'server declares',
3565
- apiVer
3566
- );
3520
+ console.warn('[wep] host API version mismatch', {
3521
+ host: HOST_PLUGIN_API_VERSION,
3522
+ manifest: apiVer
3523
+ });
3567
3524
  }
3568
3525
  }
3569
3526
 
@@ -3571,11 +3528,7 @@ async function bootstrapPlugins$1(router, createHostApiFactory, runtimeOptions)
3571
3528
 
3572
3529
  const plugins = data.plugins || [];
3573
3530
  if (plugins.length === 0) {
3574
- console.info(
3575
- '[plugins] 清单为空。请检查:① 后端清单服务(plugin-web-starter)是否已接入;② web-plugin.web-plugins-dir 是否指向含各插件子目录及 manifest.json 的路径;③ 浏览器直接访问',
3576
- manifestUrl,
3577
- '是否返回 plugins 条目。'
3578
- );
3531
+ console.info('[wep] empty plugin manifest — check backend and URL', manifestUrl);
3579
3532
  }
3580
3533
 
3581
3534
  const summary = {
@@ -3590,7 +3543,7 @@ async function bootstrapPlugins$1(router, createHostApiFactory, runtimeOptions)
3590
3543
  for (const p of plugins) {
3591
3544
  const range = p.engines && p.engines.host;
3592
3545
  if (range && !semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
3593
- console.warn('[plugins] skip (engines.host)', p.id, range);
3546
+ console.warn('[wep] skip plugin (engines.host)', p.id, range);
3594
3547
  summary.skipEngines++;
3595
3548
  continue
3596
3549
  }
@@ -3598,39 +3551,34 @@ async function bootstrapPlugins$1(router, createHostApiFactory, runtimeOptions)
3598
3551
  try {
3599
3552
  await loadPluginEntry(p, entryUrl, devMap, hostSet);
3600
3553
  } catch (e) {
3601
- console.warn('[plugins] script load failed', p.id, e);
3554
+ console.warn('[wep] script load failed', p.id, e);
3602
3555
  summary.skipLoad++;
3603
3556
  continue
3604
3557
  }
3605
3558
  const activator = window.__PLUGIN_ACTIVATORS__[p.id];
3606
3559
  if (typeof activator !== 'function') {
3607
- console.warn('[plugins] no activator for', p.id);
3560
+ console.warn('[wep] no activator for', p.id);
3608
3561
  summary.skipNoActivator++;
3609
3562
  continue
3610
3563
  }
3611
3564
  const hostApi = createHostApiFactory(p.id, router, hostKit);
3612
3565
  try {
3613
- activator(hostApi);
3566
+ const pluginRecord = Object.freeze({ ...p });
3567
+ activator(hostApi, { pluginRecord });
3614
3568
  summary.activated++;
3615
3569
  } catch (e) {
3616
- console.error('[plugins] activate failed', p.id, e);
3570
+ console.error('[wep] activate failed', p.id, e);
3617
3571
  summary.activateFail++;
3618
3572
  }
3619
3573
  }
3620
3574
 
3621
3575
  if (shouldShowBootstrapSummary(opts)) {
3622
- console.info('[plugins] bootstrap_summary', { ok: true, ...summary });
3576
+ console.info('[wep] bootstrap_summary', { ok: true, ...summary });
3623
3577
  }
3624
3578
  }
3625
3579
 
3626
3580
  /**
3627
- * 宿主侧插件引导:拉取清单、dev 映射、加载入口脚本、调用 activator。
3628
- * 实现已拆至 `src/runtime/` 下各模块;本文件保留模块说明与对外类型约定,并 re-export 稳定 API。
3629
- *
3630
- * **Webpack 宿主**:用 `DefinePlugin` 注入 `process.env.VITE_*` 或 **`PLUGIN_*`**(等价键),或 `resolveRuntimeOptions` 显式传参。
3631
- * **Vite 宿主**:入口调用 `setWebExtendPluginEnv(import.meta.env)`,或 `installWebExtendPluginVue2(..., { env: import.meta.env })`。
3632
- *
3633
- * @module PluginRuntime
3581
+ * 运行时引导相关 API 的聚合导出(实现位于 `./runtime/`)。
3634
3582
  */
3635
3583
 
3636
3584
  var pluginRuntime = /*#__PURE__*/Object.freeze({
@@ -3642,7 +3590,7 @@ var pluginRuntime = /*#__PURE__*/Object.freeze({
3642
3590
 
3643
3591
  /**
3644
3592
  * 清单拉取函数的组合工具:缓存、埋点等以**中间件**形式扩展,不侵入 `bootstrapPlugins` 核心逻辑,
3645
- * 符合第三方依赖「可组合、可替换」约定。契约与 `PluginRuntime` `fetchManifest` 一致。
3593
+ * 可组合的 `fetchManifest` 包装;入参/出参与 `resolveRuntimeOptions({ fetchManifest })` 一致。
3646
3594
  *
3647
3595
  * @module runtime/manifest-fetch-composer
3648
3596
  */
@@ -3857,356 +3805,367 @@ var manifestComposer = /*#__PURE__*/Object.freeze({
3857
3805
  wrapManifestFetchWithCache: wrapManifestFetchWithCache$1
3858
3806
  });
3859
3807
 
3860
- /**
3861
- * 插件通过宿主访问后端的受控通道:仅允许配置的前缀路径,默认 `/api/`;强制默认 `same-origin` 携带 Cookie。
3862
- * 前缀列表由 `createRequestBridge({ allowedPathPrefixes })` 传入,与 `defaultWebExtendPluginRuntime.bridgeAllowedPathPrefixes` 对齐。
3863
- *
3864
- * @module bridge
3865
- */
3866
-
3867
- /**
3868
- * @param {{ allowedPathPrefixes?: string[] }} [config]
3869
- */
3870
- function createRequestBridge(config = {}) {
3871
- const raw =
3872
- Array.isArray(config.allowedPathPrefixes) && config.allowedPathPrefixes.length > 0
3873
- ? config.allowedPathPrefixes
3874
- : defaultWebExtendPluginRuntime.bridgeAllowedPathPrefixes;
3875
- const allowedPathPrefixes = raw.map((p) => ensureLeadingPath(p));
3876
-
3877
- return {
3878
- /**
3879
- * 发起受控 `fetch`。
3880
- * @param {string} path 必须以 `/` 开头,且匹配某一 `allowedPathPrefixes` 前缀
3881
- * @param {RequestInit} [init] 会与默认 `{ credentials: 'same-origin' }` 合并(后者可被覆盖)
3882
- */
3883
- async request(path, init = {}) {
3884
- if (typeof path !== 'string' || !path.startsWith('/')) {
3885
- throw new Error('[bridge] path must be a string starting with /')
3886
- }
3887
- const allowed = allowedPathPrefixes.some((p) => path.startsWith(p));
3888
- if (!allowed) {
3889
- throw new Error('[bridge] path not allowed: ' + path)
3890
- }
3891
- return fetch(path, {
3892
- credentials: 'same-origin',
3893
- ...init
3894
- })
3895
- }
3896
- }
3808
+ /**
3809
+ * 插件访问后端的受控通道:`fetch` 仅允许落在配置的路径前缀下(默认 `/api/`),默认 `credentials: 'same-origin'`。
3810
+ */
3811
+
3812
+ /**
3813
+ * @param {{ allowedPathPrefixes?: string[] }} [config]
3814
+ */
3815
+ function createRequestBridge(config = {}) {
3816
+ const raw =
3817
+ Array.isArray(config.allowedPathPrefixes) && config.allowedPathPrefixes.length > 0
3818
+ ? config.allowedPathPrefixes
3819
+ : defaultWebExtendPluginRuntime.bridgeAllowedPathPrefixes;
3820
+ const allowedPathPrefixes = raw.map((p) => ensureLeadingPath(p));
3821
+
3822
+ return {
3823
+ /**
3824
+ * @param {string} path 必须以 `/` 开头,且匹配某一白名单前缀
3825
+ * @param {RequestInit} [init]
3826
+ */
3827
+ async request(path, init = {}) {
3828
+ if (typeof path !== 'string' || !path.startsWith('/')) {
3829
+ throw new Error('[wep:bridge] path must start with /')
3830
+ }
3831
+ const allowed = allowedPathPrefixes.some((p) => path.startsWith(p));
3832
+ if (!allowed) {
3833
+ throw new Error('[wep:bridge] path not allowed: ' + path)
3834
+ }
3835
+ return fetch(path, {
3836
+ credentials: 'same-origin',
3837
+ ...init
3838
+ })
3839
+ }
3840
+ }
3897
3841
  }
3898
3842
 
3899
- /**
3900
- * 宿主全局响应式注册表:菜单与扩展点槽位,供布局与 `ExtensionPoint` 订阅。
3901
- *
3902
- * @module registries
3903
- */
3904
-
3905
- /**
3906
- * @type {{
3907
- * menus: object[],
3908
- * slots: Record<string, Array<{ pluginId: string, component: import('vue').Component, priority: number, key: string }>>
3909
- * }}
3910
- */
3911
- const registries = Vue.observable({
3912
- menus: [],
3913
- /** 扩展点 id → 已注册组件列表(内容区 / 工具栏等共用模型) */
3914
- slots: {},
3915
- /**
3916
- * 每次变更 slots 时递增,供 ExtensionPoint 计算属性显式依赖。
3917
- * Vue 2 对「先访问不存在的 slots[key]、后 Vue.set 补 key」的依赖收集不可靠,会导致扩展点不刷新。
3918
- */
3919
- slotRevision: 0
3843
+ /**
3844
+ * 宿主侧响应式注册表:插件菜单与扩展点槽位(供布局与 `ExtensionPoint` 消费)。
3845
+ */
3846
+
3847
+ /**
3848
+ * @type {{
3849
+ * menus: object[],
3850
+ * slots: Record<string, Array<{ pluginId: string, component: import('vue').Component, priority: number, key: string }>>
3851
+ * }}
3852
+ */
3853
+ const registries = Vue.observable({
3854
+ menus: [],
3855
+ slots: {},
3856
+ /** 槽位变更计数;缓解 Vue2 对动态 `Vue.set(slots, key)` 的依赖收集不完整。 */
3857
+ slotRevision: 0
3920
3858
  });
3921
3859
 
3922
- /**
3923
- * `pluginId` 收集 `onTeardown` 回调,供 `disposeWebPlugin` 统一执行。
3924
- *
3925
- * @module teardown-registry
3926
- */
3927
-
3928
- /** @type {Map<string, Function[]>} */
3929
- const byPlugin = new Map();
3930
-
3931
- /**
3932
- * 登记插件卸载时要执行的同步回调(建议只做解绑、清定时器等轻量逻辑)。
3933
- * @param {string} pluginId
3934
- * @param {() => void} fn
3935
- */
3936
- function registerPluginTeardown(pluginId, fn) {
3937
- if (typeof fn !== 'function') {
3938
- return
3939
- }
3940
- let arr = byPlugin.get(pluginId);
3941
- if (!arr) {
3942
- arr = [];
3943
- byPlugin.set(pluginId, arr);
3944
- }
3945
- arr.push(fn);
3946
- }
3947
-
3948
- /**
3949
- * 执行并清空该插件已登记的全部 teardown;调用后 Map 中不再保留该 id。
3950
- * @param {string} pluginId
3951
- */
3952
- function runPluginTeardowns(pluginId) {
3953
- const arr = byPlugin.get(pluginId);
3954
- if (!arr) {
3955
- return
3956
- }
3957
- byPlugin.delete(pluginId);
3958
- for (const fn of arr) {
3959
- try {
3960
- fn();
3961
- } catch (e) {
3962
- console.warn('[plugins] teardown failed', pluginId, e);
3963
- }
3964
- }
3860
+ /**
3861
+ * 按插件 id 收集 `onTeardown` 回调,供 `disposeWebPlugin` 统一执行。
3862
+ */
3863
+
3864
+ /** @type {Map<string, Array<() => void>>} */
3865
+ const _byPlugin = new Map();
3866
+
3867
+ /**
3868
+ * @param {string} pluginId
3869
+ * @param {() => void} fn
3870
+ */
3871
+ function registerPluginTeardown(pluginId, fn) {
3872
+ if (typeof fn !== 'function') {
3873
+ return
3874
+ }
3875
+ let list = _byPlugin.get(pluginId);
3876
+ if (!list) {
3877
+ list = [];
3878
+ _byPlugin.set(pluginId, list);
3879
+ }
3880
+ list.push(fn);
3965
3881
  }
3966
3882
 
3967
- /**
3968
- * 构造供插件 activator 调用的宿主 API(路由、菜单、扩展点、资源注入等)。
3969
- * 与打包工具无关;Webpack 宿主需已配置 `vue-loader` 以编译本包内的 `.vue` 依赖。
3970
- *
3971
- * @module createHostApi
3972
- */
3973
-
3974
- /** 扩展点列表项 key 递增,避免用 Date.now() 导致列表重排时误卸载组件 */
3975
- let slotItemKeySeq = 0;
3976
- /** name 的动态路由合成名递增,避免多次 registerRoutes 重名 */
3977
- let routeSynthSeq = 0;
3978
-
3979
- /**
3980
- * @typedef {object} RegisterSlotEntry
3981
- * @property {import('vue').Component} component
3982
- * @property {number} [priority]
3983
- */
3984
-
3985
- /**
3986
- * @typedef {object} HostApi
3987
- * @property {string} hostPluginApiVersion 宿主实现的协议版本
3988
- * @property {(routes: import('vue-router').RouteConfig[]) => void} registerRoutes 注册路由;无 `name` 时自动生成稳定合成名;优先 `router.addRoute`
3989
- * @property {(items: object[]) => void} registerMenuItems 注册菜单并按 `order` 排序
3990
- * @property {(pointId: string, components: RegisterSlotEntry[]) => void} registerSlotComponents 向扩展点挂载组件
3991
- * @property {(urls?: string[]) => void} registerStylesheetUrls 注入 `link[rel=stylesheet]`,带 `data-plugin-asset`
3992
- * @property {(urls?: string[]) => void} registerScriptUrls 顺序注入外链脚本
3993
- * @property {() => void} registerSanitizedHtmlSnippet MVP 未实现,调用即抛错
3994
- * @property {() => ReturnType<typeof createRequestBridge>} getBridge 受控 `fetch` 代理
3995
- * @property {(pluginId: string, fn: () => void) => void} onTeardown 注册卸载回调;由 `disposeWebPlugin(pluginId)` 触发
3996
- */
3997
-
3998
- /**
3999
- * @typedef {object} HostKitOptions
4000
- * @property {string[]} [bridgeAllowedPathPrefixes] 覆盖 `getBridge().request` 允许的 URL 前缀;默认见 `defaultWebExtendPluginRuntime.bridgeAllowedPathPrefixes`
4001
- */
4002
-
4003
- /**
4004
- * 创建单个插件在宿主侧的 API 句柄,传入插件 `activator(hostApi)`。
4005
- *
4006
- * `bootstrapPlugins` 始终以 `(pluginId, router, hostKitOptions)` 调用工厂;请使用 `(id, r, kit) => createHostApi(id, r, kit)` 传入 `bridgeAllowedPathPrefixes`。
4007
- * 单参工厂 `(id) => createHostApi(id, router)` 仍可用(忽略后两个实参),此时 bridge 仅用包内默认前缀。
4008
- *
4009
- * @param {string} pluginId 与 manifest.id 一致
4010
- * @param {import('vue-router').default} router 宿主 Vue Router 实例(vue-router@3)
4011
- * @param {HostKitOptions} [hostKitOptions]
4012
- * @returns {HostApi}
4013
- */
4014
- function createHostApi(pluginId, router, hostKitOptions = {}) {
4015
- const bridgePrefixes =
4016
- Array.isArray(hostKitOptions.bridgeAllowedPathPrefixes) &&
4017
- hostKitOptions.bridgeAllowedPathPrefixes.length > 0
4018
- ? hostKitOptions.bridgeAllowedPathPrefixes
4019
- : defaultWebExtendPluginRuntime.bridgeAllowedPathPrefixes;
4020
- const bridge = createRequestBridge({ allowedPathPrefixes: bridgePrefixes });
4021
-
4022
- /**
4023
- * 注入样式表;`disposeWebPlugin` 会按 `data-plugin-asset` 移除对应节点。
4024
- * @param {string} href
4025
- */
4026
- function injectStylesheet(href) {
4027
- const link = document.createElement('link');
4028
- link.rel = 'stylesheet';
4029
- link.href = href;
4030
- link.setAttribute('data-plugin-asset', pluginId);
4031
- document.head.appendChild(link);
4032
- }
4033
-
4034
- /**
4035
- * 注入外链脚本(用于插件额外资源,非清单主入口)。
4036
- * @param {string} src
4037
- * @returns {Promise<void>}
4038
- */
4039
- function injectScript(src) {
4040
- return new Promise((resolve, reject) => {
4041
- const s = document.createElement('script');
4042
- s.async = true;
4043
- s.src = src;
4044
- s.setAttribute('data-plugin-asset', pluginId);
4045
- s.onload = () => resolve();
4046
- s.onerror = () => reject(new Error('script failed: ' + src));
4047
- document.head.appendChild(s);
4048
- })
4049
- }
4050
-
4051
- return {
4052
- hostPluginApiVersion: HOST_PLUGIN_API_VERSION,
4053
-
4054
- /**
4055
- * 动态注册路由。Vue Router 3.5+ 推荐 `addRoute`;若不存在则回退已弃用的 `addRoutes`。
4056
- * @param {import('vue-router').RouteConfig[]} routes
4057
- */
4058
- registerRoutes(routes) {
4059
- const wrapped = routes.map((r) => ({
4060
- ...r,
4061
- name: r.name || `__wep_${pluginId}_${routeSynthSeq++}`,
4062
- meta: { ...(r.meta || {}), pluginId }
4063
- }));
4064
- if (typeof router.addRoute === 'function') {
4065
- for (const r of wrapped) {
4066
- router.addRoute(r);
4067
- }
4068
- } else {
4069
- router.addRoutes(wrapped);
4070
- }
4071
- },
4072
-
4073
- /**
4074
- * 写入全局菜单注册表(响应式);按 `order` 升序排列。
4075
- * @param {object[]} items
4076
- */
4077
- registerMenuItems(items) {
4078
- for (const item of items) {
4079
- registries.menus.push({ ...item, pluginId });
4080
- }
4081
- registries.menus.sort(
4082
- (a, b) => (a.order != null ? a.order : 0) - (b.order != null ? b.order : 0)
4083
- );
4084
- },
4085
-
4086
- /**
4087
- * 向指定扩展点 id 注册 Vue 组件;`ExtensionPoint` 按 `priority` 降序渲染。
4088
- * @param {string} pointId
4089
- * @param {RegisterSlotEntry[]} components
4090
- */
4091
- registerSlotComponents(pointId, components) {
4092
- if (!pointId) {
4093
- return
4094
- }
4095
- if (!registries.slots[pointId]) {
4096
- Vue.set(registries.slots, pointId, []);
4097
- }
4098
- const list = registries.slots[pointId];
4099
- for (const c of components) {
4100
- list.push({
4101
- pluginId,
4102
- component: c.component,
4103
- priority: c.priority != null ? c.priority : 0,
4104
- key: `${pluginId}-${pointId}-${++slotItemKeySeq}`
4105
- });
4106
- }
4107
- list.sort((a, b) => b.priority - a.priority);
4108
- registries.slotRevision++;
4109
- },
4110
-
4111
- /**
4112
- * @param {string[]|undefined} urls
4113
- */
4114
- registerStylesheetUrls(urls) {
4115
- for (const u of urls || []) {
4116
- if (typeof u === 'string' && u) {
4117
- injectStylesheet(u);
4118
- }
4119
- }
4120
- },
4121
-
4122
- /**
4123
- * 串行加载多个脚本,失败仅告警不中断宿主。
4124
- * @param {string[]|undefined} urls
4125
- */
4126
- registerScriptUrls(urls) {
4127
- const chain = (urls || []).filter((u) => typeof u === 'string' && u).reduce(
4128
- (p, u) => p.then(() => injectScript(u)),
4129
- Promise.resolve()
4130
- );
4131
- chain.catch((e) => console.warn('[plugins] registerScriptUrls', pluginId, e));
4132
- },
4133
-
4134
- registerSanitizedHtmlSnippet() {
4135
- throw new Error('registerSanitizedHtmlSnippet is not enabled in MVP')
4136
- },
4137
-
4138
- getBridge: () => bridge,
4139
-
4140
- /**
4141
- * 插件卸载前清理逻辑;第一个参数为预留与协议对齐,实际以创建 API 时的 `pluginId` 为准。
4142
- * @param {string} _pluginId 预留,与 manifest.id 一致时可传入
4143
- * @param {() => void} fn
4144
- */
4145
- onTeardown(_pluginId, fn) {
4146
- if (typeof fn === 'function') {
4147
- registerPluginTeardown(pluginId, fn);
4148
- }
4149
- }
4150
- }
3883
+ /**
3884
+ * @param {string} pluginId
3885
+ */
3886
+ function runPluginTeardowns(pluginId) {
3887
+ const list = _byPlugin.get(pluginId);
3888
+ if (!list) {
3889
+ return
3890
+ }
3891
+ _byPlugin.delete(pluginId);
3892
+ for (const fn of list) {
3893
+ try {
3894
+ fn();
3895
+ } catch (e) {
3896
+ console.warn('[wep] teardown failed', pluginId, e);
3897
+ }
3898
+ }
4151
3899
  }
4152
3900
 
4153
- /**
4154
- * 单插件卸载:与 `bootstrapPlugins` / `createHostApi` 对称,清理注册表与 DOM 副作用。
4155
- *
4156
- * @module dispose-plugin
4157
- */
4158
-
4159
- /**
4160
- * 卸载指定 id 的插件:依次执行 teardown、移除菜单与扩展点条目、删除 activator、移除带 `data-plugin-asset` 的节点。
4161
- *
4162
- * **路由**:Vue Router 3 无公开 `removeRoute`,此处不改动 matcher;动态路由需整页刷新或自行维护路由表。
4163
- *
4164
- * @param {string} pluginId 与 manifest.id 一致
4165
- */
4166
- function disposeWebPlugin(pluginId) {
4167
- if (!pluginId || typeof pluginId !== 'string') {
4168
- return
4169
- }
4170
-
4171
- runPluginTeardowns(pluginId);
4172
-
4173
- for (let i = registries.menus.length - 1; i >= 0; i--) {
4174
- if (registries.menus[i].pluginId === pluginId) {
4175
- registries.menus.splice(i, 1);
4176
- }
4177
- }
4178
-
4179
- const slots = registries.slots;
4180
- for (const pointId of Object.keys(slots)) {
4181
- const list = slots[pointId];
4182
- if (!Array.isArray(list)) {
4183
- continue
4184
- }
4185
- const next = list.filter((x) => x.pluginId !== pluginId);
4186
- if (next.length === 0) {
4187
- Vue.delete(slots, pointId);
4188
- } else if (next.length !== list.length) {
4189
- Vue.set(slots, pointId, next);
4190
- }
4191
- }
4192
- registries.slotRevision++;
4193
-
4194
- if (typeof window !== 'undefined' && window.__PLUGIN_ACTIVATORS__) {
4195
- delete window.__PLUGIN_ACTIVATORS__[pluginId];
4196
- }
4197
-
4198
- if (typeof document !== 'undefined') {
4199
- document.querySelectorAll('[data-plugin-asset]').forEach((el) => {
4200
- if (el.getAttribute('data-plugin-asset') === pluginId) {
4201
- el.remove();
4202
- }
4203
- });
4204
- }
3901
+ /**
3902
+ * 构造插件 `activator(hostApi)` 使用的宿主 API:路由、菜单、扩展点、资源与受控请求桥。
3903
+ */
3904
+
3905
+ let slotItemKeySeq = 0;
3906
+ let routeSynthSeq = 0;
3907
+
3908
+ /**
3909
+ * @param {unknown[]} nodes
3910
+ */
3911
+ function analyzeRouteInputTree(nodes) {
3912
+ let hasDecl = false;
3913
+ let hasCfg = false;
3914
+ /** @param {unknown} r */
3915
+ function walk(r) {
3916
+ if (!r || typeof r !== 'object') {
3917
+ return
3918
+ }
3919
+ const o = /** @type {Record<string, unknown>} */ (r);
3920
+ const cfg = o.component != null || o.components != null;
3921
+ const decl = typeof o.componentRef === 'string';
3922
+ if (cfg) {
3923
+ hasCfg = true;
3924
+ } else if (decl) {
3925
+ hasDecl = true;
3926
+ }
3927
+ const ch = o.children;
3928
+ if (Array.isArray(ch)) {
3929
+ for (const c of ch) {
3930
+ walk(c);
3931
+ }
3932
+ }
3933
+ }
3934
+ for (const n of nodes) {
3935
+ walk(n);
3936
+ }
3937
+ return { hasDecl, hasCfg }
4205
3938
  }
4206
3939
 
4207
3940
  /**
4208
- * 在宿主布局中声明扩展点;插件通过 `hostApi.registerSlotComponents(pointId, ...)` 注入组件。
4209
- * 使用纯 render 函数,便于 Rollup 发布 dist,宿主无需再转译 .vue。
3941
+ * 单插件在宿主侧的 API 句柄。工厂请传 `(id, router, kit) => createHostApi(id, r, kit)` 以便 bridge 白名单等到位。
3942
+ *
3943
+ * @param {string} pluginId
3944
+ * @param {import('vue-router').default} router
3945
+ * @param {Record<string, unknown>} [hostKitOptions] 与 `resolveRuntimeOptions` 中路由/bridge 等字段一致
3946
+ */
3947
+ function createHostApi(pluginId, router, hostKitOptions = {}) {
3948
+ const bridgePrefixes =
3949
+ Array.isArray(hostKitOptions.bridgeAllowedPathPrefixes) &&
3950
+ hostKitOptions.bridgeAllowedPathPrefixes.length > 0
3951
+ ? hostKitOptions.bridgeAllowedPathPrefixes
3952
+ : defaultWebExtendPluginRuntime.bridgeAllowedPathPrefixes;
3953
+ const bridge = createRequestBridge({ allowedPathPrefixes: bridgePrefixes });
3954
+
3955
+ const parentName =
3956
+ typeof hostKitOptions.pluginRoutesParentName === 'string'
3957
+ ? hostKitOptions.pluginRoutesParentName.trim()
3958
+ : '';
3959
+
3960
+ /** @param {import('vue-router').RouteConfig[]} rawRouteConfigs */
3961
+ function applyInternalRegister(rawRouteConfigs) {
3962
+ const wrapped = rawRouteConfigs.map((r) => ({
3963
+ ...r,
3964
+ name: r.name || `__wep_${pluginId}_${routeSynthSeq++}`,
3965
+ meta: { ...(r.meta || {}), pluginId }
3966
+ }));
3967
+ if (typeof router.addRoute === 'function') {
3968
+ if (parentName) {
3969
+ for (const r of wrapped) {
3970
+ router.addRoute(parentName, r);
3971
+ }
3972
+ } else {
3973
+ for (const r of wrapped) {
3974
+ router.addRoute(r);
3975
+ }
3976
+ }
3977
+ } else {
3978
+ if (parentName) {
3979
+ console.warn(
3980
+ '[wep] pluginRoutesParentName requires vue-router 3.5+ addRoute; falling back to top-level addRoutes'
3981
+ );
3982
+ }
3983
+ router.addRoutes(wrapped);
3984
+ }
3985
+ }
3986
+
3987
+ function injectStylesheet(href) {
3988
+ const link = document.createElement('link');
3989
+ link.rel = 'stylesheet';
3990
+ link.href = href;
3991
+ link.setAttribute('data-plugin-asset', pluginId);
3992
+ document.head.appendChild(link);
3993
+ }
3994
+
3995
+ /** @param {string} src */
3996
+ function injectScript(src) {
3997
+ return new Promise((resolve, reject) => {
3998
+ const s = document.createElement('script');
3999
+ s.async = true;
4000
+ s.src = src;
4001
+ s.setAttribute('data-plugin-asset', pluginId);
4002
+ s.onload = () => resolve();
4003
+ s.onerror = () => reject(new Error('script failed: ' + src));
4004
+ document.head.appendChild(s);
4005
+ })
4006
+ }
4007
+
4008
+ return {
4009
+ hostPluginApiVersion: HOST_PLUGIN_API_VERSION,
4010
+
4011
+ registerRoutes(routes) {
4012
+ const list = Array.isArray(routes) ? routes : [];
4013
+ if (list.length === 0) {
4014
+ return
4015
+ }
4016
+ const { hasDecl, hasCfg } = analyzeRouteInputTree(list);
4017
+ if (hasDecl && hasCfg) {
4018
+ throw new Error(
4019
+ '[wep] registerRoutes: cannot mix RouteDeclaration (componentRef) with RouteConfig (component)'
4020
+ )
4021
+ }
4022
+ let /** @type {import('vue-router').RouteConfig[]} */ configs;
4023
+ if (hasDecl) {
4024
+ const adapt = hostKitOptions.adaptRouteDeclarations;
4025
+ if (typeof adapt !== 'function') {
4026
+ throw new Error(
4027
+ '[wep] registerRoutes: RouteDeclaration (componentRef) requires adaptRouteDeclarations on the host'
4028
+ )
4029
+ }
4030
+ configs = adapt({
4031
+ pluginId,
4032
+ router,
4033
+ declarations: /** @type {unknown[]} */ (list)
4034
+ });
4035
+ } else {
4036
+ configs = /** @type {import('vue-router').RouteConfig[]} */ (/** @type {unknown} */ (list));
4037
+ }
4038
+
4039
+ if (typeof hostKitOptions.transformRoutes === 'function') {
4040
+ configs = hostKitOptions.transformRoutes({
4041
+ pluginId,
4042
+ router,
4043
+ routes: configs
4044
+ });
4045
+ }
4046
+
4047
+ if (typeof hostKitOptions.interceptRegisterRoutes === 'function') {
4048
+ hostKitOptions.interceptRegisterRoutes({
4049
+ pluginId,
4050
+ router,
4051
+ routes: configs,
4052
+ applyInternalRegister
4053
+ });
4054
+ } else {
4055
+ applyInternalRegister(configs);
4056
+ }
4057
+ },
4058
+
4059
+ registerMenuItems(items) {
4060
+ for (const item of items) {
4061
+ registries.menus.push({ ...item, pluginId });
4062
+ }
4063
+ registries.menus.sort(
4064
+ (a, b) => (a.order != null ? a.order : 0) - (b.order != null ? b.order : 0)
4065
+ );
4066
+ },
4067
+
4068
+ registerSlotComponents(pointId, components) {
4069
+ if (!pointId) {
4070
+ return
4071
+ }
4072
+ if (!registries.slots[pointId]) {
4073
+ Vue.set(registries.slots, pointId, []);
4074
+ }
4075
+ const list = registries.slots[pointId];
4076
+ for (const c of components) {
4077
+ list.push({
4078
+ pluginId,
4079
+ component: c.component,
4080
+ priority: c.priority != null ? c.priority : 0,
4081
+ key: `${pluginId}-${pointId}-${++slotItemKeySeq}`
4082
+ });
4083
+ }
4084
+ list.sort((a, b) => b.priority - a.priority);
4085
+ registries.slotRevision++;
4086
+ },
4087
+
4088
+ registerStylesheetUrls(urls) {
4089
+ for (const u of urls || []) {
4090
+ if (typeof u === 'string' && u) {
4091
+ injectStylesheet(u);
4092
+ }
4093
+ }
4094
+ },
4095
+
4096
+ registerScriptUrls(urls) {
4097
+ const chain = (urls || []).filter((u) => typeof u === 'string' && u).reduce(
4098
+ (p, u) => p.then(() => injectScript(u)),
4099
+ Promise.resolve()
4100
+ );
4101
+ chain.catch((e) => console.warn('[wep] registerScriptUrls', pluginId, e));
4102
+ },
4103
+
4104
+ registerSanitizedHtmlSnippet() {
4105
+ throw new Error('registerSanitizedHtmlSnippet is not enabled')
4106
+ },
4107
+
4108
+ getBridge: () => bridge,
4109
+
4110
+ onTeardown(_pluginId, fn) {
4111
+ if (typeof fn === 'function') {
4112
+ registerPluginTeardown(pluginId, fn);
4113
+ }
4114
+ }
4115
+ }
4116
+ }
4117
+
4118
+ /**
4119
+ * 卸载单个插件:执行 teardown、清理注册表与 activator、移除带 `data-plugin-asset` 的 DOM。
4120
+ * 注意:Vue Router 3 无公开 `removeRoute`,动态路由通常需整页刷新或宿主自行维护。
4121
+ */
4122
+
4123
+ /**
4124
+ * @param {string} pluginId 与 manifest `id` 一致
4125
+ */
4126
+ function disposeWebPlugin(pluginId) {
4127
+ if (!pluginId || typeof pluginId !== 'string') {
4128
+ return
4129
+ }
4130
+
4131
+ runPluginTeardowns(pluginId);
4132
+
4133
+ for (let i = registries.menus.length - 1; i >= 0; i--) {
4134
+ if (registries.menus[i].pluginId === pluginId) {
4135
+ registries.menus.splice(i, 1);
4136
+ }
4137
+ }
4138
+
4139
+ const slots = registries.slots;
4140
+ for (const pointId of Object.keys(slots)) {
4141
+ const list = slots[pointId];
4142
+ if (!Array.isArray(list)) {
4143
+ continue
4144
+ }
4145
+ const next = list.filter((x) => x.pluginId !== pluginId);
4146
+ if (next.length === 0) {
4147
+ Vue.delete(slots, pointId);
4148
+ } else if (next.length !== list.length) {
4149
+ Vue.set(slots, pointId, next);
4150
+ }
4151
+ }
4152
+ registries.slotRevision++;
4153
+
4154
+ if (typeof window !== 'undefined' && window.__PLUGIN_ACTIVATORS__) {
4155
+ delete window.__PLUGIN_ACTIVATORS__[pluginId];
4156
+ }
4157
+
4158
+ if (typeof document !== 'undefined') {
4159
+ document.querySelectorAll('[data-plugin-asset]').forEach((el) => {
4160
+ if (el.getAttribute('data-plugin-asset') === pluginId) {
4161
+ el.remove();
4162
+ }
4163
+ });
4164
+ }
4165
+ }
4166
+
4167
+ /**
4168
+ * 布局中的扩展点占位;插件通过 `hostApi.registerSlotComponents(pointId, ...)` 注入组件。
4210
4169
  */
4211
4170
 
4212
4171
  const SlotErrorBoundary = {
@@ -4217,7 +4176,7 @@ const SlotErrorBoundary = {
4217
4176
  },
4218
4177
  errorCaptured(err) {
4219
4178
  this.error = err && err.message ? err.message : String(err);
4220
- console.error('[ExtensionPoint] render error in', this.label, err);
4179
+ console.error('[wep:ExtensionPoint]', this.label, err);
4221
4180
  return false
4222
4181
  },
4223
4182
  render(h) {
@@ -4272,17 +4231,13 @@ var ExtensionPoint = {
4272
4231
  };
4273
4232
 
4274
4233
  /**
4275
- * 一键接入:注册 `ExtensionPoint` 并执行 `bootstrapPlugins`。
4276
- * @module install
4234
+ * 注册全局 `ExtensionPoint` 并异步拉取清单、激活插件。
4277
4235
  */
4278
4236
 
4279
4237
  /**
4280
- * 注册全局组件 `ExtensionPoint` 并异步引导插件清单。
4281
- *
4282
4238
  * @param {*} Vue
4283
- * @param {*} router vue-router 实例
4284
- * @param {Record<string, unknown>} [options] 传给 `resolveRuntimeOptions` 的字段;可含 `env`(Vite 传入 `import.meta.env`)以读取 `VITE_*`。
4285
- * @returns {Promise<void>}
4239
+ * @param {*} router
4240
+ * @param {Record<string, unknown>} [options] 传给 `resolveRuntimeOptions`;可含 `env` 以读取 `VITE_*`
4286
4241
  */
4287
4242
  function installWebExtendPluginVue2(Vue, router, options) {
4288
4243
  const opts = options || {};
@@ -4298,23 +4253,20 @@ function installWebExtendPluginVue2(Vue, router, options) {
4298
4253
  }
4299
4254
 
4300
4255
  /**
4301
- * 预设:Vue CLI + 统一 axios(如若依 `utils/request`)。
4302
- * 对外以 {@link presetVueCliAxios} 聚合;亦保留具名函数便于 tree-shaking。
4303
- *
4304
- * @module presets/vue-cli-axios
4256
+ * Vue CLI + 统一 axios(如 RuoYi `utils/request`)场景的 `install` 预设。
4305
4257
  */
4306
4258
 
4307
4259
  /**
4308
- * @typedef {object} VueCliAxiosInstallPresetDeps
4309
- * @property {(config: { url: string, method?: string, [key: string]: unknown }) => Promise<unknown>} request 宿主 axios 封装(已含 baseURL、Token 等)
4260
+ * @typedef {object} VueCliAxiosPresetDeps
4261
+ * @property {(config: { url: string, method?: string }) => Promise<unknown>} request
4310
4262
  */
4311
4263
 
4312
4264
  /**
4313
- * `manifestBase + manifestListPath` 转为相对 `VUE_APP_BASE_API` 的路径(供 axios baseURL 拼接)。
4265
+ * 将完整 manifest URL 转为相对 `apiBase` 的请求 path,供 axios `baseURL` 拼接。
4314
4266
  * @param {string} manifestUrl
4315
4267
  * @param {string} [apiBase]
4316
4268
  */
4317
- function manifestPathForVueCliApiBase(manifestUrl, apiBase) {
4269
+ function resolveManifestPathUnderApiBase(manifestUrl, apiBase) {
4318
4270
  const base = String(
4319
4271
  apiBase !== undefined
4320
4272
  ? apiBase
@@ -4332,11 +4284,11 @@ function manifestPathForVueCliApiBase(manifestUrl, apiBase) {
4332
4284
  }
4333
4285
 
4334
4286
  /**
4335
- * 兼容裸清单 JSON 与常见 `{ code, data: { plugins } }` 包装。
4287
+ * 将裸 `{ plugins }` 与 `{ code, data: { plugins } }` 式响应解包为清单对象。
4336
4288
  * @param {unknown} body
4337
4289
  * @returns {object|null}
4338
4290
  */
4339
- function unwrapTableStyleManifestBody(body) {
4291
+ function unwrapNestedManifestBody(body) {
4340
4292
  if (!body || typeof body !== 'object') {
4341
4293
  return null
4342
4294
  }
@@ -4368,15 +4320,13 @@ function bridgePrefixesFromVueCliEnv() {
4368
4320
  }
4369
4321
 
4370
4322
  /**
4371
- * 生成可直接传给 `installWebExtendPluginVue2` 的 options(含 fetchManifest、manifestBase、bridge 前缀)。
4372
- * @param {VueCliAxiosInstallPresetDeps} deps
4373
- * @param {Record<string, unknown>} [extra] 合并覆盖,如 `manifestListPath`、`env` 等
4374
- * @returns {Record<string, unknown>}
4323
+ * @param {VueCliAxiosPresetDeps} deps
4324
+ * @param {Record<string, unknown>} [extra]
4375
4325
  */
4376
4326
  function createVueCliAxiosInstallOptions(deps, extra = {}) {
4377
4327
  const { request } = deps;
4378
4328
  if (typeof request !== 'function') {
4379
- throw new Error('[web-extend-plugin-vue2] createVueCliAxiosInstallOptions({ request }) 需要宿主 request 函数')
4329
+ throw new Error('[wep] createVueCliAxiosInstallOptions requires deps.request')
4380
4330
  }
4381
4331
  const envBase = (
4382
4332
  typeof process !== 'undefined' && process.env && process.env.VUE_APP_BASE_API
@@ -4391,16 +4341,16 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
4391
4341
 
4392
4342
  const fetchManifest = async (ctx) => {
4393
4343
  try {
4394
- const url = manifestPathForVueCliApiBase(ctx.manifestUrl, stripBase);
4344
+ const url = resolveManifestPathUnderApiBase(ctx.manifestUrl, stripBase);
4395
4345
  const body = await request({
4396
4346
  url,
4397
4347
  method: 'get'
4398
4348
  });
4399
- const data = unwrapTableStyleManifestBody(body);
4349
+ const data = unwrapNestedManifestBody(body);
4400
4350
  if (!data || typeof data !== 'object') {
4401
4351
  return {
4402
4352
  ok: false,
4403
- error: new Error('[web-plugin] 清单响应格式无效'),
4353
+ error: new Error('[wep] invalid manifest response'),
4404
4354
  data: null
4405
4355
  }
4406
4356
  }
@@ -4426,28 +4376,18 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
4426
4376
  return opts
4427
4377
  }
4428
4378
 
4429
- /**
4430
- * Vue CLI + axios 预设的**对外聚合入口**(与具名导出函数等价,便于按需查阅与扩展多预设)。
4431
- * @type {Readonly<{ id: string, description: string, createInstallOptions: typeof createVueCliAxiosInstallOptions, manifestPathForApiBase: typeof manifestPathForVueCliApiBase, unwrapManifestBody: typeof unwrapTableStyleManifestBody }>}
4432
- */
4433
4379
  const presetVueCliAxios = Object.freeze({
4434
4380
  id: 'vue-cli-axios',
4435
- description: 'Vue CLI + 统一 axios 实例(如若依 utils/request),清单走宿主 request',
4381
+ description: 'Vue CLI + unified axios request; manifest uses host request()',
4436
4382
  createInstallOptions: createVueCliAxiosInstallOptions,
4437
- manifestPathForApiBase: manifestPathForVueCliApiBase,
4438
- unwrapManifestBody: unwrapTableStyleManifestBody
4383
+ manifestPathForApiBase: resolveManifestPathUnderApiBase,
4384
+ unwrapManifestBody: unwrapNestedManifestBody
4439
4385
  });
4440
4386
 
4441
4387
  /**
4442
- * 稳定对外 API:具名导出 + {@link WebExtendPluginVue2} 聚合对象。
4443
- * 语义化版本内保持本文件导出的符号与聚合结构兼容。
4444
- *
4445
- * `PluginRuntime` 与 `manifest-fetch-composer` 通过 namespace 绑定到 `WebExtendPluginVue2.runtime`,避免具名导出与聚合对象重复列举。
4446
- *
4447
- * @module public
4388
+ * 包对外稳定导出与 `WebExtendPluginVue2` 命名空间。
4448
4389
  */
4449
4390
 
4450
-
4451
4391
  const {
4452
4392
  bootstrapPlugins,
4453
4393
  defaultFetchWebPluginManifest,
@@ -4460,18 +4400,6 @@ const {
4460
4400
  wrapManifestFetchWithCache
4461
4401
  } = manifestComposer;
4462
4402
 
4463
- /**
4464
- * 根命名空间:宿主可通过 `WebExtendPluginVue2.presets.vueCliAxios` 等路径发现能力,避免仅依赖散落的具名导出。
4465
- * @type {Readonly<{
4466
- * install: typeof installWebExtendPluginVue2,
4467
- * runtime: Readonly<{ bootstrapPlugins: typeof pluginRuntime.bootstrapPlugins, resolveRuntimeOptions: typeof pluginRuntime.resolveRuntimeOptions, defaultFetchWebPluginManifest: typeof pluginRuntime.defaultFetchWebPluginManifest, composeManifestFetch: typeof manifestComposer.composeManifestFetch, manifestFetchCacheMiddleware: typeof manifestComposer.manifestFetchCacheMiddleware, wrapManifestFetchWithCache: typeof manifestComposer.wrapManifestFetchWithCache }>,
4468
- * host: Readonly<{ createHostApi: typeof createHostApi, disposeWebPlugin: typeof disposeWebPlugin, createRequestBridge: typeof createRequestBridge, registries: typeof registries }>,
4469
- * config: Readonly<{ defaultWebExtendPluginRuntime: typeof defaultWebExtendPluginRuntime, setWebExtendPluginEnv: typeof setWebExtendPluginEnv }>,
4470
- * constants: Readonly<{ HOST_PLUGIN_API_VERSION: typeof HOST_PLUGIN_API_VERSION }>,
4471
- * components: Readonly<{ ExtensionPoint: typeof ExtensionPoint }>,
4472
- * presets: Readonly<{ vueCliAxios: typeof presetVueCliAxios }>
4473
- * }>}
4474
- */
4475
4403
  const WebExtendPluginVue2 = Object.freeze({
4476
4404
  install: installWebExtendPluginVue2,
4477
4405
  runtime: Object.freeze({
@@ -4493,7 +4421,8 @@ const WebExtendPluginVue2 = Object.freeze({
4493
4421
  setWebExtendPluginEnv
4494
4422
  }),
4495
4423
  constants: Object.freeze({
4496
- HOST_PLUGIN_API_VERSION
4424
+ HOST_PLUGIN_API_VERSION,
4425
+ RUNTIME_CONSOLE_LABEL
4497
4426
  }),
4498
4427
  components: Object.freeze({
4499
4428
  ExtensionPoint
@@ -4503,5 +4432,5 @@ const WebExtendPluginVue2 = Object.freeze({
4503
4432
  })
4504
4433
  });
4505
4434
 
4506
- export { ExtensionPoint, HOST_PLUGIN_API_VERSION, WebExtendPluginVue2, bootstrapPlugins, composeManifestFetch, createHostApi, createRequestBridge, createVueCliAxiosInstallOptions, defaultFetchWebPluginManifest, defaultWebExtendPluginRuntime, disposeWebPlugin, installWebExtendPluginVue2, manifestFetchCacheMiddleware, manifestPathForVueCliApiBase, presetVueCliAxios, registries, resolveRuntimeOptions, setWebExtendPluginEnv, unwrapTableStyleManifestBody, wrapManifestFetchWithCache };
4435
+ export { ExtensionPoint, HOST_PLUGIN_API_VERSION, RUNTIME_CONSOLE_LABEL, WebExtendPluginVue2, bootstrapPlugins, composeManifestFetch, createHostApi, createRequestBridge, createVueCliAxiosInstallOptions, defaultFetchWebPluginManifest, defaultWebExtendPluginRuntime, disposeWebPlugin, installWebExtendPluginVue2, manifestFetchCacheMiddleware, presetVueCliAxios, registries, resolveManifestPathUnderApiBase, resolveRuntimeOptions, setWebExtendPluginEnv, unwrapNestedManifestBody, wrapManifestFetchWithCache };
4507
4436
  //# sourceMappingURL=index.mjs.map