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