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