web-extend-plugin-vue2 0.2.3 → 0.3.0

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