web-extend-plugin-vue2 0.2.2 → 0.2.5

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,437 +1,491 @@
1
1
  import Vue from 'vue';
2
2
 
3
- /**
4
- * 宿主可覆盖的默认运行时配置(路径、白名单、超时等)。
5
- * 使用方式:`resolveRuntimeOptions({ ...defaultWebExtendPluginRuntime, manifestListPath: '/api/my-plugins' })`
6
- * 或只传需要改动的字段:`resolveRuntimeOptions({ manifestListPath: '/api/my-plugins' })`。
7
- *
8
- * @module default-runtime-config
9
- */
10
-
11
- /**
12
- * @typedef {typeof defaultWebExtendPluginRuntime} WebExtendPluginDefaultRuntime
13
- */
14
-
15
- const defaultWebExtendPluginRuntime = {
16
- /** 清单 HTTP 服务前缀(与后端 context-path 对齐),不含尾部 `/` */
17
- manifestBase: '/fp-api',
18
-
19
- /**
20
- * 拉取插件清单的 **路径段**(以 `/` 开头),拼在 `manifestBase` 之后。
21
- * 完整 URL:`{manifestBase}{manifestListPath}` → 默认 `/fp-api/api/frontend-plugins`
22
- */
23
- manifestListPath: '/api/frontend-plugins',
24
-
25
- /** 清单 `fetch` 的 `credentials`,需 Cookie 会话时用 `include` */
26
- manifestFetchCredentials: 'include',
27
-
28
- /** 插件 dev 服务存活探测路径(拼在 `webPluginDevOrigin` 后) */
29
- devPingPath: '/__web_plugin_dev_ping',
30
-
31
- /** 插件 dev 热更新 SSE 路径(拼在插件 dev 的 origin 后) */
32
- devReloadSsePath: '/__web_plugin_reload_stream',
33
-
34
- /** 隐式 dev 映射里,每个 id 对应的入口路径(相对插件 dev origin) */
35
- webPluginDevEntryPath: '/src/plugin-entry.js',
36
-
37
- /** `fetch(devPingUrl)` 超时毫秒数 */
38
- 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
- defaultImplicitDevPluginIds: [],
49
-
50
- /**
51
- * 允许通过 `<script>` / 动态 `import()` 加载的插件脚本所在主机名(小写),防误配公网 URL。
52
- */
53
- allowedScriptHosts: ['localhost', '127.0.0.1', '::1'],
54
-
55
- /**
56
- * `hostApi.getBridge().request(path)` 允许的 path 前缀;须以 `/` 开头。
57
- * 需与后端实际 API 前缀一致。
58
- */
59
- bridgeAllowedPathPrefixes: ['/api/']
3
+ /**
4
+ * 对外配置单一入口:`resolveRuntimeOptions` / 文档 / 其它模块的默认值与环境键名均由此引用。
5
+ * 宿主通过 `resolveRuntimeOptions(partial)` 覆盖的对象形状见 `defaultWebExtendPluginRuntime`。
6
+ */
7
+ /** 与 `package.json` 的 peer 下限一致;`npm run test:peer-min` / CI matrix 须与此保持同步 */
8
+ const peerMinimumVersions = {
9
+ vue: '2.6.14',
10
+ vueRouter: '3.5.4'
11
+ };
12
+ // ---------------------------------------------------------------------------
13
+ // 协议与品牌(发布契约;与清单 `hostPluginApiVersion` 对齐 semver 主版本,一般不随宿主覆盖)
14
+ // ---------------------------------------------------------------------------
15
+ const HOST_PLUGIN_API_VERSION = '1.0.0';
16
+ /** 控制台日志与首次引导横幅使用的短名称 */
17
+ const RUNTIME_CONSOLE_LABEL = 'web-extend-plugin-vue2';
18
+ // ---------------------------------------------------------------------------
19
+ // 与 `build-env` / `resolveBundledEnv` 配套的键名(单一事实来源,便于检索与文档)
20
+ // ---------------------------------------------------------------------------
21
+ const webExtendPluginEnvKeys = {
22
+ manifestBase: 'VITE_FRONTEND_PLUGIN_BASE',
23
+ manifestListPath: 'VITE_WEB_PLUGIN_MANIFEST_PATH',
24
+ implicitDevIds: 'VITE_WEB_PLUGIN_IMPLICIT_DEV_IDS',
25
+ allowedScriptHosts: 'VITE_WEB_PLUGIN_ALLOWED_SCRIPT_HOSTS',
26
+ bridgePrefixes: 'VITE_WEB_PLUGIN_BRIDGE_PREFIXES',
27
+ devFallbackManifestUrl: 'VITE_WEB_PLUGIN_DEV_FALLBACK_MANIFEST_URL',
28
+ manifestMode: 'VITE_WEB_PLUGIN_MANIFEST_MODE',
29
+ staticManifestUrl: 'VITE_WEB_PLUGIN_STATIC_MANIFEST_URL',
30
+ mountPath: 'VITE_WEB_PLUGIN_MOUNT_PATH',
31
+ devEntry: 'VITE_WEB_PLUGIN_DEV_ENTRY',
32
+ devPing: 'VITE_WEB_PLUGIN_DEV_PING_PATH',
33
+ devSse: 'VITE_WEB_PLUGIN_DEV_SSE_PATH',
34
+ devPingTimeout: 'VITE_WEB_PLUGIN_DEV_PING_TIMEOUT_MS',
35
+ manifestCredentials: 'VITE_WEB_PLUGIN_MANIFEST_CREDENTIALS',
36
+ devManifestFallback: 'VITE_WEB_PLUGIN_DEV_MANIFEST_FALLBACK',
37
+ webPluginDevOrigin: 'VITE_WEB_PLUGIN_DEV_ORIGIN',
38
+ webPluginDevIds: 'VITE_WEB_PLUGIN_DEV_IDS',
39
+ webPluginDevMap: 'VITE_WEB_PLUGIN_DEV_MAP',
40
+ pluginsBootstrapSummary: 'VITE_PLUGINS_BOOTSTRAP_SUMMARY',
41
+ /** 清单路径备选:部分宿主单独使用 */
42
+ manifestPathAlt: 'VUE_APP_WEB_PLUGIN_MANIFEST_PATH'
43
+ };
44
+ // ---------------------------------------------------------------------------
45
+ // `manifestMode` 未配置且环境未指定时的默认值
46
+ // ---------------------------------------------------------------------------
47
+ const defaultManifestMode = 'api';
48
+ // ---------------------------------------------------------------------------
49
+ // `manifest-fetch-composer` 中间件默认(中间件 options 可逐项覆盖)
50
+ // ---------------------------------------------------------------------------
51
+ const defaultManifestFetchCache = {
52
+ storageKeyPrefix: 'wep.manifestFetch.v1',
53
+ maxEntries: 50
54
+ };
55
+ // ---------------------------------------------------------------------------
56
+ // 路由合成名前缀(`createHostApi` 为无 name 的路由生成 `__wep_${pluginId}_${seq}`)
57
+ // ---------------------------------------------------------------------------
58
+ const routeSynthNamePrefix = '__wep_';
59
+ // ---------------------------------------------------------------------------
60
+ // 宿主可通过 `resolveRuntimeOptions` 覆盖的运行时默认值(与 README / index.d.ts 描述一致)
61
+ // ---------------------------------------------------------------------------
62
+ const defaultWebExtendPluginRuntime = {
63
+ manifestBase: '/fp-api',
64
+ manifestListPath: '/api/frontend-plugins',
65
+ manifestFetchCredentials: 'include',
66
+ devPingPath: '/__web_plugin_dev_ping',
67
+ devReloadSsePath: '/__web_plugin_reload_stream',
68
+ webPluginDevEntryPath: '/src/plugin-entry.js',
69
+ devPingTimeoutMs: 500,
70
+ defaultImplicitDevPluginIds: [],
71
+ allowedScriptHosts: ['localhost', '127.0.0.1', '::1'],
72
+ bridgeAllowedPathPrefixes: ['/api/'],
73
+ /** 与 `hostLayoutComponent` 同时使用时默认父路由 name */
74
+ pluginRoutesParentName: '__wepPluginHost',
75
+ /** 插件壳路径(与菜单、ensurePluginHostRoute 一致) */
76
+ pluginMountPath: '/plugin',
77
+ /** `manifestMode=api` 且开发环境下,API 失败或 plugins 为空时尝试的静态 JSON(可放于 `public/web-plugins/`) */
78
+ devFallbackStaticManifestUrl: '/web-plugins/plugins.manifest.json'
60
79
  };
61
80
 
62
81
  /**
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
82
+ * 与具体打包器解耦的运行时环境读取:优先显式注入,其次 `globalThis.__WEP_ENV__`,再读 `process.env`。
83
+ * Vite 宿主建议在入口调用 `setWebExtendPluginEnv(import.meta.env)`。
69
84
  */
70
-
71
- /** @type {Record<string, unknown> | null} */
72
- let _explicitEnv = null;
73
-
85
+ let _injected = null;
74
86
  /**
75
- * 显式注入与 `import.meta.env` 同形态的对象(推荐 Vite 宿主在入口调用一次)。
76
- * @param {Record<string, unknown> | null | undefined} env
87
+ * 注入与 `import.meta.env` 同形态的对象(键如 `VITE_*`、`DEV`)。
77
88
  */
78
89
  function setWebExtendPluginEnv(env) {
79
- _explicitEnv = env && typeof env === 'object' ? env : null;
90
+ _injected = env && typeof env === 'object' ? env : null;
80
91
  }
81
-
82
- /**
83
- * @returns {Record<string, unknown> | null}
84
- */
85
- function getInjectedEnvObject() {
86
- if (_explicitEnv) {
87
- return _explicitEnv
88
- }
89
- try {
90
- const g = typeof globalThis !== 'undefined' ? globalThis : undefined;
91
- if (g && g.__WEP_ENV__ && typeof g.__WEP_ENV__ === 'object') {
92
- return g.__WEP_ENV__
92
+ function getEnvObject() {
93
+ if (_injected) {
94
+ return _injected;
95
+ }
96
+ try {
97
+ const g = typeof globalThis !== 'undefined' ? globalThis : undefined;
98
+ const raw = g;
99
+ if (raw && raw.__WEP_ENV__ && typeof raw.__WEP_ENV__ === 'object') {
100
+ return raw.__WEP_ENV__;
101
+ }
93
102
  }
94
- } catch (_) {}
95
- return null
103
+ catch {
104
+ /* ignore */
105
+ }
106
+ return null;
96
107
  }
97
-
98
108
  /**
99
- * 从注入环境读取字符串配置键(`VITE_*` / `PLUGIN_*` 等)。
100
- * @param {string} key
101
- * @returns {string|undefined}
109
+ * 读取注入环境中的字符串配置(`VITE_*` / `PLUGIN_*` 等价键由调用方传入)。
102
110
  */
103
111
  function readInjectedEnvKey(key) {
104
- const o = getInjectedEnvObject();
105
- if (!o || !(key in o)) {
106
- return undefined
107
- }
108
- const v = o[key];
109
- if (v === undefined || v === '') {
110
- return undefined
111
- }
112
- return String(v)
112
+ const o = getEnvObject();
113
+ if (!o || !(key in o)) {
114
+ return undefined;
115
+ }
116
+ const v = o[key];
117
+ if (v === undefined || v === '') {
118
+ return undefined;
119
+ }
120
+ return String(v);
113
121
  }
114
-
115
- /**
116
- * @returns {boolean}
117
- */
118
122
  function readInjectedEnvDev() {
119
- const o = getInjectedEnvObject();
120
- return !!(o && o.DEV === true)
123
+ const o = getEnvObject();
124
+ return !!(o && o.DEV === true);
121
125
  }
122
126
 
123
127
  /**
124
- * 从注入环境与 `process.env` 解析 VITE_/PLUGIN_ 键。
125
- * @module runtime/env-resolve
126
- */
127
-
128
- /**
129
- * @param {string} key
130
- * @returns {string|undefined}
128
+ * 从注入环境与 `process.env` 解析 `VITE_*` / `PLUGIN_*` 键。
131
129
  */
132
130
  function readProcessEnv(key) {
133
- try {
134
- if (typeof process !== 'undefined' && process.env && key in process.env) {
135
- const v = process.env[key];
136
- if (v !== undefined && v !== '') {
137
- return String(v)
138
- }
131
+ try {
132
+ if (typeof process !== 'undefined' && process.env && key in process.env) {
133
+ const v = process.env[key];
134
+ if (v !== undefined && v !== '') {
135
+ return String(v);
136
+ }
137
+ }
138
+ }
139
+ catch {
140
+ /* ignore */
139
141
  }
140
- } catch (_) {}
141
- return undefined
142
+ return undefined;
142
143
  }
143
-
144
- /**
145
- * @param {string} viteStyleKey
146
- * @returns {string|null}
147
- */
148
144
  function viteKeyToPluginAlternate(viteStyleKey) {
149
- if (typeof viteStyleKey !== 'string' || !viteStyleKey.startsWith('VITE_')) {
150
- return null
151
- }
152
- return `PLUGIN_${viteStyleKey.slice(5)}`
145
+ if (typeof viteStyleKey !== 'string' || !viteStyleKey.startsWith('VITE_')) {
146
+ return null;
147
+ }
148
+ return `PLUGIN_${viteStyleKey.slice(5)}`;
153
149
  }
154
-
155
- /**
156
- * @param {string} key
157
- * @param {string} [fallback='']
158
- */
159
150
  function resolveBundledEnv(key, fallback = '') {
160
- const alt = viteKeyToPluginAlternate(key);
161
- const inj = readInjectedEnvKey(key);
162
- const fromInjected =
163
- inj === undefined || inj === null ? (alt ? readInjectedEnvKey(alt) : undefined) : inj;
164
- const proc = readProcessEnv(key);
165
- const fromProcess =
166
- proc === undefined || proc === null ? (alt ? readProcessEnv(alt) : undefined) : proc;
167
- const first = fromInjected === undefined || fromInjected === null ? fromProcess : fromInjected;
168
- return first === undefined || first === null ? fallback : first
151
+ const alt = viteKeyToPluginAlternate(key);
152
+ const inj = readInjectedEnvKey(key);
153
+ const fromInjected = inj === undefined || inj === null ? (alt ? readInjectedEnvKey(alt) : undefined) : inj;
154
+ const proc = readProcessEnv(key);
155
+ const fromProcess = proc === undefined || proc === null ? (alt ? readProcessEnv(alt) : undefined) : proc;
156
+ const first = fromInjected === undefined || fromInjected === null ? fromProcess : fromInjected;
157
+ return first === undefined || first === null ? fallback : first;
169
158
  }
170
-
171
- /**
172
- * @returns {boolean}
173
- */
174
159
  function resolveBundledIsDev() {
175
- try {
176
- if (readInjectedEnvDev()) {
177
- return true
160
+ try {
161
+ if (readInjectedEnvDev()) {
162
+ return true;
163
+ }
164
+ }
165
+ catch {
166
+ /* ignore */
178
167
  }
179
- } catch (_) {}
180
- try {
181
- if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'development') {
182
- return true
168
+ try {
169
+ if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'development') {
170
+ return true;
171
+ }
183
172
  }
184
- } catch (_) {}
185
- return false
173
+ catch {
174
+ /* ignore */
175
+ }
176
+ return false;
186
177
  }
187
178
 
188
179
  /**
189
- * 路径与脚本主机名校验工具。
190
- * @module runtime/path-host-utils
180
+ * 清单模式与静态清单 URL 配置解析(显式 options + VITE_* / PLUGIN_* 环境)。
191
181
  */
182
+ const EK$1 = webExtendPluginEnvKeys;
183
+ function resolveManifestModeFromInputs(userMode) {
184
+ if (userMode !== undefined && userMode !== null && String(userMode).trim() !== '') {
185
+ const s = String(userMode).trim().toLowerCase();
186
+ if (s === 'static' || s === 'api') {
187
+ return s;
188
+ }
189
+ }
190
+ const e = resolveBundledEnv(EK$1.manifestMode, '');
191
+ if (e) {
192
+ const s = String(e).trim().toLowerCase();
193
+ if (s === 'static' || s === 'api') {
194
+ return s;
195
+ }
196
+ }
197
+ return defaultManifestMode;
198
+ }
199
+ function resolveStaticManifestUrlFromInputs(userUrl) {
200
+ if (userUrl !== undefined && userUrl !== null) {
201
+ const s = String(userUrl).trim();
202
+ if (s !== '') {
203
+ return s;
204
+ }
205
+ }
206
+ return String(resolveBundledEnv(EK$1.staticManifestUrl, '') || '').trim();
207
+ }
192
208
 
193
209
  /**
194
- * @param {string} p
210
+ * 路径与脚本主机名校验工具。
195
211
  */
196
212
  function ensureLeadingPath(p) {
197
- const t = String(p || '').trim();
198
- if (!t) {
199
- return '/'
200
- }
201
- return t.startsWith('/') ? t : `/${t}`
213
+ const t = String(p || '').trim();
214
+ if (!t) {
215
+ return '/';
216
+ }
217
+ return t.startsWith('/') ? t : `/${t}`;
202
218
  }
203
-
204
- /**
205
- * @param {string} hostname
206
- */
207
219
  function normalizeHost(hostname) {
208
- if (!hostname) {
209
- return ''
210
- }
211
- const h = hostname.toLowerCase();
212
- if (h.startsWith('[') && h.endsWith(']')) {
213
- return h.slice(1, -1)
214
- }
215
- return h
220
+ if (!hostname) {
221
+ return '';
222
+ }
223
+ const h = hostname.toLowerCase();
224
+ if (h.startsWith('[') && h.endsWith(']')) {
225
+ return h.slice(1, -1);
226
+ }
227
+ return h;
216
228
  }
217
-
218
- /**
219
- * @param {string[]} hostnames
220
- * @returns {Set<string>}
221
- */
222
229
  function buildAllowedScriptHostsSet(hostnames) {
223
- const s = new Set();
224
- for (const h of hostnames) {
225
- const n = normalizeHost(h);
226
- if (n) {
227
- s.add(n);
230
+ const s = new Set();
231
+ for (const h of hostnames) {
232
+ const n = normalizeHost(h);
233
+ if (n) {
234
+ s.add(n);
235
+ }
228
236
  }
229
- }
230
- return s
237
+ return s;
231
238
  }
232
-
233
- /**
234
- * @param {string} url
235
- * @param {Set<string>} hostSet
236
- */
237
239
  function isScriptHostAllowed(url, hostSet) {
238
- if (typeof window === 'undefined') {
239
- return false
240
- }
241
- try {
242
- const u = new URL(url, window.location.origin);
243
- const h = normalizeHost(u.hostname);
244
- return hostSet.has(h)
245
- } catch {
246
- return false
247
- }
240
+ if (typeof window === 'undefined') {
241
+ return false;
242
+ }
243
+ try {
244
+ const u = new URL(url, window.location.origin);
245
+ const h = normalizeHost(u.hostname);
246
+ return hostSet.has(h);
247
+ }
248
+ catch {
249
+ return false;
250
+ }
248
251
  }
249
252
 
250
253
  /**
251
254
  * 合并用户、环境与默认配置得到运行时选项。
252
- * @module runtime/resolve-runtime-options
253
255
  */
254
-
255
-
256
256
  const DEF = defaultWebExtendPluginRuntime;
257
-
258
- /**
259
- * @param {string|undefined} userVal
260
- * @param {string} envKey
261
- * @param {RequestCredentials} fallback
262
- */
257
+ const EK = webExtendPluginEnvKeys;
263
258
  function resolveManifestCredentials(userVal, envKey, fallback) {
264
- if (userVal !== undefined && userVal !== '') {
265
- const s = String(userVal);
266
- if (s === 'include' || s === 'omit' || s === 'same-origin') {
267
- return s
259
+ if (userVal !== undefined) {
260
+ const s = String(userVal);
261
+ if (s === 'include' || s === 'omit' || s === 'same-origin') {
262
+ return s;
263
+ }
264
+ }
265
+ const e = resolveBundledEnv(envKey, '');
266
+ if (e === 'include' || e === 'omit' || e === 'same-origin') {
267
+ return e;
268
268
  }
269
- }
270
- const e = resolveBundledEnv(envKey, '');
271
- if (e === 'include' || e === 'omit' || e === 'same-origin') {
272
- return e
273
- }
274
- return fallback
269
+ return fallback;
275
270
  }
276
-
277
- /**
278
- * @param {number|undefined} userVal
279
- * @param {string} envKey
280
- * @param {number} fallback
281
- */
282
271
  function resolvePositiveInt(userVal, envKey, fallback) {
283
- if (typeof userVal === 'number' && Number.isFinite(userVal) && userVal > 0) {
284
- return Math.floor(userVal)
285
- }
286
- const raw = resolveBundledEnv(envKey, '');
287
- const n = raw ? parseInt(raw, 10) : NaN;
288
- if (Number.isFinite(n) && n > 0) {
289
- return n
290
- }
291
- return fallback
272
+ if (typeof userVal === 'number' && Number.isFinite(userVal) && userVal > 0) {
273
+ return Math.floor(userVal);
274
+ }
275
+ const raw = resolveBundledEnv(envKey, '');
276
+ const n = raw ? parseInt(raw, 10) : NaN;
277
+ if (Number.isFinite(n) && n > 0) {
278
+ return n;
279
+ }
280
+ return fallback;
292
281
  }
293
-
294
- /**
295
- * 合并用户、环境变量与 `defaultWebExtendPluginRuntime`,得到完整运行时选项(宿主可只传需要覆盖的字段)。
296
- * @param {WebExtendPluginRuntimeOptions} [user]
297
- * @returns {object}
298
- */
282
+ /** 合并用户、环境变量与 `defaultWebExtendPluginRuntime`,得到完整运行时选项(宿主可只传需要覆盖的字段)。 */
283
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
299
284
  function resolveRuntimeOptions$1(user = {}) {
300
- const manifestBaseRaw =
301
- user.manifestBase !== undefined && user.manifestBase !== ''
302
- ? String(user.manifestBase)
303
- : resolveBundledEnv('VITE_FRONTEND_PLUGIN_BASE', DEF.manifestBase) || DEF.manifestBase;
304
-
305
- const manifestListPath = ensureLeadingPath(
306
- user.manifestListPath !== undefined && user.manifestListPath !== ''
307
- ? user.manifestListPath
308
- : resolveBundledEnv('VITE_WEB_PLUGIN_MANIFEST_PATH', DEF.manifestListPath)
309
- );
310
-
311
- const defaultImplicitDevPluginIds = Array.isArray(user.defaultImplicitDevPluginIds)
312
- ? user.defaultImplicitDevPluginIds.map(String).filter(Boolean)
313
- : (() => {
314
- const e = resolveBundledEnv('VITE_WEB_PLUGIN_IMPLICIT_DEV_IDS', '');
315
- if (e) {
316
- return e
317
- .split(',')
318
- .map((s) => s.trim())
319
- .filter(Boolean)
320
- }
321
- return [...DEF.defaultImplicitDevPluginIds]
322
- })();
323
-
324
- const allowedScriptHosts =
325
- Array.isArray(user.allowedScriptHosts) && user.allowedScriptHosts.length > 0
326
- ? user.allowedScriptHosts.map((h) => normalizeHost(String(h))).filter(Boolean)
327
- : (() => {
328
- const e = resolveBundledEnv('VITE_WEB_PLUGIN_ALLOWED_SCRIPT_HOSTS', '');
329
- if (e) {
330
- return e
331
- .split(',')
332
- .map((s) => normalizeHost(s.trim()))
333
- .filter(Boolean)
334
- }
335
- return [...DEF.allowedScriptHosts]
285
+ const manifestBaseRaw = user.manifestBase !== undefined && user.manifestBase !== ''
286
+ ? String(user.manifestBase)
287
+ : resolveBundledEnv(EK.manifestBase, DEF.manifestBase) || DEF.manifestBase;
288
+ const manifestListPath = ensureLeadingPath(user.manifestListPath !== undefined && user.manifestListPath !== ''
289
+ ? user.manifestListPath
290
+ : resolveBundledEnv(EK.manifestListPath, DEF.manifestListPath));
291
+ const defaultImplicitDevPluginIds = Array.isArray(user.defaultImplicitDevPluginIds)
292
+ ? user.defaultImplicitDevPluginIds.map(String).filter(Boolean)
293
+ : (() => {
294
+ const e = resolveBundledEnv(EK.implicitDevIds, '');
295
+ if (e) {
296
+ return e
297
+ .split(',')
298
+ .map((s) => s.trim())
299
+ .filter(Boolean);
300
+ }
301
+ return [...DEF.defaultImplicitDevPluginIds];
336
302
  })();
337
-
338
- const bridgeAllowedPathPrefixes =
339
- Array.isArray(user.bridgeAllowedPathPrefixes) && user.bridgeAllowedPathPrefixes.length > 0
340
- ? user.bridgeAllowedPathPrefixes.map((p) => ensureLeadingPath(p)).filter(Boolean)
341
- : (() => {
342
- const e = resolveBundledEnv('VITE_WEB_PLUGIN_BRIDGE_PREFIXES', '');
343
- if (e) {
344
- return e
345
- .split(',')
346
- .map((s) => ensureLeadingPath(s.trim()))
347
- .filter(Boolean)
348
- }
349
- return [...DEF.bridgeAllowedPathPrefixes]
303
+ const allowedScriptHosts = Array.isArray(user.allowedScriptHosts) && user.allowedScriptHosts.length > 0
304
+ ? user.allowedScriptHosts.map((h) => normalizeHost(String(h))).filter(Boolean)
305
+ : (() => {
306
+ const e = resolveBundledEnv(EK.allowedScriptHosts, '');
307
+ if (e) {
308
+ return e
309
+ .split(',')
310
+ .map((s) => normalizeHost(s.trim()))
311
+ .filter(Boolean);
312
+ }
313
+ return [...DEF.allowedScriptHosts];
350
314
  })();
351
-
352
- return {
353
- manifestBase: manifestBaseRaw.replace(/\/$/, '') || DEF.manifestBase.replace(/\/$/, ''),
354
- manifestListPath,
355
- manifestFetchCredentials: resolveManifestCredentials(
356
- user.manifestFetchCredentials,
357
- 'VITE_WEB_PLUGIN_MANIFEST_CREDENTIALS',
358
- DEF.manifestFetchCredentials
359
- ),
360
- isDev: user.isDev !== undefined ? user.isDev : resolveBundledIsDev(),
361
- webPluginDevOrigin:
362
- user.webPluginDevOrigin !== undefined ? user.webPluginDevOrigin : resolveBundledEnv('VITE_WEB_PLUGIN_DEV_ORIGIN', ''),
363
- webPluginDevIds:
364
- user.webPluginDevIds !== undefined ? user.webPluginDevIds : resolveBundledEnv('VITE_WEB_PLUGIN_DEV_IDS', ''),
365
- webPluginDevMapJson:
366
- user.webPluginDevMapJson !== undefined
367
- ? user.webPluginDevMapJson
368
- : resolveBundledEnv('VITE_WEB_PLUGIN_DEV_MAP', ''),
369
- webPluginDevEntryPath: ensureLeadingPath(
370
- user.webPluginDevEntryPath !== undefined && user.webPluginDevEntryPath !== ''
371
- ? user.webPluginDevEntryPath
372
- : resolveBundledEnv('VITE_WEB_PLUGIN_DEV_ENTRY', DEF.webPluginDevEntryPath)
373
- ),
374
- devPingPath: ensureLeadingPath(
375
- user.devPingPath !== undefined && user.devPingPath !== ''
376
- ? user.devPingPath
377
- : resolveBundledEnv('VITE_WEB_PLUGIN_DEV_PING_PATH', DEF.devPingPath)
378
- ),
379
- devReloadSsePath: ensureLeadingPath(
380
- user.devReloadSsePath !== undefined && user.devReloadSsePath !== ''
381
- ? user.devReloadSsePath
382
- : resolveBundledEnv('VITE_WEB_PLUGIN_DEV_SSE_PATH', DEF.devReloadSsePath)
383
- ),
384
- devPingTimeoutMs: resolvePositiveInt(user.devPingTimeoutMs, 'VITE_WEB_PLUGIN_DEV_PING_TIMEOUT_MS', DEF.devPingTimeoutMs),
385
- defaultImplicitDevPluginIds,
386
- allowedScriptHosts,
387
- bridgeAllowedPathPrefixes,
388
- bootstrapSummary: user.bootstrapSummary,
389
- ...(typeof user.fetchManifest === 'function' ? { fetchManifest: user.fetchManifest } : {})
390
- }
315
+ const bridgeAllowedPathPrefixes = Array.isArray(user.bridgeAllowedPathPrefixes) && user.bridgeAllowedPathPrefixes.length > 0
316
+ ? user.bridgeAllowedPathPrefixes.map((p) => ensureLeadingPath(p)).filter(Boolean)
317
+ : (() => {
318
+ const e = resolveBundledEnv(EK.bridgePrefixes, '');
319
+ if (e) {
320
+ return e
321
+ .split(',')
322
+ .map((s) => ensureLeadingPath(s.trim()))
323
+ .filter(Boolean);
324
+ }
325
+ return [...DEF.bridgeAllowedPathPrefixes];
326
+ })();
327
+ const manifestMode = resolveManifestModeFromInputs(user.manifestMode);
328
+ const staticManifestUrl = resolveStaticManifestUrlFromInputs(user.staticManifestUrl);
329
+ const isDevResolved = user.isDev !== undefined ? user.isDev : resolveBundledIsDev();
330
+ const devFallbackStaticManifestUrl = (() => {
331
+ if (user.devFallbackStaticManifestUrl !== undefined && String(user.devFallbackStaticManifestUrl).trim() !== '') {
332
+ return String(user.devFallbackStaticManifestUrl).trim();
333
+ }
334
+ const e = resolveBundledEnv(EK.devFallbackManifestUrl, '');
335
+ if (e) {
336
+ return e.trim();
337
+ }
338
+ return String(DEF.devFallbackStaticManifestUrl).trim();
339
+ })();
340
+ const hostLayoutComponent = user.hostLayoutComponent;
341
+ const pluginRoutesParentName = (() => {
342
+ if (user.pluginRoutesParentName !== undefined) {
343
+ return String(user.pluginRoutesParentName).trim();
344
+ }
345
+ if (hostLayoutComponent != null) {
346
+ return String(DEF.pluginRoutesParentName).trim();
347
+ }
348
+ return '';
349
+ })();
350
+ const pluginMountRaw = user.pluginMountPath !== undefined && String(user.pluginMountPath).trim() !== ''
351
+ ? String(user.pluginMountPath).trim()
352
+ : String(resolveBundledEnv(EK.mountPath, '') || DEF.pluginMountPath).trim();
353
+ const pluginMountPath = ensureLeadingPath(pluginMountRaw || DEF.pluginMountPath);
354
+ const pluginHostRouteMeta = user.pluginHostRouteMeta !== undefined && user.pluginHostRouteMeta !== null
355
+ ? user.pluginHostRouteMeta
356
+ : undefined;
357
+ const ensurePluginHostRoute = user.ensurePluginHostRoute !== false;
358
+ const devManifestFallback = (() => {
359
+ if (manifestMode === 'static') {
360
+ return false;
361
+ }
362
+ if (user.devManifestFallback === false) {
363
+ return false;
364
+ }
365
+ if (user.devManifestFallback === true) {
366
+ return true;
367
+ }
368
+ const envFlag = resolveBundledEnv(EK.devManifestFallback, '');
369
+ if (envFlag === '0' || envFlag === 'false') {
370
+ return false;
371
+ }
372
+ if (envFlag === '1' || envFlag === 'true') {
373
+ return true;
374
+ }
375
+ return !!isDevResolved;
376
+ })();
377
+ return {
378
+ manifestBase: manifestBaseRaw.replace(/\/$/, '') || DEF.manifestBase.replace(/\/$/, ''),
379
+ manifestListPath,
380
+ manifestMode,
381
+ staticManifestUrl,
382
+ devManifestFallback,
383
+ devFallbackStaticManifestUrl,
384
+ manifestFetchCredentials: resolveManifestCredentials(user.manifestFetchCredentials, EK.manifestCredentials, DEF.manifestFetchCredentials),
385
+ isDev: isDevResolved,
386
+ webPluginDevOrigin: user.webPluginDevOrigin !== undefined
387
+ ? user.webPluginDevOrigin
388
+ : resolveBundledEnv(EK.webPluginDevOrigin, ''),
389
+ webPluginDevIds: user.webPluginDevIds !== undefined ? user.webPluginDevIds : resolveBundledEnv(EK.webPluginDevIds, ''),
390
+ webPluginDevMapJson: user.webPluginDevMapJson !== undefined
391
+ ? user.webPluginDevMapJson
392
+ : resolveBundledEnv(EK.webPluginDevMap, ''),
393
+ webPluginDevEntryPath: ensureLeadingPath(user.webPluginDevEntryPath !== undefined && user.webPluginDevEntryPath !== ''
394
+ ? user.webPluginDevEntryPath
395
+ : resolveBundledEnv(EK.devEntry, DEF.webPluginDevEntryPath)),
396
+ devPingPath: ensureLeadingPath(user.devPingPath !== undefined && user.devPingPath !== ''
397
+ ? user.devPingPath
398
+ : resolveBundledEnv(EK.devPing, DEF.devPingPath)),
399
+ devReloadSsePath: ensureLeadingPath(user.devReloadSsePath !== undefined && user.devReloadSsePath !== ''
400
+ ? user.devReloadSsePath
401
+ : resolveBundledEnv(EK.devSse, DEF.devReloadSsePath)),
402
+ devPingTimeoutMs: resolvePositiveInt(user.devPingTimeoutMs, EK.devPingTimeout, DEF.devPingTimeoutMs),
403
+ defaultImplicitDevPluginIds,
404
+ allowedScriptHosts,
405
+ bridgeAllowedPathPrefixes,
406
+ bootstrapSummary: user.bootstrapSummary,
407
+ hostLayoutComponent,
408
+ pluginMountPath,
409
+ pluginHostRouteMeta,
410
+ ensurePluginHostRoute,
411
+ pluginRoutesParentName,
412
+ ...(typeof user.fetchManifest === 'function' ? { fetchManifest: user.fetchManifest } : {}),
413
+ ...(typeof user.transformRoutes === 'function' ? { transformRoutes: user.transformRoutes } : {}),
414
+ ...(typeof user.interceptRegisterRoutes === 'function'
415
+ ? { interceptRegisterRoutes: user.interceptRegisterRoutes }
416
+ : {}),
417
+ ...(typeof user.adaptRouteDeclarations === 'function'
418
+ ? { adaptRouteDeclarations: user.adaptRouteDeclarations }
419
+ : {}),
420
+ ...(typeof user.applyPluginMenuItems === 'function'
421
+ ? { applyPluginMenuItems: user.applyPluginMenuItems }
422
+ : {}),
423
+ ...(typeof user.revokePluginMenuItems === 'function'
424
+ ? { revokePluginMenuItems: user.revokePluginMenuItems }
425
+ : {}),
426
+ ...(user.hostContext !== undefined &&
427
+ user.hostContext !== null &&
428
+ typeof user.hostContext === 'object' &&
429
+ !Array.isArray(user.hostContext)
430
+ ? { hostContext: user.hostContext }
431
+ : {}),
432
+ ...(typeof user.onBeforePluginActivate === 'function'
433
+ ? { onBeforePluginActivate: user.onBeforePluginActivate }
434
+ : {}),
435
+ ...(typeof user.onAfterPluginActivate === 'function'
436
+ ? { onAfterPluginActivate: user.onAfterPluginActivate }
437
+ : {}),
438
+ ...(typeof user.onPluginActivateError === 'function'
439
+ ? { onPluginActivateError: user.onPluginActivateError }
440
+ : {})
441
+ };
391
442
  }
392
443
 
393
444
  /**
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>}
445
+ * 将裸 `{ plugins }``{ code, data: { plugins } }` 式响应解包为清单对象。
416
446
  */
447
+ function unwrapNestedManifestBody(body) {
448
+ if (!body || typeof body !== 'object') {
449
+ return null;
450
+ }
451
+ const o = body;
452
+ if (Array.isArray(o.plugins)) {
453
+ return o;
454
+ }
455
+ const d = o.data;
456
+ if (d && typeof d === 'object') {
457
+ const inner = d;
458
+ if (Array.isArray(inner.plugins)) {
459
+ return inner;
460
+ }
461
+ if ('plugins' in inner) {
462
+ return inner;
463
+ }
464
+ }
465
+ return d !== undefined && d !== null && typeof d === 'object' ? d : o;
466
+ }
417
467
 
418
- /**
419
- * 默认清单请求(未配置 `fetchManifest` 时)。
420
- * @param {FetchWebPluginManifestContext} ctx
421
- * @returns {Promise<FetchWebPluginManifestResult>}
422
- */
423
468
  async function defaultFetchWebPluginManifest$1(ctx) {
424
- const { manifestUrl, credentials } = ctx;
425
- try {
426
- const res = await fetch(manifestUrl, { credentials });
427
- if (!res.ok) {
428
- return { ok: false, status: res.status, data: null }
469
+ const { manifestUrl, credentials } = ctx;
470
+ try {
471
+ const res = await fetch(manifestUrl, { credentials });
472
+ if (!res.ok) {
473
+ return { ok: false, status: res.status, data: null };
474
+ }
475
+ const body = await res.json();
476
+ const data = unwrapNestedManifestBody(body);
477
+ if (!data || typeof data !== 'object') {
478
+ return {
479
+ ok: false,
480
+ error: new Error('[wep] invalid manifest response body'),
481
+ data: null
482
+ };
483
+ }
484
+ return { ok: true, data };
485
+ }
486
+ catch (e) {
487
+ return { ok: false, error: e, data: null };
429
488
  }
430
- const data = await res.json();
431
- return { ok: true, data }
432
- } catch (e) {
433
- return { ok: false, error: e, data: null }
434
- }
435
489
  }
436
490
 
437
491
  function getDefaultExportFromCjs (x) {
@@ -3159,1349 +3213,1349 @@ function requireSemver () {
3159
3213
  var semverExports = requireSemver();
3160
3214
  var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
3161
3215
 
3162
- /**
3163
- * 与 `plugin-web-starter`(WebPluginsResponse)返回的 `hostPluginApiVersion` 保持一致,用于契约校验。
3164
- * @type {string}
3165
- */
3166
- const HOST_PLUGIN_API_VERSION = '1.0.0';
3167
-
3168
3216
  /**
3169
- * 开发模式插件 URL 映射(显式 JSON + 隐式 dev 探测)。
3170
- * @module runtime/dev-map
3217
+ * `disposeWebPlugin` 使用的菜单撤销钩子:在 `bootstrapPlugins` 中用 `resolveRuntimeOptions` 结果注册。
3171
3218
  */
3219
+ let revokePluginMenuItems;
3220
+ function setRevokePluginMenuItems(fn) {
3221
+ revokePluginMenuItems = fn;
3222
+ }
3223
+ function revokePluginMenusIfConfigured(pluginId) {
3224
+ if (typeof revokePluginMenuItems === 'function') {
3225
+ try {
3226
+ revokePluginMenuItems(pluginId);
3227
+ }
3228
+ catch (e) {
3229
+ console.warn('[wep] revokePluginMenuItems failed', pluginId, e);
3230
+ }
3231
+ }
3232
+ }
3172
3233
 
3173
3234
  /**
3174
- * @param {{ isDev: boolean, webPluginDevMapJson?: string|null }} opts
3235
+ * 开发模式插件 URL 映射(显式 JSON + 隐式 dev 探测)。
3175
3236
  */
3176
3237
  function parseWebPluginDevMapExplicit(opts) {
3177
- if (!opts.isDev) {
3178
- return null
3179
- }
3180
- const raw = opts.webPluginDevMapJson;
3181
- if (raw === undefined || raw === null || String(raw).trim() === '') {
3182
- return null
3183
- }
3184
- try {
3185
- const map = JSON.parse(String(raw));
3186
- return map && typeof map === 'object' ? map : null
3187
- } catch {
3188
- console.warn('[plugins] webPluginDevMapJson / VITE_WEB_PLUGIN_DEV_MAP is not valid JSON');
3189
- return null
3190
- }
3238
+ if (!opts.isDev) {
3239
+ return null;
3240
+ }
3241
+ const raw = opts.webPluginDevMapJson;
3242
+ if (raw === undefined || raw === null || String(raw).trim() === '') {
3243
+ return null;
3244
+ }
3245
+ try {
3246
+ const map = JSON.parse(String(raw));
3247
+ return map && typeof map === 'object' ? map : null;
3248
+ }
3249
+ catch {
3250
+ console.warn('[wep] invalid webPluginDevMapJson / VITE_WEB_PLUGIN_DEV_MAP');
3251
+ return null;
3252
+ }
3191
3253
  }
3192
-
3193
- /**
3194
- * @param {Record<string, string>} implicit
3195
- * @param {Record<string, string>|null} explicit
3196
- */
3197
3254
  function mergeDevMaps(implicit, explicit) {
3198
- const i = implicit && typeof implicit === 'object' ? implicit : {};
3199
- const e = explicit && typeof explicit === 'object' ? explicit : {};
3200
- return { ...i, ...e }
3255
+ const i = implicit && typeof implicit === 'object' ? implicit : {};
3256
+ const e = explicit && typeof explicit === 'object' ? explicit : {};
3257
+ return { ...i, ...e };
3201
3258
  }
3202
-
3203
- /**
3204
- * @param {object} opts
3205
- * @param {boolean} opts.isDev
3206
- * @param {string|undefined|null} opts.webPluginDevOrigin
3207
- * @param {string|undefined|null} opts.webPluginDevIds
3208
- * @param {string[]} opts.defaultImplicitDevPluginIds
3209
- * @param {string} opts.devPingPath
3210
- * @param {number} opts.devPingTimeoutMs
3211
- * @param {string} opts.webPluginDevEntryPath
3212
- * @param {Set<string>} hostSet
3213
- */
3214
3259
  async function buildImplicitWebPluginDevMap(opts, hostSet) {
3215
- if (!opts.isDev) {
3216
- return {}
3217
- }
3218
- const origin =
3219
- opts.webPluginDevOrigin === undefined || opts.webPluginDevOrigin === null
3220
- ? ''
3221
- : String(opts.webPluginDevOrigin).trim();
3222
- if (!origin) {
3223
- return {}
3224
- }
3225
- if (!isScriptHostAllowed(`${origin}/`, hostSet)) {
3226
- return {}
3227
- }
3228
-
3229
- const idsRaw = opts.webPluginDevIds;
3230
- const ids =
3231
- idsRaw !== undefined && idsRaw !== null && String(idsRaw).trim() !== ''
3232
- ? String(idsRaw)
3233
- .split(',')
3234
- .map((s) => s.trim())
3235
- .filter(Boolean)
3236
- : [...opts.defaultImplicitDevPluginIds];
3237
-
3238
- if (ids.length === 0) {
3239
- return {}
3240
- }
3241
-
3242
- const base = origin.replace(/\/$/, '');
3243
- const pingUrl = `${base}${opts.devPingPath}`;
3244
- try {
3245
- const ctrl = new AbortController();
3246
- const timer = setTimeout(() => ctrl.abort(), opts.devPingTimeoutMs);
3247
- const r = await fetch(pingUrl, {
3248
- mode: 'cors',
3249
- cache: 'no-store',
3250
- signal: ctrl.signal
3251
- });
3252
- clearTimeout(timer);
3253
- if (!r.ok) {
3254
- return {}
3260
+ if (!opts.isDev) {
3261
+ return {};
3262
+ }
3263
+ const origin = opts.webPluginDevOrigin === undefined || opts.webPluginDevOrigin === null
3264
+ ? ''
3265
+ : String(opts.webPluginDevOrigin).trim();
3266
+ if (!origin) {
3267
+ return {};
3268
+ }
3269
+ if (!isScriptHostAllowed(`${origin}/`, hostSet)) {
3270
+ return {};
3271
+ }
3272
+ const idsRaw = opts.webPluginDevIds;
3273
+ const ids = idsRaw !== undefined && idsRaw !== null && String(idsRaw).trim() !== ''
3274
+ ? String(idsRaw)
3275
+ .split(',')
3276
+ .map((s) => s.trim())
3277
+ .filter(Boolean)
3278
+ : [...opts.defaultImplicitDevPluginIds];
3279
+ if (ids.length === 0) {
3280
+ return {};
3281
+ }
3282
+ const base = origin.replace(/\/$/, '');
3283
+ const pingUrl = `${base}${opts.devPingPath}`;
3284
+ try {
3285
+ const ctrl = new AbortController();
3286
+ const timer = setTimeout(() => ctrl.abort(), opts.devPingTimeoutMs);
3287
+ const r = await fetch(pingUrl, {
3288
+ mode: 'cors',
3289
+ cache: 'no-store',
3290
+ signal: ctrl.signal
3291
+ });
3292
+ clearTimeout(timer);
3293
+ if (!r.ok) {
3294
+ return {};
3295
+ }
3296
+ const body = (await r.text()).trim();
3297
+ if (body !== 'ok') {
3298
+ return {};
3299
+ }
3300
+ }
3301
+ catch {
3302
+ return {};
3255
3303
  }
3256
- const body = (await r.text()).trim();
3257
- if (body !== 'ok') {
3258
- return {}
3304
+ const pathPart = opts.webPluginDevEntryPath;
3305
+ const map = {};
3306
+ for (const id of ids) {
3307
+ map[id] = `${base}${pathPart}`;
3259
3308
  }
3260
- } catch {
3261
- return {}
3262
- }
3263
-
3264
- const pathPart = opts.webPluginDevEntryPath;
3265
- const map = {};
3266
- for (const id of ids) {
3267
- map[id] = `${base}${pathPart}`;
3268
- }
3269
- if (ids.length) {
3270
- console.info(
3271
- '[plugins] 已检测到插件 dev 服务(',
3272
- base,
3273
- '),下列 id 将加载隐式 dev 入口(',
3274
- pathPart,
3275
- ')而非清单 dist:',
3276
- ids.join(', ')
3277
- );
3278
- }
3279
- return map
3309
+ if (ids.length) {
3310
+ console.info('[wep] plugin dev server', base, '→ implicit entries', pathPart, ids.join(', '));
3311
+ }
3312
+ return map;
3280
3313
  }
3281
3314
 
3282
3315
  /**
3283
3316
  * 开发模式下插件热更新 SSE(按 dev 映射中的 origin 连接)。
3284
- * @module runtime/dev-reload-sse
3285
3317
  */
3286
-
3287
- /** @type {Map<string, EventSource>} */
3288
3318
  const pluginDevEventSources = new Map();
3289
-
3290
3319
  let pluginDevBeforeUnloadRegistered = false;
3291
-
3292
3320
  function closeAllPluginDevEventSources() {
3293
- for (const es of pluginDevEventSources.values()) {
3294
- try {
3295
- es.close();
3296
- } catch (_) {}
3297
- }
3298
- pluginDevEventSources.clear();
3321
+ for (const es of pluginDevEventSources.values()) {
3322
+ try {
3323
+ es.close();
3324
+ }
3325
+ catch {
3326
+ /* ignore */
3327
+ }
3328
+ }
3329
+ pluginDevEventSources.clear();
3299
3330
  }
3300
-
3301
3331
  function ensurePluginDevBeforeUnload() {
3302
- if (pluginDevBeforeUnloadRegistered || typeof window === 'undefined') {
3303
- return
3304
- }
3305
- pluginDevBeforeUnloadRegistered = true;
3306
- window.addEventListener('beforeunload', closeAllPluginDevEventSources);
3332
+ if (pluginDevBeforeUnloadRegistered || typeof window === 'undefined') {
3333
+ return;
3334
+ }
3335
+ pluginDevBeforeUnloadRegistered = true;
3336
+ window.addEventListener('beforeunload', closeAllPluginDevEventSources);
3307
3337
  }
3308
-
3309
- /**
3310
- * @param {string} origin
3311
- * @param {Set<string>} hostSet
3312
- */
3313
3338
  function isDevOriginAllowedForSse(origin, hostSet) {
3314
- try {
3315
- const u = new URL(origin);
3316
- return hostSet.has(normalizeHost(u.hostname))
3317
- } catch {
3318
- return false
3319
- }
3339
+ try {
3340
+ const u = new URL(origin);
3341
+ return hostSet.has(normalizeHost(u.hostname));
3342
+ }
3343
+ catch {
3344
+ return false;
3345
+ }
3320
3346
  }
3321
-
3322
- /**
3323
- * @param {string} origin
3324
- * @param {boolean} isDev
3325
- * @param {Set<string>} hostSet
3326
- * @param {string} ssePath
3327
- */
3328
3347
  function startPluginDevReloadSse(origin, isDev, hostSet, ssePath) {
3329
- if (!isDev || pluginDevEventSources.has(origin)) {
3330
- return
3331
- }
3332
- if (!isDevOriginAllowedForSse(origin, hostSet)) {
3333
- return
3334
- }
3335
- ensurePluginDevBeforeUnload();
3336
- const base = origin.replace(/\/$/, '');
3337
- const url = `${base}${ssePath}`;
3338
- try {
3339
- const es = new EventSource(url);
3340
- pluginDevEventSources.set(origin, es);
3341
- es.addEventListener('reload', () => {
3342
- window.location.reload();
3343
- });
3344
- es.onopen = () => {
3345
- console.info('[plugins] plugin dev reload SSE:', url);
3346
- };
3347
- } catch (e) {
3348
- console.warn('[plugins] EventSource failed', url, e);
3349
- }
3348
+ if (!isDev || pluginDevEventSources.has(origin)) {
3349
+ return;
3350
+ }
3351
+ if (!isDevOriginAllowedForSse(origin, hostSet)) {
3352
+ return;
3353
+ }
3354
+ ensurePluginDevBeforeUnload();
3355
+ const base = origin.replace(/\/$/, '');
3356
+ const url = `${base}${ssePath}`;
3357
+ try {
3358
+ const es = new EventSource(url);
3359
+ pluginDevEventSources.set(origin, es);
3360
+ es.addEventListener('reload', () => {
3361
+ window.location.reload();
3362
+ });
3363
+ es.onopen = () => {
3364
+ console.info('[wep] dev reload SSE', url);
3365
+ };
3366
+ }
3367
+ catch (e) {
3368
+ console.warn('[wep] EventSource failed', url, e);
3369
+ }
3350
3370
  }
3351
-
3352
- /**
3353
- * @param {Record<string, string>|null|undefined} devMap
3354
- * @param {boolean} isDev
3355
- * @param {Set<string>} hostSet
3356
- * @param {string} ssePath
3357
- */
3358
3371
  function startPluginDevSseForMap(devMap, isDev, hostSet, ssePath) {
3359
- if (!isDev || !devMap || typeof window === 'undefined') {
3360
- return
3361
- }
3362
- const origins = new Set();
3363
- for (const entry of Object.values(devMap)) {
3364
- if (typeof entry !== 'string') {
3365
- continue
3372
+ if (!isDev || !devMap || typeof window === 'undefined') {
3373
+ return;
3366
3374
  }
3367
- const t = entry.trim();
3368
- if (!t) {
3369
- continue
3375
+ const origins = new Set();
3376
+ for (const entry of Object.values(devMap)) {
3377
+ if (typeof entry !== 'string') {
3378
+ continue;
3379
+ }
3380
+ const t = entry.trim();
3381
+ if (!t) {
3382
+ continue;
3383
+ }
3384
+ try {
3385
+ origins.add(new URL(t, window.location.href).origin);
3386
+ }
3387
+ catch {
3388
+ /* skip */
3389
+ }
3370
3390
  }
3371
- try {
3372
- origins.add(new URL(t, window.location.href).origin);
3373
- } catch {
3374
- /* skip */
3391
+ for (const o of origins) {
3392
+ startPluginDevReloadSse(o, isDev, hostSet, ssePath);
3375
3393
  }
3376
- }
3377
- for (const o of origins) {
3378
- startPluginDevReloadSse(o, isDev, hostSet, ssePath);
3379
- }
3380
3394
  }
3381
3395
 
3382
3396
  /**
3383
3397
  * 动态加载脚本(去重与并发合并)。
3384
- * @module runtime/load-script
3385
3398
  */
3386
-
3387
3399
  const loadScriptMemo = new Map();
3400
+ function loadScript(src) {
3401
+ if (typeof document === 'undefined') {
3402
+ return Promise.reject(new Error('loadScript: no document'));
3403
+ }
3404
+ if (loadScriptMemo.has(src)) {
3405
+ return loadScriptMemo.get(src);
3406
+ }
3407
+ const p = new Promise((resolve, reject) => {
3408
+ const scripts = document.getElementsByTagName('script');
3409
+ for (let i = 0; i < scripts.length; i++) {
3410
+ const el = scripts[i];
3411
+ if (el.src === src) {
3412
+ if (el.getAttribute('data-wep-loaded') === 'true') {
3413
+ resolve();
3414
+ return;
3415
+ }
3416
+ el.addEventListener('load', () => {
3417
+ el.setAttribute('data-wep-loaded', 'true');
3418
+ resolve();
3419
+ }, { once: true });
3420
+ el.addEventListener('error', () => reject(new Error('loadScript failed: ' + src)), { once: true });
3421
+ return;
3422
+ }
3423
+ }
3424
+ const s = document.createElement('script');
3425
+ s.async = true;
3426
+ s.src = src;
3427
+ s.onload = () => {
3428
+ s.setAttribute('data-wep-loaded', 'true');
3429
+ resolve();
3430
+ };
3431
+ s.onerror = () => reject(new Error('loadScript failed: ' + src));
3432
+ document.head.appendChild(s);
3433
+ });
3434
+ loadScriptMemo.set(src, p);
3435
+ p.catch(() => loadScriptMemo.delete(src));
3436
+ return p;
3437
+ }
3438
+
3439
+ let _printed = false;
3440
+ /** 在首次引导插件时打印一行运行时标识(非大块 ASCII art)。 */
3441
+ function printRuntimeBannerOnce() {
3442
+ if (_printed) {
3443
+ return;
3444
+ }
3445
+ _printed = true;
3446
+ if (typeof console !== 'undefined' && typeof console.info === 'function') {
3447
+ console.info(`[wep] ${RUNTIME_CONSOLE_LABEL} · host API ${HOST_PLUGIN_API_VERSION}`);
3448
+ }
3449
+ }
3388
3450
 
3389
3451
  /**
3390
- * @param {string} src
3391
- * @returns {Promise<void>}
3452
+ * 浏览器 fetch 静态 JSON 清单(与 defaultFetchWebPluginManifest 相同解包规则)。
3392
3453
  */
3393
- function loadScript(src) {
3394
- if (typeof document === 'undefined') {
3395
- return Promise.reject(new Error('loadScript: no document'))
3396
- }
3397
- if (loadScriptMemo.has(src)) {
3398
- return loadScriptMemo.get(src)
3399
- }
3400
- const p = new Promise((resolve, reject) => {
3401
- const scripts = document.getElementsByTagName('script');
3402
- for (let i = 0; i < scripts.length; i++) {
3403
- const el = scripts[i];
3404
- if (el.src === src) {
3405
- if (el.getAttribute('data-wep-loaded') === 'true') {
3406
- resolve();
3407
- return
3454
+ async function fetchStaticManifestViaHttp(ctx) {
3455
+ const { manifestUrl, credentials } = ctx;
3456
+ try {
3457
+ const creds = credentials === 'omit' ? 'omit' : 'same-origin';
3458
+ const res = await fetch(manifestUrl, {
3459
+ method: 'GET',
3460
+ credentials: creds,
3461
+ cache: 'no-store'
3462
+ });
3463
+ if (!res.ok) {
3464
+ return {
3465
+ ok: false,
3466
+ status: res.status,
3467
+ error: new Error('[wep] static manifest HTTP ' + res.status),
3468
+ data: null
3469
+ };
3408
3470
  }
3409
- el.addEventListener(
3410
- 'load',
3411
- () => {
3412
- el.setAttribute('data-wep-loaded', 'true');
3413
- resolve();
3414
- },
3415
- { once: true }
3416
- );
3417
- el.addEventListener('error', () => reject(new Error('loadScript failed: ' + src)), { once: true });
3418
- return
3419
- }
3471
+ const body = await res.json();
3472
+ const data = unwrapNestedManifestBody(body);
3473
+ if (!data || typeof data !== 'object') {
3474
+ return {
3475
+ ok: false,
3476
+ error: new Error('[wep] invalid static manifest JSON'),
3477
+ data: null
3478
+ };
3479
+ }
3480
+ return { ok: true, data };
3481
+ }
3482
+ catch (e) {
3483
+ return { ok: false, error: e, data: null };
3420
3484
  }
3421
- const s = document.createElement('script');
3422
- s.async = true;
3423
- s.src = src;
3424
- s.onload = () => {
3425
- s.setAttribute('data-wep-loaded', 'true');
3426
- resolve();
3427
- };
3428
- s.onerror = () => reject(new Error('loadScript failed: ' + src));
3429
- document.head.appendChild(s);
3430
- });
3431
- loadScriptMemo.set(src, p);
3432
- p.catch(() => loadScriptMemo.delete(src));
3433
- return p
3434
3485
  }
3435
3486
 
3436
3487
  /**
3437
- * 拉取清单、合并 dev 映射、加载入口并执行 activator。
3438
- * @module runtime/bootstrap-plugins
3488
+ * 开箱:在未手工配置时,注册 `/plugin` + 宿主 Layout 的命名父路由,供 `router.addRoute(parentName, child)` 挂载插件页。
3439
3489
  */
3490
+ function routeNameExists(router, name) {
3491
+ if (!name) {
3492
+ return false;
3493
+ }
3494
+ if (typeof router.getRoutes === 'function') {
3495
+ return router.getRoutes().some((r) => r.name === name);
3496
+ }
3497
+ return walkRouteNames(router.options && router.options.routes, name);
3498
+ }
3499
+ function walkRouteNames(routes, name) {
3500
+ if (!Array.isArray(routes)) {
3501
+ return false;
3502
+ }
3503
+ for (const r of routes) {
3504
+ if (r && typeof r === 'object' && r.name === name) {
3505
+ return true;
3506
+ }
3507
+ const ch = r && typeof r === 'object' ? r.children : null;
3508
+ if (walkRouteNames(ch, name)) {
3509
+ return true;
3510
+ }
3511
+ }
3512
+ return false;
3513
+ }
3514
+ function ensurePluginHostRoute$1(router, opts) {
3515
+ if (opts.ensurePluginHostRoute === false) {
3516
+ return;
3517
+ }
3518
+ if (!router || typeof router.addRoute !== 'function') {
3519
+ return;
3520
+ }
3521
+ const parentName = String(opts.pluginRoutesParentName || '').trim();
3522
+ if (!parentName) {
3523
+ return;
3524
+ }
3525
+ if (routeNameExists(router, parentName)) {
3526
+ return;
3527
+ }
3528
+ const Layout = opts.hostLayoutComponent;
3529
+ if (!Layout) {
3530
+ console.warn('[wep] 缺少 hostLayoutComponent,未自动注册插件壳路由;请传入宿主 Layout,或在路由表中自行配置与 pluginRoutesParentName 一致的父路由');
3531
+ return;
3532
+ }
3533
+ const mountDefault = defaultWebExtendPluginRuntime.pluginMountPath;
3534
+ let pathRaw = String(opts.pluginMountPath || mountDefault).trim().replace(/\/$/, '') || mountDefault;
3535
+ if (!pathRaw.startsWith('/')) {
3536
+ pathRaw = `/${pathRaw}`;
3537
+ }
3538
+ const meta = opts.pluginHostRouteMeta && typeof opts.pluginHostRouteMeta === 'object'
3539
+ ? { ...opts.pluginHostRouteMeta }
3540
+ : { requiresConfig: true, hidden: true };
3541
+ router.addRoute({
3542
+ path: pathRaw,
3543
+ name: parentName,
3544
+ component: Layout,
3545
+ redirect: 'noredirect',
3546
+ meta,
3547
+ children: []
3548
+ });
3549
+ }
3440
3550
 
3441
3551
  /**
3442
- * @param {import('./resolve-runtime-options.js').WebExtendPluginRuntimeOptions|object} opts
3552
+ * 宿主通过 `resolveRuntimeOptions({ hostContext })` 注入的只读上下文,
3553
+ * 经浅拷贝 + 浅冻结后挂到 `hostApi.hostContext`,供插件访问 store/i18n 等而不污染 HostApi 顶层命名。
3443
3554
  */
3444
- function shouldShowBootstrapSummary(opts) {
3445
- if (opts.bootstrapSummary === true) {
3446
- return true
3447
- }
3448
- if (opts.bootstrapSummary === false) {
3449
- return false
3450
- }
3451
- const env = resolveBundledEnv('VITE_PLUGINS_BOOTSTRAP_SUMMARY', '');
3452
- if (env === '0' || env === 'false') {
3453
- return false
3454
- }
3455
- if (env === '1' || env === 'true') {
3456
- return true
3457
- }
3458
- return resolveBundledIsDev()
3555
+ function freezeShallowHostContext(input) {
3556
+ if (input == null || typeof input !== 'object' || Array.isArray(input)) {
3557
+ return Object.freeze({});
3558
+ }
3559
+ return Object.freeze({ ...input });
3459
3560
  }
3460
3561
 
3461
3562
  /**
3462
- * @param {{ id: string }} p
3463
- * @param {string} [entryUrl]
3464
- * @param {Record<string, string>|null|undefined} devMap
3465
- * @param {Set<string>} hostSet
3563
+ * 将静态清单配置路径解析为用于 fetch 的绝对 URL(浏览器)。
3466
3564
  */
3467
- async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
3468
- const devEntry = devMap && typeof devMap[p.id] === 'string' ? devMap[p.id].trim() : '';
3469
- if (devEntry) {
3470
- if (!isScriptHostAllowed(devEntry, hostSet)) {
3471
- console.warn('[plugins] dev entry URL not allowed', p.id, devEntry);
3472
- return
3565
+ function resolveStaticManifestUrlForFetch(url, origin) {
3566
+ const u = String(url || '').trim();
3567
+ if (!u) {
3568
+ return '';
3569
+ }
3570
+ if (/^https?:\/\//i.test(u)) {
3571
+ return u;
3572
+ }
3573
+ const o = String(origin || '').trim();
3574
+ if (!o) {
3575
+ return u.startsWith('/') ? u : `/${u}`;
3473
3576
  }
3474
3577
  try {
3475
- await import(
3476
- /* webpackIgnore: true */
3477
- /* @vite-ignore */
3478
- devEntry
3479
- );
3480
- } catch (e) {
3481
- console.warn('[plugins] dev module import failed, try manifest entryUrl', p.id, e);
3482
- if (entryUrl && isScriptHostAllowed(entryUrl, hostSet)) {
3483
- await loadScript(entryUrl);
3484
- }
3485
- return
3578
+ const pathPart = u.startsWith('/') ? u : `/${u}`;
3579
+ return new URL(pathPart, o).href;
3580
+ }
3581
+ catch {
3582
+ return u;
3486
3583
  }
3487
- return
3488
- }
3489
- if (!entryUrl || !isScriptHostAllowed(entryUrl, hostSet)) {
3490
- console.warn('[plugins] skip (entryUrl not allowed)', p.id, entryUrl);
3491
- return
3492
- }
3493
- await loadScript(entryUrl);
3494
3584
  }
3495
3585
 
3496
3586
  /**
3497
- * @param {import('vue-router').default} router
3498
- * @param {(pluginId: string, router: import('vue-router').default, hostKit?: { bridgeAllowedPathPrefixes: string[] }) => object} createHostApiFactory
3499
- * @param {import('./resolve-runtime-options.js').WebExtendPluginRuntimeOptions} [runtimeOptions]
3587
+ * 拉取插件清单、加载入口脚本并调用各插件 `activator`。
3500
3588
  */
3501
- async function bootstrapPlugins$1(router, createHostApiFactory, runtimeOptions) {
3502
- if (typeof window === 'undefined') {
3503
- console.warn('[plugins] bootstrapPlugins skipped: requires browser (window)');
3504
- return
3505
- }
3506
- const opts = resolveRuntimeOptions$1(runtimeOptions || {});
3507
- const base = String(opts.manifestBase).replace(/\/$/, '');
3508
- const manifestUrl = `${base}${opts.manifestListPath}`;
3509
- const hostSet = buildAllowedScriptHostsSet(opts.allowedScriptHosts);
3510
- const explicit = parseWebPluginDevMapExplicit(opts);
3511
-
3512
- const manifestCtx = {
3513
- manifestUrl,
3514
- credentials: opts.manifestFetchCredentials
3515
- };
3516
- const [manifestResult, implicit] = await Promise.all([
3517
- (async () => {
3518
- try {
3519
- if (typeof opts.fetchManifest === 'function') {
3520
- return await opts.fetchManifest(manifestCtx)
3589
+ function shouldShowBootstrapSummary(opts) {
3590
+ if (opts.bootstrapSummary === true) {
3591
+ return true;
3592
+ }
3593
+ if (opts.bootstrapSummary === false) {
3594
+ return false;
3595
+ }
3596
+ const env = resolveBundledEnv(webExtendPluginEnvKeys.pluginsBootstrapSummary, '');
3597
+ if (env === '0' || env === 'false') {
3598
+ return false;
3599
+ }
3600
+ if (env === '1' || env === 'true') {
3601
+ return true;
3602
+ }
3603
+ return resolveBundledIsDev();
3604
+ }
3605
+ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
3606
+ const devEntry = devMap && typeof devMap[p.id] === 'string' ? devMap[p.id].trim() : '';
3607
+ if (devEntry) {
3608
+ if (!isScriptHostAllowed(devEntry, hostSet)) {
3609
+ console.warn('[wep] dev entry URL not allowed', p.id, devEntry);
3610
+ return;
3611
+ }
3612
+ try {
3613
+ await import(
3614
+ /* webpackIgnore: true */
3615
+ /* @vite-ignore */
3616
+ devEntry);
3521
3617
  }
3522
- return await defaultFetchWebPluginManifest$1(manifestCtx)
3523
- } catch (e) {
3524
- return { ok: false, error: e, data: null }
3525
- }
3526
- })(),
3527
- buildImplicitWebPluginDevMap(opts, hostSet)
3528
- ]);
3529
-
3530
- const devMap = mergeDevMaps(implicit, explicit);
3531
- startPluginDevSseForMap(devMap, opts.isDev, hostSet, opts.devReloadSsePath);
3532
-
3533
- const hostKit = { bridgeAllowedPathPrefixes: opts.bridgeAllowedPathPrefixes };
3534
-
3535
- if (!manifestResult.ok) {
3536
- if (manifestResult.error) {
3537
- console.warn('[plugins] fetch manifest failed', manifestResult.error);
3538
- } else {
3539
- console.warn('[plugins] manifest HTTP', manifestResult.status, manifestUrl);
3618
+ catch (e) {
3619
+ console.warn('[wep] dev import failed, trying manifest entryUrl', p.id, e);
3620
+ if (entryUrl && isScriptHostAllowed(entryUrl, hostSet)) {
3621
+ await loadScript(entryUrl);
3622
+ }
3623
+ return;
3624
+ }
3625
+ return;
3540
3626
  }
3541
- if (shouldShowBootstrapSummary(opts)) {
3542
- console.info('[plugins] bootstrap_summary', { ok: false, reason: 'manifest_fetch' });
3627
+ if (!entryUrl || !isScriptHostAllowed(entryUrl, hostSet)) {
3628
+ console.warn('[wep] skip (entryUrl not allowed)', p.id, entryUrl);
3629
+ return;
3543
3630
  }
3544
- return
3545
- }
3546
- /** @type {{ hostPluginApiVersion?: string, plugins?: object[] }} */
3547
- const data = manifestResult.data;
3548
- if (!data) {
3549
- if (shouldShowBootstrapSummary(opts)) {
3550
- console.info('[plugins] bootstrap_summary', { ok: false, reason: 'manifest_empty_body' });
3631
+ await loadScript(entryUrl);
3632
+ }
3633
+ async function bootstrapPlugins$1(
3634
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3635
+ router,
3636
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3637
+ createHostApiFactory, runtimeOptions) {
3638
+ if (typeof window === 'undefined') {
3639
+ console.warn('[wep] bootstrapPlugins skipped (no window)');
3640
+ return;
3551
3641
  }
3552
- return
3553
- }
3554
-
3555
- const apiVer = data.hostPluginApiVersion;
3556
- if (apiVer) {
3557
- const coerced = semver.coerce(apiVer);
3558
- const maj = coerced ? coerced.major : 0;
3559
- const range = `^${maj}.0.0`;
3560
- 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
- );
3642
+ printRuntimeBannerOnce();
3643
+ const opts = resolveRuntimeOptions$1(runtimeOptions || {});
3644
+ setRevokePluginMenuItems(typeof opts.revokePluginMenuItems === 'function' ? opts.revokePluginMenuItems : undefined);
3645
+ ensurePluginHostRoute$1(router, opts);
3646
+ const base = String(opts.manifestBase).replace(/\/$/, '');
3647
+ const isStatic = opts.manifestMode === 'static';
3648
+ let manifestUrl;
3649
+ if (isStatic) {
3650
+ const raw = String(opts.staticManifestUrl || '').trim();
3651
+ if (!raw) {
3652
+ console.warn('[wep] manifestMode=static requires non-empty staticManifestUrl (or env VITE_WEB_PLUGIN_STATIC_MANIFEST_URL)');
3653
+ if (shouldShowBootstrapSummary(opts)) {
3654
+ console.info('[wep] bootstrap_summary', { ok: false, reason: 'static_manifest_url_missing' });
3655
+ }
3656
+ return;
3657
+ }
3658
+ manifestUrl = resolveStaticManifestUrlForFetch(raw, window.location.origin);
3567
3659
  }
3568
- }
3569
-
3570
- window.__PLUGIN_ACTIVATORS__ = window.__PLUGIN_ACTIVATORS__ || {};
3571
-
3572
- const plugins = data.plugins || [];
3573
- if (plugins.length === 0) {
3574
- console.info(
3575
- '[plugins] 清单为空。请检查:① 后端清单服务(plugin-web-starter)是否已接入;② web-plugin.web-plugins-dir 是否指向含各插件子目录及 manifest.json 的路径;③ 浏览器直接访问',
3576
- manifestUrl,
3577
- '是否返回 plugins 条目。'
3578
- );
3579
- }
3580
-
3581
- const summary = {
3582
- manifestCount: plugins.length,
3583
- activated: 0,
3584
- skipEngines: 0,
3585
- skipLoad: 0,
3586
- skipNoActivator: 0,
3587
- activateFail: 0
3588
- };
3589
-
3590
- for (const p of plugins) {
3591
- const range = p.engines && p.engines.host;
3592
- if (range && !semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
3593
- console.warn('[plugins] skip (engines.host)', p.id, range);
3594
- summary.skipEngines++;
3595
- continue
3660
+ else {
3661
+ manifestUrl = `${base}${opts.manifestListPath}`;
3596
3662
  }
3597
- const entryUrl = p.entryUrl;
3598
- try {
3599
- await loadPluginEntry(p, entryUrl, devMap, hostSet);
3600
- } catch (e) {
3601
- console.warn('[plugins] script load failed', p.id, e);
3602
- summary.skipLoad++;
3603
- continue
3663
+ const hostSet = buildAllowedScriptHostsSet(opts.allowedScriptHosts);
3664
+ const explicit = parseWebPluginDevMapExplicit(opts);
3665
+ const manifestCtx = {
3666
+ manifestUrl,
3667
+ credentials: opts.manifestFetchCredentials
3668
+ };
3669
+ const [primaryResult, implicit] = await Promise.all([
3670
+ (async () => {
3671
+ try {
3672
+ if (typeof opts.fetchManifest === 'function') {
3673
+ return await opts.fetchManifest(manifestCtx);
3674
+ }
3675
+ return await defaultFetchWebPluginManifest$1(manifestCtx);
3676
+ }
3677
+ catch (e) {
3678
+ return { ok: false, error: e, data: null };
3679
+ }
3680
+ })(),
3681
+ buildImplicitWebPluginDevMap(opts, hostSet)
3682
+ ]);
3683
+ let manifestResult = primaryResult;
3684
+ let manifestUrlUsed = manifestUrl;
3685
+ if (!isStatic && opts.devManifestFallback && opts.isDev) {
3686
+ const dataObj = primaryResult.ok && primaryResult.data && typeof primaryResult.data === 'object'
3687
+ ? primaryResult.data
3688
+ : null;
3689
+ const plen = dataObj && Array.isArray(dataObj.plugins) ? dataObj.plugins.length : 0;
3690
+ const needFallback = !primaryResult.ok || plen === 0;
3691
+ const fallbackRaw = String(opts.devFallbackStaticManifestUrl || '').trim();
3692
+ if (needFallback && fallbackRaw) {
3693
+ const fallbackUrl = resolveStaticManifestUrlForFetch(fallbackRaw, window.location.origin);
3694
+ const fallbackCtx = {
3695
+ manifestUrl: fallbackUrl,
3696
+ credentials: opts.manifestFetchCredentials
3697
+ };
3698
+ const fr = await fetchStaticManifestViaHttp(fallbackCtx);
3699
+ const fdata = fr.ok && fr.data && typeof fr.data === 'object' ? fr.data : null;
3700
+ const flen = fdata && Array.isArray(fdata.plugins) ? fdata.plugins.length : 0;
3701
+ if (fr.ok && flen > 0) {
3702
+ manifestResult = fr;
3703
+ manifestUrlUsed = fallbackUrl;
3704
+ console.info('[wep] dev manifest fallback', { url: fallbackUrl, plugins: flen });
3705
+ }
3706
+ }
3604
3707
  }
3605
- const activator = window.__PLUGIN_ACTIVATORS__[p.id];
3606
- if (typeof activator !== 'function') {
3607
- console.warn('[plugins] no activator for', p.id);
3608
- summary.skipNoActivator++;
3609
- continue
3708
+ const devMap = mergeDevMaps(implicit, explicit);
3709
+ startPluginDevSseForMap(devMap, opts.isDev, hostSet, opts.devReloadSsePath);
3710
+ const frozenHostContext = freezeShallowHostContext(opts.hostContext !== undefined ? opts.hostContext : undefined);
3711
+ const hostKit = {
3712
+ hostContext: frozenHostContext,
3713
+ bridgeAllowedPathPrefixes: opts.bridgeAllowedPathPrefixes,
3714
+ ...(opts.pluginRoutesParentName ? { pluginRoutesParentName: opts.pluginRoutesParentName } : {}),
3715
+ ...(typeof opts.transformRoutes === 'function' ? { transformRoutes: opts.transformRoutes } : {}),
3716
+ ...(typeof opts.interceptRegisterRoutes === 'function'
3717
+ ? { interceptRegisterRoutes: opts.interceptRegisterRoutes }
3718
+ : {}),
3719
+ ...(typeof opts.adaptRouteDeclarations === 'function'
3720
+ ? { adaptRouteDeclarations: opts.adaptRouteDeclarations }
3721
+ : {}),
3722
+ ...(typeof opts.applyPluginMenuItems === 'function'
3723
+ ? { applyPluginMenuItems: opts.applyPluginMenuItems }
3724
+ : {}),
3725
+ ...(typeof opts.revokePluginMenuItems === 'function'
3726
+ ? { revokePluginMenuItems: opts.revokePluginMenuItems }
3727
+ : {})
3728
+ };
3729
+ if (!manifestResult.ok) {
3730
+ if (manifestResult.error) {
3731
+ console.warn('[wep] fetch manifest failed', manifestResult.error);
3732
+ }
3733
+ else {
3734
+ const label = isStatic ? 'static manifest' : 'manifest HTTP';
3735
+ console.warn(`[wep] ${label}`, manifestResult.status, manifestUrlUsed);
3736
+ }
3737
+ if (shouldShowBootstrapSummary(opts)) {
3738
+ console.info('[wep] bootstrap_summary', { ok: false, reason: 'manifest_fetch' });
3739
+ }
3740
+ return;
3610
3741
  }
3611
- const hostApi = createHostApiFactory(p.id, router, hostKit);
3612
- try {
3613
- activator(hostApi);
3614
- summary.activated++;
3615
- } catch (e) {
3616
- console.error('[plugins] activate failed', p.id, e);
3617
- summary.activateFail++;
3742
+ const data = manifestResult.data;
3743
+ if (!data) {
3744
+ if (shouldShowBootstrapSummary(opts)) {
3745
+ console.info('[wep] bootstrap_summary', { ok: false, reason: 'manifest_empty_body' });
3746
+ }
3747
+ return;
3748
+ }
3749
+ const apiVer = data.hostPluginApiVersion;
3750
+ if (apiVer) {
3751
+ const coerced = semver.coerce(apiVer);
3752
+ const maj = coerced ? coerced.major : 0;
3753
+ const range = `^${maj}.0.0`;
3754
+ if (!semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
3755
+ console.warn('[wep] host API version mismatch', {
3756
+ host: HOST_PLUGIN_API_VERSION,
3757
+ manifest: apiVer
3758
+ });
3759
+ }
3760
+ }
3761
+ window.__PLUGIN_ACTIVATORS__ = window.__PLUGIN_ACTIVATORS__ || {};
3762
+ const plugins = data.plugins || [];
3763
+ if (plugins.length === 0) {
3764
+ const hint = isStatic ? 'check static JSON file and plugins[]' : 'check backend and URL';
3765
+ console.info('[wep] empty plugin manifest — ' + hint, manifestUrlUsed);
3766
+ }
3767
+ const summary = {
3768
+ manifestCount: plugins.length,
3769
+ activated: 0,
3770
+ skipEngines: 0,
3771
+ skipLoad: 0,
3772
+ skipNoActivator: 0,
3773
+ activateFail: 0
3774
+ };
3775
+ for (const p of plugins) {
3776
+ const range = p.engines && p.engines.host;
3777
+ if (range && !semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
3778
+ console.warn('[wep] skip plugin (engines.host)', p.id, range);
3779
+ summary.skipEngines++;
3780
+ continue;
3781
+ }
3782
+ const entryUrl = p.entryUrl;
3783
+ try {
3784
+ await loadPluginEntry(p, entryUrl, devMap, hostSet);
3785
+ }
3786
+ catch (e) {
3787
+ console.warn('[wep] script load failed', p.id, e);
3788
+ summary.skipLoad++;
3789
+ continue;
3790
+ }
3791
+ const activator = window.__PLUGIN_ACTIVATORS__[p.id];
3792
+ if (typeof activator !== 'function') {
3793
+ console.warn('[wep] no activator for', p.id);
3794
+ summary.skipNoActivator++;
3795
+ continue;
3796
+ }
3797
+ const pluginRecord = Object.freeze({ ...p });
3798
+ try {
3799
+ if (typeof opts.onBeforePluginActivate === 'function') {
3800
+ await Promise.resolve(opts.onBeforePluginActivate({
3801
+ pluginId: p.id,
3802
+ router,
3803
+ pluginRecord
3804
+ }));
3805
+ }
3806
+ }
3807
+ catch (e) {
3808
+ console.warn('[wep] activate skipped (onBeforePluginActivate)', p.id, e);
3809
+ summary.activateFail++;
3810
+ continue;
3811
+ }
3812
+ const hostApi = createHostApiFactory(p.id, router, hostKit);
3813
+ try {
3814
+ await Promise.resolve(activator(hostApi, { pluginRecord }));
3815
+ summary.activated++;
3816
+ if (typeof opts.onAfterPluginActivate === 'function') {
3817
+ await Promise.resolve(opts.onAfterPluginActivate({
3818
+ pluginId: p.id,
3819
+ router,
3820
+ pluginRecord,
3821
+ hostApi
3822
+ }));
3823
+ }
3824
+ }
3825
+ catch (e) {
3826
+ console.error('[wep] activate failed', p.id, e);
3827
+ summary.activateFail++;
3828
+ if (typeof opts.onPluginActivateError === 'function') {
3829
+ try {
3830
+ await Promise.resolve(opts.onPluginActivateError({
3831
+ pluginId: p.id,
3832
+ error: e,
3833
+ pluginRecord,
3834
+ hostApi
3835
+ }));
3836
+ }
3837
+ catch (hookErr) {
3838
+ console.warn('[wep] onPluginActivateError hook failed', p.id, hookErr);
3839
+ }
3840
+ }
3841
+ }
3842
+ }
3843
+ if (shouldShowBootstrapSummary(opts)) {
3844
+ console.info('[wep] bootstrap_summary', { ok: true, ...summary });
3618
3845
  }
3619
- }
3620
-
3621
- if (shouldShowBootstrapSummary(opts)) {
3622
- console.info('[plugins] bootstrap_summary', { ok: true, ...summary });
3623
- }
3624
3846
  }
3625
3847
 
3626
3848
  /**
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
3849
+ * 运行时引导相关 API 的聚合导出。
3634
3850
  */
3635
3851
 
3636
3852
  var pluginRuntime = /*#__PURE__*/Object.freeze({
3637
- __proto__: null,
3638
- bootstrapPlugins: bootstrapPlugins$1,
3639
- defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
3640
- resolveRuntimeOptions: resolveRuntimeOptions$1
3853
+ __proto__: null,
3854
+ bootstrapPlugins: bootstrapPlugins$1,
3855
+ defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
3856
+ ensurePluginHostRoute: ensurePluginHostRoute$1,
3857
+ resolveRuntimeOptions: resolveRuntimeOptions$1
3641
3858
  });
3642
3859
 
3643
3860
  /**
3644
3861
  * 清单拉取函数的组合工具:缓存、埋点等以**中间件**形式扩展,不侵入 `bootstrapPlugins` 核心逻辑,
3645
- * 符合第三方依赖「可组合、可替换」约定。契约与 `PluginRuntime` `fetchManifest` 一致。
3646
- *
3647
- * @module runtime/manifest-fetch-composer
3648
- */
3649
-
3650
- /**
3651
- * @typedef {object} FetchWebPluginManifestContext
3652
- * @property {string} manifestUrl
3653
- * @property {RequestCredentials} credentials
3654
- */
3655
-
3656
- /**
3657
- * @typedef {object} FetchWebPluginManifestResult
3658
- * @property {boolean} ok
3659
- * @property {number} [status]
3660
- * @property {{ hostPluginApiVersion?: string, plugins?: object[] }|null} [data]
3661
- * @property {unknown} [error]
3662
- */
3663
-
3664
- /**
3665
- * @callback FetchWebPluginManifestFn
3666
- * @param {FetchWebPluginManifestContext} ctx
3667
- * @returns {Promise<FetchWebPluginManifestResult>}
3668
- */
3669
-
3670
- /**
3671
- * 将内层 `fetchManifest` 包装为带缓存的版本。等价于
3672
- * `composeManifestFetch(inner, manifestFetchCacheMiddleware(options))`。
3673
- *
3674
- * @param {FetchWebPluginManifestFn} inner 内层实现(如预设生成的 `fetchManifest` 或 `defaultFetchWebPluginManifest`)
3675
- * @param {ManifestFetchCacheOptions} [options]
3676
- * @returns {FetchWebPluginManifestFn}
3862
+ * 可组合的 `fetchManifest` 包装;入参/出参与 `resolveRuntimeOptions({ fetchManifest })` 一致。
3677
3863
  */
3678
3864
  function wrapManifestFetchWithCache$1(inner, options = {}) {
3679
- return composeManifestFetch$1(inner, manifestFetchCacheMiddleware$1(options))
3865
+ return composeManifestFetch$1(inner, manifestFetchCacheMiddleware$1(options));
3680
3866
  }
3681
-
3682
- /**
3683
- * @typedef {object} ManifestFetchCacheOptions
3684
- * @property {number} [ttlMs] 缓存时长(毫秒)。`<= 0` 或未传时**不缓存**(直接透传 `inner`)。
3685
- * @property {'memory'|'session'|'local'} [storage='memory'] `session`/`local` 依赖 `JSON.stringify`,仅适合可序列化的 `data`。
3686
- * @property {string} [storageKeyPrefix='wep.manifestFetch.v1'] Web Storage 键前缀(仅 storage 非 memory 时生效)。
3687
- * @property {(ctx: FetchWebPluginManifestContext) => string} [cacheKey] 默认 `manifestUrl + '\0' + credentials`。
3688
- * @property {(result: FetchWebPluginManifestResult) => boolean} [shouldCache] 默认:`ok === true` 且 `data` 非空。
3689
- * @property {number} [maxEntries=50] 仅 `memory`:超过条数时淘汰最久未读条目。
3690
- * @property {() => number} [now] 测试注入时间戳。
3691
- */
3692
-
3693
- /**
3694
- * 清单拉取**中间件工厂**:`next` 为内层 `fetchManifest`。
3695
- *
3696
- * @param {ManifestFetchCacheOptions} options
3697
- * @returns {(next: FetchWebPluginManifestFn) => FetchWebPluginManifestFn}
3698
- */
3699
3867
  function manifestFetchCacheMiddleware$1(options = {}) {
3700
- const ttlMs = typeof options.ttlMs === 'number' && Number.isFinite(options.ttlMs) ? options.ttlMs : 0;
3701
- if (ttlMs <= 0) {
3702
- return (next) => next
3703
- }
3704
-
3705
- const storage = options.storage || 'memory';
3706
- const prefix = options.storageKeyPrefix || 'wep.manifestFetch.v1';
3707
- const maxEntries = typeof options.maxEntries === 'number' && options.maxEntries > 0 ? options.maxEntries : 50;
3708
- const getNow = typeof options.now === 'function' ? options.now : () => Date.now();
3709
- const cacheKeyFn =
3710
- typeof options.cacheKey === 'function'
3711
- ? options.cacheKey
3712
- : (ctx) => `${String(ctx.manifestUrl)}\0${String(ctx.credentials)}`;
3713
-
3714
- const shouldCache =
3715
- typeof options.shouldCache === 'function'
3716
- ? options.shouldCache
3717
- : (r) => !!(r && r.ok === true && r.data != null);
3718
-
3719
- /** @type {Map<string, { expiresAt: number, result: FetchWebPluginManifestResult, lastRead: number }>} */
3720
- const memory = new Map();
3721
-
3722
- function cloneResult(r) {
3723
- try {
3724
- if (typeof structuredClone === 'function') {
3725
- return structuredClone(r)
3726
- }
3727
- } catch (_) {}
3728
- try {
3729
- return JSON.parse(JSON.stringify(r))
3730
- } catch (_) {
3731
- return { ...r, data: r.data }
3732
- }
3733
- }
3734
-
3735
- function touchMemory(key) {
3736
- const e = memory.get(key);
3737
- if (e) {
3738
- e.lastRead = getNow();
3739
- }
3740
- }
3741
-
3742
- function pruneMemory() {
3743
- if (memory.size <= maxEntries) {
3744
- return
3868
+ const ttlMs = typeof options.ttlMs === 'number' && Number.isFinite(options.ttlMs) ? options.ttlMs : 0;
3869
+ if (ttlMs <= 0) {
3870
+ return (next) => next;
3745
3871
  }
3746
- const entries = [...memory.entries()].sort((a, b) => a[1].lastRead - b[1].lastRead);
3747
- const drop = memory.size - maxEntries;
3748
- for (let i = 0; i < drop; i++) {
3749
- memory.delete(entries[i][0]);
3872
+ const storage = options.storage || 'memory';
3873
+ const prefix = options.storageKeyPrefix || defaultManifestFetchCache.storageKeyPrefix;
3874
+ const maxEntries = typeof options.maxEntries === 'number' && options.maxEntries > 0
3875
+ ? options.maxEntries
3876
+ : defaultManifestFetchCache.maxEntries;
3877
+ const getNow = typeof options.now === 'function' ? options.now : () => Date.now();
3878
+ const cacheKeyFn = typeof options.cacheKey === 'function'
3879
+ ? options.cacheKey
3880
+ : (ctx) => `${String(ctx.manifestUrl)}\0${String(ctx.credentials)}`;
3881
+ const shouldCache = typeof options.shouldCache === 'function'
3882
+ ? options.shouldCache
3883
+ : (r) => !!(r && r.ok === true && r.data != null);
3884
+ const memory = new Map();
3885
+ function cloneResult(r) {
3886
+ try {
3887
+ if (typeof structuredClone === 'function') {
3888
+ return structuredClone(r);
3889
+ }
3890
+ }
3891
+ catch {
3892
+ /* ignore */
3893
+ }
3894
+ try {
3895
+ return JSON.parse(JSON.stringify(r));
3896
+ }
3897
+ catch {
3898
+ return { ...r, data: r.data };
3899
+ }
3750
3900
  }
3751
- }
3752
-
3753
- function readWebStorage(store, key) {
3754
- try {
3755
- const raw = store.getItem(key);
3756
- if (!raw) {
3757
- return null
3758
- }
3759
- const o = JSON.parse(raw);
3760
- if (!o || typeof o !== 'object') {
3761
- return null
3762
- }
3763
- const exp = o.expiresAt;
3764
- const res = o.result;
3765
- if (typeof exp !== 'number' || getNow() > exp) {
3766
- store.removeItem(key);
3767
- return null
3768
- }
3769
- return /** @type {FetchWebPluginManifestResult} */ (res)
3770
- } catch (_) {
3771
- return null
3901
+ function touchMemory(key) {
3902
+ const e = memory.get(key);
3903
+ if (e) {
3904
+ e.lastRead = getNow();
3905
+ }
3772
3906
  }
3773
- }
3774
-
3775
- function writeWebStorage(store, key, result, expiresAt) {
3776
- try {
3777
- store.setItem(key, JSON.stringify({ expiresAt, result }));
3778
- } catch (_) {
3779
- /* Quota / 不可序列化 */
3907
+ function pruneMemory() {
3908
+ if (memory.size <= maxEntries) {
3909
+ return;
3910
+ }
3911
+ const entries = [...memory.entries()].sort((a, b) => a[1].lastRead - b[1].lastRead);
3912
+ const drop = memory.size - maxEntries;
3913
+ for (let i = 0; i < drop; i++) {
3914
+ memory.delete(entries[i][0]);
3915
+ }
3780
3916
  }
3781
- }
3782
-
3783
- return (next) => {
3784
- return async (ctx) => {
3785
- const key = cacheKeyFn(ctx);
3786
- const now = getNow();
3787
-
3788
- if (storage === 'memory') {
3789
- const hit = memory.get(key);
3790
- if (hit && hit.expiresAt > now) {
3791
- touchMemory(key);
3792
- return cloneResult(hit.result)
3917
+ function readWebStorage(store, key) {
3918
+ try {
3919
+ const raw = store.getItem(key);
3920
+ if (!raw) {
3921
+ return null;
3922
+ }
3923
+ const o = JSON.parse(raw);
3924
+ if (!o || typeof o !== 'object') {
3925
+ return null;
3926
+ }
3927
+ const exp = o.expiresAt;
3928
+ const res = o.result;
3929
+ if (typeof exp !== 'number' || getNow() > exp) {
3930
+ store.removeItem(key);
3931
+ return null;
3932
+ }
3933
+ return res;
3934
+ }
3935
+ catch {
3936
+ return null;
3793
3937
  }
3794
- } else if (storage === 'session' && typeof sessionStorage !== 'undefined') {
3795
- const sk = `${prefix}:${key}`;
3796
- const hit = readWebStorage(sessionStorage, sk);
3797
- if (hit) {
3798
- return cloneResult(hit)
3938
+ }
3939
+ function writeWebStorage(store, key, result, expiresAt) {
3940
+ try {
3941
+ store.setItem(key, JSON.stringify({ expiresAt, result }));
3799
3942
  }
3800
- } else if (storage === 'local' && typeof localStorage !== 'undefined') {
3801
- const sk = `${prefix}:${key}`;
3802
- const hit = readWebStorage(localStorage, sk);
3803
- if (hit) {
3804
- return cloneResult(hit)
3943
+ catch {
3944
+ /* Quota / 不可序列化 */
3805
3945
  }
3806
- }
3807
-
3808
- const result = await next(ctx);
3809
-
3810
- if (!shouldCache(result)) {
3811
- return result
3812
- }
3813
-
3814
- const expiresAt = now + ttlMs;
3815
- const frozen = cloneResult(result);
3816
-
3817
- if (storage === 'memory') {
3818
- memory.set(key, { expiresAt, result: frozen, lastRead: now });
3819
- pruneMemory();
3820
- } else if (storage === 'session' && typeof sessionStorage !== 'undefined') {
3821
- writeWebStorage(sessionStorage, `${prefix}:${key}`, frozen, expiresAt);
3822
- } else if (storage === 'local' && typeof localStorage !== 'undefined') {
3823
- writeWebStorage(localStorage, `${prefix}:${key}`, frozen, expiresAt);
3824
- }
3825
-
3826
- return result
3827
3946
  }
3828
- }
3947
+ return (next) => {
3948
+ return async (ctx) => {
3949
+ const key = cacheKeyFn(ctx);
3950
+ const now = getNow();
3951
+ if (storage === 'memory') {
3952
+ const hit = memory.get(key);
3953
+ if (hit && hit.expiresAt > now) {
3954
+ touchMemory(key);
3955
+ return cloneResult(hit.result);
3956
+ }
3957
+ }
3958
+ else if (storage === 'session' && typeof sessionStorage !== 'undefined') {
3959
+ const sk = `${prefix}:${key}`;
3960
+ const hit = readWebStorage(sessionStorage, sk);
3961
+ if (hit) {
3962
+ return cloneResult(hit);
3963
+ }
3964
+ }
3965
+ else if (storage === 'local' && typeof localStorage !== 'undefined') {
3966
+ const sk = `${prefix}:${key}`;
3967
+ const hit = readWebStorage(localStorage, sk);
3968
+ if (hit) {
3969
+ return cloneResult(hit);
3970
+ }
3971
+ }
3972
+ const result = await next(ctx);
3973
+ if (!shouldCache(result)) {
3974
+ return result;
3975
+ }
3976
+ const expiresAt = now + ttlMs;
3977
+ const frozen = cloneResult(result);
3978
+ if (storage === 'memory') {
3979
+ memory.set(key, { expiresAt, result: frozen, lastRead: now });
3980
+ pruneMemory();
3981
+ }
3982
+ else if (storage === 'session' && typeof sessionStorage !== 'undefined') {
3983
+ writeWebStorage(sessionStorage, `${prefix}:${key}`, frozen, expiresAt);
3984
+ }
3985
+ else if (storage === 'local' && typeof localStorage !== 'undefined') {
3986
+ writeWebStorage(localStorage, `${prefix}:${key}`, frozen, expiresAt);
3987
+ }
3988
+ return result;
3989
+ };
3990
+ };
3829
3991
  }
3830
-
3831
- /**
3832
- * 自右向左组合中间件(与 Koa/Redux 习惯一致:`compose(f,g,h)(inner)` = f(g(h(inner))))。
3833
- *
3834
- * @param {FetchWebPluginManifestFn} inner 最内层拉取实现
3835
- * @param {...function(FetchWebPluginManifestFn): FetchWebPluginManifestFn} middlewares 每个元素签名 `(next) => async (ctx) => result`
3836
- * @returns {FetchWebPluginManifestFn}
3837
- */
3838
3992
  function composeManifestFetch$1(inner, ...middlewares) {
3839
- if (typeof inner !== 'function') {
3840
- throw new Error('[web-extend-plugin-vue2] composeManifestFetch 需要 inner 为函数')
3841
- }
3842
- let f = inner;
3843
- for (let i = middlewares.length - 1; i >= 0; i--) {
3844
- const mw = middlewares[i];
3845
- if (typeof mw !== 'function') {
3846
- throw new Error('[web-extend-plugin-vue2] composeManifestFetch 中间件须为函数')
3993
+ if (typeof inner !== 'function') {
3994
+ throw new Error('[web-extend-plugin-vue2] composeManifestFetch 需要 inner 为函数');
3847
3995
  }
3848
- f = mw(f);
3849
- }
3850
- return f
3996
+ let f = inner;
3997
+ for (let i = middlewares.length - 1; i >= 0; i--) {
3998
+ const mw = middlewares[i];
3999
+ if (typeof mw !== 'function') {
4000
+ throw new Error('[web-extend-plugin-vue2] composeManifestFetch 中间件须为函数');
4001
+ }
4002
+ f = mw(f);
4003
+ }
4004
+ return f;
3851
4005
  }
3852
4006
 
3853
4007
  var manifestComposer = /*#__PURE__*/Object.freeze({
3854
- __proto__: null,
3855
- composeManifestFetch: composeManifestFetch$1,
3856
- manifestFetchCacheMiddleware: manifestFetchCacheMiddleware$1,
3857
- wrapManifestFetchWithCache: wrapManifestFetchWithCache$1
4008
+ __proto__: null,
4009
+ composeManifestFetch: composeManifestFetch$1,
4010
+ manifestFetchCacheMiddleware: manifestFetchCacheMiddleware$1,
4011
+ wrapManifestFetchWithCache: wrapManifestFetchWithCache$1
3858
4012
  });
3859
4013
 
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
- }
3897
- }
3898
-
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
4014
+ /**
4015
+ * 宿主侧响应式注册表:扩展点槽位(供布局与 `ExtensionPoint` 消费)。
4016
+ * 菜单数据由宿主在 `applyPluginMenuItems` 中自行并入其路由/菜单 state,框架不维护平行菜单列表。
4017
+ */
4018
+ const registries = Vue.observable({
4019
+ slots: {},
4020
+ slotRevision: 0
3920
4021
  });
3921
4022
 
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
- }
4023
+ /**
4024
+ * 按插件 id 收集 `onTeardown` 回调,供 `disposeWebPlugin` 统一执行。
4025
+ */
4026
+ const _byPlugin = new Map();
4027
+ function registerPluginTeardown(pluginId, fn) {
4028
+ if (typeof fn !== 'function') {
4029
+ return;
4030
+ }
4031
+ let list = _byPlugin.get(pluginId);
4032
+ if (!list) {
4033
+ list = [];
4034
+ _byPlugin.set(pluginId, list);
4035
+ }
4036
+ list.push(fn);
3965
4037
  }
3966
-
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
- }
4038
+ function runPluginTeardowns(pluginId) {
4039
+ const list = _byPlugin.get(pluginId);
4040
+ if (!list) {
4041
+ return;
4042
+ }
4043
+ _byPlugin.delete(pluginId);
4044
+ for (const fn of list) {
4045
+ try {
4046
+ fn();
4047
+ }
4048
+ catch (e) {
4049
+ console.warn('[wep] teardown failed', pluginId, e);
4050
+ }
4051
+ }
4151
4052
  }
4152
4053
 
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
- }
4054
+ /**
4055
+ * 插件访问后端的受控通道:`fetch` 仅允许落在配置的路径前缀下(默认 `/api/`),默认 `credentials: 'same-origin'`。
4056
+ */
4057
+ function createRequestBridge(config = {}) {
4058
+ const raw = Array.isArray(config.allowedPathPrefixes) && config.allowedPathPrefixes.length > 0
4059
+ ? config.allowedPathPrefixes
4060
+ : defaultWebExtendPluginRuntime.bridgeAllowedPathPrefixes;
4061
+ const allowedPathPrefixes = raw.map((p) => ensureLeadingPath(p));
4062
+ return {
4063
+ async request(path, init = {}) {
4064
+ if (typeof path !== 'string' || !path.startsWith('/')) {
4065
+ throw new Error('[wep:bridge] path must start with /');
4066
+ }
4067
+ const allowed = allowedPathPrefixes.some((p) => path.startsWith(p));
4068
+ if (!allowed) {
4069
+ throw new Error('[wep:bridge] path not allowed: ' + path);
4070
+ }
4071
+ return fetch(path, {
4072
+ credentials: 'same-origin',
4073
+ ...init
4074
+ });
4075
+ }
4076
+ };
4205
4077
  }
4206
4078
 
4207
4079
  /**
4208
- * 在宿主布局中声明扩展点;插件通过 `hostApi.registerSlotComponents(pointId, ...)` 注入组件。
4209
- * 使用纯 render 函数,便于 Rollup 发布 dist,宿主无需再转译 .vue。
4080
+ * 构造插件 `activator(hostApi)` 使用的宿主 API:路由、菜单、扩展点、资源与受控请求桥。
4210
4081
  */
4211
-
4212
- const SlotErrorBoundary = {
4213
- name: 'SlotErrorBoundary',
4214
- props: { label: String },
4215
- data() {
4216
- return { error: null }
4217
- },
4218
- errorCaptured(err) {
4219
- this.error = err && err.message ? err.message : String(err);
4220
- console.error('[ExtensionPoint] render error in', this.label, err);
4221
- return false
4222
- },
4223
- render(h) {
4224
- if (this.error) {
4225
- return h(
4226
- 'div',
4227
- { class: 'plugin-point-error', style: { color: '#c00', fontSize: '12px' } },
4228
- `[插件 ${this.label}] 渲染失败`
4229
- )
4082
+ let slotItemKeySeq = 0;
4083
+ let routeSynthSeq = 0;
4084
+ function analyzeRouteInputTree(nodes) {
4085
+ let hasDecl = false;
4086
+ let hasCfg = false;
4087
+ function walk(r) {
4088
+ if (!r || typeof r !== 'object') {
4089
+ return;
4090
+ }
4091
+ const o = r;
4092
+ const cfg = o.component != null || o.components != null;
4093
+ const decl = typeof o.componentRef === 'string';
4094
+ if (cfg) {
4095
+ hasCfg = true;
4096
+ }
4097
+ else if (decl) {
4098
+ hasDecl = true;
4099
+ }
4100
+ const ch = o.children;
4101
+ if (Array.isArray(ch)) {
4102
+ for (const c of ch) {
4103
+ walk(c);
4104
+ }
4105
+ }
4230
4106
  }
4231
- const d = this.$slots.default;
4232
- return d && d[0] ? d[0] : h('span')
4233
- }
4234
- };
4235
-
4236
- var ExtensionPoint = {
4237
- name: 'ExtensionPoint',
4238
- components: { SlotErrorBoundary },
4239
- props: {
4240
- pointId: { type: String, required: true },
4241
- slotProps: { type: Object, default: () => ({}) }
4242
- },
4243
- computed: {
4244
- items() {
4245
- void registries.slotRevision;
4246
- return registries.slots[this.pointId] || []
4247
- },
4248
- forwardProps() {
4249
- return this.slotProps || {}
4107
+ for (const n of nodes) {
4108
+ walk(n);
4250
4109
  }
4251
- },
4252
- render(h) {
4253
- return h(
4254
- 'div',
4255
- {
4256
- class: 'extension-point',
4257
- style: { minHeight: '8px' },
4258
- attrs: { 'data-point-id': this.pointId }
4259
- },
4260
- this.items.map((item) =>
4261
- h(
4262
- SlotErrorBoundary,
4263
- {
4264
- key: item.key,
4265
- props: { label: item.pluginId }
4266
- },
4267
- [h(item.component, { props: this.forwardProps })]
4268
- )
4269
- )
4270
- )
4271
- }
4272
- };
4273
-
4110
+ return { hasDecl, hasCfg };
4111
+ }
4274
4112
  /**
4275
- * 一键接入:注册 `ExtensionPoint` 并执行 `bootstrapPlugins`。
4276
- * @module install
4113
+ * 单插件在宿主侧的 API 句柄。工厂请传 `(id, router, kit) => createHostApi(id, r, kit)` 以便 bridge 白名单等到位。
4277
4114
  */
4115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4116
+ function createHostApi(pluginId, router, hostKitOptions = {}) {
4117
+ const bridgePrefixes = Array.isArray(hostKitOptions.bridgeAllowedPathPrefixes) &&
4118
+ hostKitOptions.bridgeAllowedPathPrefixes.length > 0
4119
+ ? hostKitOptions.bridgeAllowedPathPrefixes
4120
+ : defaultWebExtendPluginRuntime.bridgeAllowedPathPrefixes;
4121
+ const bridge = createRequestBridge({ allowedPathPrefixes: bridgePrefixes });
4122
+ const parentName = typeof hostKitOptions.pluginRoutesParentName === 'string'
4123
+ ? hostKitOptions.pluginRoutesParentName.trim()
4124
+ : '';
4125
+ function applyInternalRegister(rawRouteConfigs) {
4126
+ const wrapped = rawRouteConfigs.map((r) => ({
4127
+ ...r,
4128
+ name: r.name || `${routeSynthNamePrefix}${pluginId}_${routeSynthSeq++}`,
4129
+ meta: { ...(r.meta || {}), pluginId }
4130
+ }));
4131
+ if (typeof router.addRoute !== 'function') {
4132
+ throw new Error('[wep] vue-router 3.5+ 必需:请使用 router.addRoute(不再支持 addRoutes)');
4133
+ }
4134
+ if (parentName) {
4135
+ for (const r of wrapped) {
4136
+ router.addRoute(parentName, r);
4137
+ }
4138
+ }
4139
+ else {
4140
+ for (const r of wrapped) {
4141
+ router.addRoute(r);
4142
+ }
4143
+ }
4144
+ }
4145
+ function injectStylesheet(href) {
4146
+ const link = document.createElement('link');
4147
+ link.rel = 'stylesheet';
4148
+ link.href = href;
4149
+ link.setAttribute('data-plugin-asset', pluginId);
4150
+ document.head.appendChild(link);
4151
+ }
4152
+ function injectScript(src) {
4153
+ return new Promise((resolve, reject) => {
4154
+ const s = document.createElement('script');
4155
+ s.async = true;
4156
+ s.src = src;
4157
+ s.setAttribute('data-plugin-asset', pluginId);
4158
+ s.onload = () => resolve();
4159
+ s.onerror = () => reject(new Error('script failed: ' + src));
4160
+ document.head.appendChild(s);
4161
+ });
4162
+ }
4163
+ const hostContext = hostKitOptions.hostContext != null && typeof hostKitOptions.hostContext === 'object'
4164
+ ? hostKitOptions.hostContext
4165
+ : Object.freeze({});
4166
+ return {
4167
+ hostPluginApiVersion: HOST_PLUGIN_API_VERSION,
4168
+ /** 宿主注入的只读依赖(store、router、开放能力等),见 `resolveRuntimeOptions.hostContext` */
4169
+ hostContext,
4170
+ registerRoutes(routes) {
4171
+ const list = Array.isArray(routes) ? routes : [];
4172
+ if (list.length === 0) {
4173
+ return;
4174
+ }
4175
+ const { hasDecl, hasCfg } = analyzeRouteInputTree(list);
4176
+ if (hasDecl && hasCfg) {
4177
+ throw new Error('[wep] registerRoutes: cannot mix RouteDeclaration (componentRef) with RouteConfig (component)');
4178
+ }
4179
+ let configs;
4180
+ if (hasDecl) {
4181
+ const adapt = hostKitOptions.adaptRouteDeclarations;
4182
+ if (typeof adapt !== 'function') {
4183
+ throw new Error('[wep] registerRoutes: RouteDeclaration (componentRef) requires adaptRouteDeclarations on the host');
4184
+ }
4185
+ configs = adapt({
4186
+ pluginId,
4187
+ router,
4188
+ declarations: list
4189
+ });
4190
+ }
4191
+ else {
4192
+ configs = list;
4193
+ }
4194
+ if (typeof hostKitOptions.transformRoutes === 'function') {
4195
+ configs = hostKitOptions.transformRoutes({
4196
+ pluginId,
4197
+ router,
4198
+ routes: configs
4199
+ });
4200
+ }
4201
+ if (typeof hostKitOptions.interceptRegisterRoutes === 'function') {
4202
+ hostKitOptions.interceptRegisterRoutes({
4203
+ pluginId,
4204
+ router,
4205
+ routes: configs,
4206
+ applyInternalRegister
4207
+ });
4208
+ }
4209
+ else {
4210
+ applyInternalRegister(configs);
4211
+ }
4212
+ },
4213
+ registerMenuItems(items) {
4214
+ const apply = hostKitOptions.applyPluginMenuItems;
4215
+ if (typeof apply !== 'function') {
4216
+ throw new Error('[wep] registerMenuItems 需要宿主在 resolveRuntimeOptions 中提供 applyPluginMenuItems,将菜单数据并入宿主侧栏/目录 state(框架不再维护 registries.menus)');
4217
+ }
4218
+ const list = Array.isArray(items) ? items : [];
4219
+ const enriched = list.map((item) => ({ ...item, pluginId }));
4220
+ enriched.sort((a, b) => (a.order != null ? Number(a.order) : 0) - (b.order != null ? Number(b.order) : 0));
4221
+ apply({ pluginId, items: enriched });
4222
+ },
4223
+ registerSlotComponents(pointId, components) {
4224
+ if (!pointId) {
4225
+ return;
4226
+ }
4227
+ if (!registries.slots[pointId]) {
4228
+ Vue.set(registries.slots, pointId, []);
4229
+ }
4230
+ const list = registries.slots[pointId];
4231
+ for (const c of components) {
4232
+ list.push({
4233
+ pluginId,
4234
+ component: c.component,
4235
+ priority: c.priority != null ? c.priority : 0,
4236
+ key: `${pluginId}-${pointId}-${++slotItemKeySeq}`
4237
+ });
4238
+ }
4239
+ list.sort((a, b) => b.priority - a.priority);
4240
+ registries.slotRevision++;
4241
+ },
4242
+ registerStylesheetUrls(urls) {
4243
+ for (const u of urls || []) {
4244
+ if (typeof u === 'string' && u) {
4245
+ injectStylesheet(u);
4246
+ }
4247
+ }
4248
+ },
4249
+ registerScriptUrls(urls) {
4250
+ const chain = (urls || []).filter((u) => typeof u === 'string' && u).reduce((p, u) => p.then(() => injectScript(u)), Promise.resolve());
4251
+ chain.catch((e) => console.warn('[wep] registerScriptUrls', pluginId, e));
4252
+ },
4253
+ registerSanitizedHtmlSnippet() {
4254
+ throw new Error('registerSanitizedHtmlSnippet is not enabled');
4255
+ },
4256
+ getBridge: () => bridge,
4257
+ onTeardown(_pluginId, fn) {
4258
+ if (typeof fn === 'function') {
4259
+ registerPluginTeardown(pluginId, fn);
4260
+ }
4261
+ }
4262
+ };
4263
+ }
4278
4264
 
4279
4265
  /**
4280
- * 注册全局组件 `ExtensionPoint` 并异步引导插件清单。
4281
- *
4282
- * @param {*} Vue
4283
- * @param {*} router vue-router 实例
4284
- * @param {Record<string, unknown>} [options] 传给 `resolveRuntimeOptions` 的字段;可含 `env`(Vite 传入 `import.meta.env`)以读取 `VITE_*`。
4285
- * @returns {Promise<void>}
4266
+ * 卸载单个插件:执行 teardown、清理注册表与 activator、移除带 `data-plugin-asset` 的 DOM。
4267
+ * 注意:Vue Router 3 无公开 `removeRoute`,动态路由通常需整页刷新或宿主自行维护。
4286
4268
  */
4287
- function installWebExtendPluginVue2(Vue, router, options) {
4288
- const opts = options || {};
4289
- const { env: injectedEnv, ...runtimeUser } = opts;
4290
- if (injectedEnv && typeof injectedEnv === 'object') {
4291
- setWebExtendPluginEnv(injectedEnv);
4292
- }
4293
- if (Vue && ExtensionPoint) {
4294
- Vue.component('ExtensionPoint', ExtensionPoint);
4295
- }
4296
- const runtime = resolveRuntimeOptions$1(runtimeUser);
4297
- return bootstrapPlugins$1(router, (id, r, kit) => createHostApi(id, r, kit), runtime)
4269
+ function disposeWebPlugin(pluginId) {
4270
+ if (!pluginId || typeof pluginId !== 'string') {
4271
+ return;
4272
+ }
4273
+ runPluginTeardowns(pluginId);
4274
+ revokePluginMenusIfConfigured(pluginId);
4275
+ const slots = registries.slots;
4276
+ for (const pointId of Object.keys(slots)) {
4277
+ const list = slots[pointId];
4278
+ if (!Array.isArray(list)) {
4279
+ continue;
4280
+ }
4281
+ const next = list.filter((x) => x.pluginId !== pluginId);
4282
+ if (next.length === 0) {
4283
+ Vue.delete(slots, pointId);
4284
+ }
4285
+ else if (next.length !== list.length) {
4286
+ Vue.set(slots, pointId, next);
4287
+ }
4288
+ }
4289
+ registries.slotRevision++;
4290
+ if (typeof window !== 'undefined' && window.__PLUGIN_ACTIVATORS__) {
4291
+ delete window.__PLUGIN_ACTIVATORS__[pluginId];
4292
+ }
4293
+ if (typeof document !== 'undefined') {
4294
+ document.querySelectorAll('[data-plugin-asset]').forEach((el) => {
4295
+ if (el.getAttribute('data-plugin-asset') === pluginId) {
4296
+ el.remove();
4297
+ }
4298
+ });
4299
+ }
4298
4300
  }
4299
4301
 
4300
4302
  /**
4301
- * 预设:Vue CLI + 统一 axios(如若依 `utils/request`)。
4302
- * 对外以 {@link presetVueCliAxios} 聚合;亦保留具名函数便于 tree-shaking。
4303
- *
4304
- * @module presets/vue-cli-axios
4303
+ * 布局中的扩展点占位;插件通过 `hostApi.registerSlotComponents(pointId, ...)` 注入组件。
4304
+ * 样式由宿主全局 CSS 覆盖 `.extension-point` / `.plugin-point-error`。
4305
4305
  */
4306
+ const SlotErrorBoundary = Vue.extend({
4307
+ name: 'SlotErrorBoundary',
4308
+ props: { label: String },
4309
+ data() {
4310
+ return { error: null };
4311
+ },
4312
+ errorCaptured(err) {
4313
+ this.error = err instanceof Error && err.message ? err.message : String(err);
4314
+ console.error('[wep:ExtensionPoint]', this.label, err);
4315
+ return false;
4316
+ },
4317
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4318
+ render(h) {
4319
+ if (this.error) {
4320
+ return h('div', { class: 'plugin-point-error' }, `[插件 ${this.label}] 渲染失败`);
4321
+ }
4322
+ const d = this.$slots.default;
4323
+ return d && d[0] ? d[0] : h('span');
4324
+ }
4325
+ });
4326
+ var ExtensionPoint = Vue.extend({
4327
+ name: 'ExtensionPoint',
4328
+ components: { SlotErrorBoundary },
4329
+ props: {
4330
+ pointId: { type: String, required: true },
4331
+ slotProps: { type: Object, default: () => ({}) }
4332
+ },
4333
+ computed: {
4334
+ items() {
4335
+ void registries.slotRevision;
4336
+ return registries.slots[this.pointId] || [];
4337
+ },
4338
+ forwardProps() {
4339
+ return this.slotProps || {};
4340
+ }
4341
+ },
4342
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4343
+ render(h) {
4344
+ return h('div', {
4345
+ class: 'extension-point',
4346
+ attrs: { 'data-point-id': this.pointId }
4347
+ }, this.items.map((item) => h(SlotErrorBoundary, {
4348
+ key: item.key,
4349
+ props: { label: item.pluginId }
4350
+ }, [h(item.component, { props: this.forwardProps })])));
4351
+ }
4352
+ });
4306
4353
 
4307
4354
  /**
4308
- * @typedef {object} VueCliAxiosInstallPresetDeps
4309
- * @property {(config: { url: string, method?: string, [key: string]: unknown }) => Promise<unknown>} request 宿主 axios 封装(已含 baseURL、Token 等)
4355
+ * 注册全局 `ExtensionPoint` 并异步拉取清单、激活插件。
4310
4356
  */
4311
-
4312
4357
  /**
4313
- * `manifestBase + manifestListPath` 转为相对 `VUE_APP_BASE_API` 的路径(供 axios baseURL 拼接)。
4314
- * @param {string} manifestUrl
4315
- * @param {string} [apiBase]
4358
+ * @param Vue Vue 构造函数
4359
+ * @param router vue-router 实例
4360
+ * @param options 传给 `resolveRuntimeOptions`;可含 `env` 以读取 `VITE_*`
4316
4361
  */
4317
- function manifestPathForVueCliApiBase(manifestUrl, apiBase) {
4318
- const base = String(
4319
- apiBase !== undefined
4320
- ? apiBase
4321
- : (typeof process !== 'undefined' && process.env && process.env.VUE_APP_BASE_API) || ''
4322
- ).replace(/\/$/, '');
4323
- if (typeof window === 'undefined') {
4324
- return '/api/frontend-plugins'
4325
- }
4326
- const u = new URL(manifestUrl, window.location.origin);
4327
- let path = u.pathname + u.search;
4328
- if (base && path.startsWith(base)) {
4329
- path = path.slice(base.length) || '/';
4330
- }
4331
- return path
4362
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4363
+ function installWebExtendPluginVue2(Vue, router, options) {
4364
+ const opts = options || {};
4365
+ const { env: injectedEnv, ...runtimeUser } = opts;
4366
+ if (injectedEnv && typeof injectedEnv === 'object') {
4367
+ setWebExtendPluginEnv(injectedEnv);
4368
+ }
4369
+ if (Vue && ExtensionPoint) {
4370
+ Vue.component('ExtensionPoint', ExtensionPoint);
4371
+ }
4372
+ const runtime = resolveRuntimeOptions$1(runtimeUser);
4373
+ return bootstrapPlugins$1(router, (id, r, kit) => createHostApi(id, r, kit || {}), runtime);
4332
4374
  }
4333
4375
 
4334
4376
  /**
4335
- * 兼容裸清单 JSON 与常见 `{ code, data: { plugins } }` 包装。
4336
- * @param {unknown} body
4337
- * @returns {object|null}
4377
+ * Vue CLI + 统一 axios(如 RuoYi `utils/request`)场景的 `install` 预设。
4338
4378
  */
4339
- function unwrapTableStyleManifestBody(body) {
4340
- if (!body || typeof body !== 'object') {
4341
- return null
4342
- }
4343
- const o = /** @type {Record<string, unknown>} */ (body);
4344
- if (Array.isArray(o.plugins)) {
4345
- return o
4346
- }
4347
- const d = o.data;
4348
- if (d && typeof d === 'object') {
4349
- const inner = /** @type {Record<string, unknown>} */ (d);
4350
- if (Array.isArray(inner.plugins)) {
4351
- return inner
4379
+ function resolveManifestPathUnderApiBase(manifestUrl, apiBase) {
4380
+ const base = String(apiBase !== undefined
4381
+ ? apiBase
4382
+ : typeof process !== 'undefined' && process.env && process.env.VUE_APP_BASE_API
4383
+ ? String(process.env.VUE_APP_BASE_API)
4384
+ : '').replace(/\/$/, '');
4385
+ if (typeof window === 'undefined') {
4386
+ return '/api/frontend-plugins';
4352
4387
  }
4353
- if ('plugins' in inner) {
4354
- return inner
4388
+ const u = new URL(manifestUrl, window.location.origin);
4389
+ let path = u.pathname + u.search;
4390
+ if (base && path.startsWith(base)) {
4391
+ path = path.slice(base.length) || '/';
4355
4392
  }
4356
- }
4357
- return d !== undefined && d !== null && typeof d === 'object' ? /** @type {object} */ (d) : o
4393
+ return path;
4358
4394
  }
4359
-
4360
- function bridgePrefixesFromVueCliEnv() {
4361
- const base = (
4362
- typeof process !== 'undefined' && process.env && process.env.VUE_APP_BASE_API
4363
- ? String(process.env.VUE_APP_BASE_API)
4364
- : ''
4365
- ).replace(/\/$/, '');
4366
- const raw = [base ? `${base}/` : '', '/api/', '/dev-api/'].filter(Boolean);
4367
- return [...new Set(raw)]
4368
- }
4369
-
4370
4395
  /**
4371
- * 生成可直接传给 `installWebExtendPluginVue2` options(含 fetchManifest、manifestBase、bridge 前缀)。
4372
- * @param {VueCliAxiosInstallPresetDeps} deps
4373
- * @param {Record<string, unknown>} [extra] 合并覆盖,如 `manifestListPath`、`env` 等
4374
- * @returns {Record<string, unknown>}
4396
+ * 常见 Java 清单路径:与 `VUE_APP_BASE_API` 拼接为 `${base}/frontend-plugins`。
4397
+ * 若后端使用 `/api/frontend-plugins` 段,请在 extra 中显式传入 `manifestListPath`。
4375
4398
  */
4399
+ const defaultVueCliJavaManifestListPath = '/frontend-plugins';
4400
+ function bridgePrefixesFromVueCliEnv() {
4401
+ const base = (typeof process !== 'undefined' && process.env && process.env.VUE_APP_BASE_API
4402
+ ? String(process.env.VUE_APP_BASE_API)
4403
+ : '').replace(/\/$/, '');
4404
+ const raw = [base ? `${base}/` : '', '/api/', '/dev-api/'].filter(Boolean);
4405
+ return [...new Set(raw)];
4406
+ }
4376
4407
  function createVueCliAxiosInstallOptions(deps, extra = {}) {
4377
- const { request } = deps;
4378
- if (typeof request !== 'function') {
4379
- throw new Error('[web-extend-plugin-vue2] createVueCliAxiosInstallOptions({ request }) 需要宿主 request 函数')
4380
- }
4381
- const envBase = (
4382
- typeof process !== 'undefined' && process.env && process.env.VUE_APP_BASE_API
4383
- ? String(process.env.VUE_APP_BASE_API)
4384
- : ''
4385
- ).replace(/\/$/, '');
4386
- const userBase =
4387
- extra.manifestBase !== undefined && String(extra.manifestBase).trim() !== ''
4388
- ? String(extra.manifestBase).replace(/\/$/, '')
4389
- : '';
4390
- const stripBase = userBase || envBase;
4391
-
4392
- const fetchManifest = async (ctx) => {
4393
- try {
4394
- const url = manifestPathForVueCliApiBase(ctx.manifestUrl, stripBase);
4395
- const body = await request({
4396
- url,
4397
- method: 'get'
4398
- });
4399
- const data = unwrapTableStyleManifestBody(body);
4400
- if (!data || typeof data !== 'object') {
4401
- return {
4402
- ok: false,
4403
- error: new Error('[web-plugin] 清单响应格式无效'),
4404
- data: null
4408
+ const { request } = deps;
4409
+ if (typeof request !== 'function') {
4410
+ throw new Error('[wep] createVueCliAxiosInstallOptions requires deps.request');
4411
+ }
4412
+ const { fetchManifest: userFetchManifest, manifestMode: extraManifestMode, staticManifestUrl: extraStaticManifestUrl, ...restExtra } = extra;
4413
+ const envBase = (typeof process !== 'undefined' && process.env && process.env.VUE_APP_BASE_API
4414
+ ? String(process.env.VUE_APP_BASE_API)
4415
+ : '').replace(/\/$/, '');
4416
+ const userBase = extra.manifestBase !== undefined && String(extra.manifestBase).trim() !== ''
4417
+ ? String(extra.manifestBase).replace(/\/$/, '')
4418
+ : '';
4419
+ const stripBase = userBase || envBase;
4420
+ const manifestMode = resolveManifestModeFromInputs(extraManifestMode);
4421
+ const staticManifestUrl = resolveStaticManifestUrlFromInputs(extraStaticManifestUrl);
4422
+ const fetchManifestApi = async (ctx) => {
4423
+ try {
4424
+ const url = resolveManifestPathUnderApiBase(ctx.manifestUrl, stripBase);
4425
+ const body = await request({
4426
+ url,
4427
+ method: 'get'
4428
+ });
4429
+ const data = unwrapNestedManifestBody(body);
4430
+ if (!data || typeof data !== 'object') {
4431
+ return {
4432
+ ok: false,
4433
+ error: new Error('[wep] invalid manifest response'),
4434
+ data: null
4435
+ };
4436
+ }
4437
+ return { ok: true, data };
4438
+ }
4439
+ catch (e) {
4440
+ return { ok: false, error: e, data: null };
4405
4441
  }
4406
- }
4407
- return { ok: true, data }
4408
- } catch (e) {
4409
- return { ok: false, error: e, data: null }
4442
+ };
4443
+ const fetchManifestStatic = (ctx) => fetchStaticManifestViaHttp(ctx);
4444
+ const fetchManifest = typeof userFetchManifest === 'function'
4445
+ ? userFetchManifest
4446
+ : manifestMode === 'static'
4447
+ ? fetchManifestStatic
4448
+ : fetchManifestApi;
4449
+ const opts = {
4450
+ manifestBase: stripBase || undefined,
4451
+ bridgeAllowedPathPrefixes: bridgePrefixesFromVueCliEnv(),
4452
+ manifestMode,
4453
+ staticManifestUrl,
4454
+ ...restExtra,
4455
+ fetchManifest
4456
+ };
4457
+ const listPath = typeof process !== 'undefined' &&
4458
+ process.env &&
4459
+ process.env[webExtendPluginEnvKeys.manifestPathAlt];
4460
+ if (listPath && opts.manifestListPath === undefined && extra.manifestListPath === undefined) {
4461
+ opts.manifestListPath = String(process.env[webExtendPluginEnvKeys.manifestPathAlt]);
4410
4462
  }
4411
- };
4412
-
4413
- const opts = {
4414
- manifestBase: stripBase || undefined,
4415
- bridgeAllowedPathPrefixes: bridgePrefixesFromVueCliEnv(),
4416
- fetchManifest,
4417
- ...extra
4418
- };
4419
-
4420
- const listPath =
4421
- typeof process !== 'undefined' && process.env && process.env.VUE_APP_WEB_PLUGIN_MANIFEST_PATH;
4422
- if (listPath && opts.manifestListPath === undefined && extra.manifestListPath === undefined) {
4423
- opts.manifestListPath = String(listPath);
4424
- }
4425
-
4426
- return opts
4463
+ return opts;
4427
4464
  }
4428
-
4429
4465
  /**
4430
- * Vue CLI + axios 预设的**对外聚合入口**(与具名导出函数等价,便于按需查阅与扩展多预设)。
4431
- * @type {Readonly<{ id: string, description: string, createInstallOptions: typeof createVueCliAxiosInstallOptions, manifestPathForApiBase: typeof manifestPathForVueCliApiBase, unwrapManifestBody: typeof unwrapTableStyleManifestBody }>}
4466
+ * 少样板接入:`hostContext` 自动含 `router`(及可选 `store`)、`isDev` 与常见 `manifestListPath` 默认值。
4467
+ * 更多运行时字段仍可通过 `extra` 覆盖。
4432
4468
  */
4469
+ function createVueCliAxiosQuickInstallOptions(
4470
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4471
+ router, deps, extra = {}) {
4472
+ const { request, hostLayoutComponent, store, hostContext: depHostContext, applyPluginMenuItems, revokePluginMenuItems } = deps;
4473
+ const { hostContext: extraHostContext, isDev: extraIsDev, manifestListPath: extraListPath, ...restExtra } = extra;
4474
+ const hostContext = {
4475
+ router,
4476
+ ...(store !== undefined ? { store } : {}),
4477
+ ...(depHostContext && typeof depHostContext === 'object' ? depHostContext : {}),
4478
+ ...(extraHostContext && typeof extraHostContext === 'object' ? extraHostContext : {})
4479
+ };
4480
+ const manifestListPath = extraListPath !== undefined && String(extraListPath).trim() !== ''
4481
+ ? String(extraListPath)
4482
+ : defaultVueCliJavaManifestListPath;
4483
+ return createVueCliAxiosInstallOptions({ request }, {
4484
+ hostLayoutComponent,
4485
+ hostContext,
4486
+ applyPluginMenuItems,
4487
+ revokePluginMenuItems,
4488
+ isDev: extraIsDev !== undefined ? Boolean(extraIsDev) : resolveBundledIsDev(),
4489
+ manifestListPath,
4490
+ ...restExtra
4491
+ });
4492
+ }
4433
4493
  const presetVueCliAxios = Object.freeze({
4434
- id: 'vue-cli-axios',
4435
- description: 'Vue CLI + 统一 axios 实例(如若依 utils/request),清单走宿主 request',
4436
- createInstallOptions: createVueCliAxiosInstallOptions,
4437
- manifestPathForApiBase: manifestPathForVueCliApiBase,
4438
- unwrapManifestBody: unwrapTableStyleManifestBody
4494
+ id: 'vue-cli-axios',
4495
+ description: 'Vue CLI + axios request for API manifest; optional manifestMode=static uses fetch',
4496
+ createInstallOptions: createVueCliAxiosInstallOptions,
4497
+ createQuickInstallOptions: createVueCliAxiosQuickInstallOptions,
4498
+ defaultJavaManifestListPath: defaultVueCliJavaManifestListPath,
4499
+ manifestPathForApiBase: resolveManifestPathUnderApiBase,
4500
+ unwrapManifestBody: unwrapNestedManifestBody
4439
4501
  });
4440
4502
 
4441
4503
  /**
4442
- * 稳定对外 API:具名导出 + {@link WebExtendPluginVue2} 聚合对象。
4443
- * 语义化版本内保持本文件导出的符号与聚合结构兼容。
4444
- *
4445
- * `PluginRuntime` 与 `manifest-fetch-composer` 通过 namespace 绑定到 `WebExtendPluginVue2.runtime`,避免具名导出与聚合对象重复列举。
4446
- *
4447
- * @module public
4504
+ * Vue CLI + axios 场景的一键安装:合并 hostContext、默认清单路径与 IIFE 全局 Vue。
4448
4505
  */
4449
-
4450
-
4451
- const {
4452
- bootstrapPlugins,
4453
- defaultFetchWebPluginManifest,
4454
- resolveRuntimeOptions
4455
- } = pluginRuntime;
4456
-
4457
- const {
4458
- composeManifestFetch,
4459
- manifestFetchCacheMiddleware,
4460
- wrapManifestFetchWithCache
4461
- } = manifestComposer;
4506
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4507
+ function installVueCliAxiosWebPlugins(Vue, router, deps, extra) {
4508
+ const exposeGlobalVue = extra?.exposeGlobalVue !== false;
4509
+ if (exposeGlobalVue && typeof window !== 'undefined') {
4510
+ window.Vue = Vue;
4511
+ }
4512
+ const { exposeGlobalVue: _skip, ...restExtra } = extra || {};
4513
+ const opts = createVueCliAxiosQuickInstallOptions(router, deps, restExtra);
4514
+ return installWebExtendPluginVue2(Vue, router, opts);
4515
+ }
4462
4516
 
4463
4517
  /**
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
- * }>}
4518
+ * 包对外稳定导出与 `WebExtendPluginVue2` 命名空间。
4474
4519
  */
4520
+ const { bootstrapPlugins, defaultFetchWebPluginManifest, resolveRuntimeOptions, ensurePluginHostRoute } = pluginRuntime;
4521
+ const { composeManifestFetch, manifestFetchCacheMiddleware, wrapManifestFetchWithCache } = manifestComposer;
4475
4522
  const WebExtendPluginVue2 = Object.freeze({
4476
- install: installWebExtendPluginVue2,
4477
- runtime: Object.freeze({
4478
- bootstrapPlugins: bootstrapPlugins$1,
4479
- resolveRuntimeOptions: resolveRuntimeOptions$1,
4480
- defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
4481
- composeManifestFetch: composeManifestFetch$1,
4482
- manifestFetchCacheMiddleware: manifestFetchCacheMiddleware$1,
4483
- wrapManifestFetchWithCache: wrapManifestFetchWithCache$1
4484
- }),
4485
- host: Object.freeze({
4486
- createHostApi,
4487
- disposeWebPlugin,
4488
- createRequestBridge,
4489
- registries
4490
- }),
4491
- config: Object.freeze({
4492
- defaultWebExtendPluginRuntime,
4493
- setWebExtendPluginEnv
4494
- }),
4495
- constants: Object.freeze({
4496
- HOST_PLUGIN_API_VERSION
4497
- }),
4498
- components: Object.freeze({
4499
- ExtensionPoint
4500
- }),
4501
- presets: Object.freeze({
4502
- vueCliAxios: presetVueCliAxios
4503
- })
4523
+ install: installWebExtendPluginVue2,
4524
+ runtime: Object.freeze({
4525
+ bootstrapPlugins: bootstrapPlugins$1,
4526
+ resolveRuntimeOptions: resolveRuntimeOptions$1,
4527
+ ensurePluginHostRoute: ensurePluginHostRoute$1,
4528
+ defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
4529
+ composeManifestFetch: composeManifestFetch$1,
4530
+ manifestFetchCacheMiddleware: manifestFetchCacheMiddleware$1,
4531
+ wrapManifestFetchWithCache: wrapManifestFetchWithCache$1
4532
+ }),
4533
+ host: Object.freeze({
4534
+ createHostApi,
4535
+ disposeWebPlugin,
4536
+ createRequestBridge,
4537
+ registries
4538
+ }),
4539
+ config: Object.freeze({
4540
+ defaultWebExtendPluginRuntime,
4541
+ setWebExtendPluginEnv,
4542
+ webExtendPluginEnvKeys,
4543
+ defaultManifestFetchCache,
4544
+ defaultManifestMode,
4545
+ routeSynthNamePrefix,
4546
+ peerMinimumVersions
4547
+ }),
4548
+ constants: Object.freeze({
4549
+ HOST_PLUGIN_API_VERSION,
4550
+ RUNTIME_CONSOLE_LABEL
4551
+ }),
4552
+ components: Object.freeze({
4553
+ ExtensionPoint
4554
+ }),
4555
+ presets: Object.freeze({
4556
+ vueCliAxios: presetVueCliAxios
4557
+ })
4504
4558
  });
4505
4559
 
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 };
4560
+ export { ExtensionPoint, HOST_PLUGIN_API_VERSION, RUNTIME_CONSOLE_LABEL, WebExtendPluginVue2, bootstrapPlugins, composeManifestFetch, createHostApi, createRequestBridge, createVueCliAxiosInstallOptions, createVueCliAxiosQuickInstallOptions, defaultFetchWebPluginManifest, defaultManifestFetchCache, defaultManifestMode, defaultVueCliJavaManifestListPath, defaultWebExtendPluginRuntime, disposeWebPlugin, ensurePluginHostRoute, installVueCliAxiosWebPlugins, installWebExtendPluginVue2, manifestFetchCacheMiddleware, peerMinimumVersions, presetVueCliAxios, registries, resolveManifestPathUnderApiBase, resolveRuntimeOptions, routeSynthNamePrefix, setWebExtendPluginEnv, unwrapNestedManifestBody, webExtendPluginEnvKeys, wrapManifestFetchWithCache };
4507
4561
  //# sourceMappingURL=index.mjs.map