web-extend-plugin-vue2 0.2.5 → 0.3.1
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/README.md +79 -189
- package/dist/index.cjs +489 -343
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +487 -337
- package/dist/index.mjs.map +1 -1
- package/index.d.ts +19 -177
- package/package.json +3 -6
package/dist/index.mjs
CHANGED
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
import Vue from 'vue';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Public constants and runtime defaults consumed by resolveRuntimeOptions and
|
|
5
|
+
* other runtime modules.
|
|
6
6
|
*/
|
|
7
|
-
/** 与 `package.json` 的 peer 下限一致;`npm run test:peer-min` / CI matrix 须与此保持同步 */
|
|
8
7
|
const peerMinimumVersions = {
|
|
9
|
-
vue: '2.6.
|
|
10
|
-
vueRouter: '3.5.
|
|
8
|
+
vue: '2.6.0',
|
|
9
|
+
vueRouter: '3.5.0'
|
|
11
10
|
};
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// 协议与品牌(发布契约;与清单 `hostPluginApiVersion` 对齐 semver 主版本,一般不随宿主覆盖)
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
11
|
const HOST_PLUGIN_API_VERSION = '1.0.0';
|
|
16
|
-
/** 控制台日志与首次引导横幅使用的短名称 */
|
|
17
12
|
const RUNTIME_CONSOLE_LABEL = 'web-extend-plugin-vue2';
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// 与 `build-env` / `resolveBundledEnv` 配套的键名(单一事实来源,便于检索与文档)
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
13
|
const webExtendPluginEnvKeys = {
|
|
22
14
|
manifestBase: 'VITE_FRONTEND_PLUGIN_BASE',
|
|
23
15
|
manifestListPath: 'VITE_WEB_PLUGIN_MANIFEST_PATH',
|
|
@@ -38,27 +30,14 @@ const webExtendPluginEnvKeys = {
|
|
|
38
30
|
webPluginDevIds: 'VITE_WEB_PLUGIN_DEV_IDS',
|
|
39
31
|
webPluginDevMap: 'VITE_WEB_PLUGIN_DEV_MAP',
|
|
40
32
|
pluginsBootstrapSummary: 'VITE_PLUGINS_BOOTSTRAP_SUMMARY',
|
|
41
|
-
/** 清单路径备选:部分宿主单独使用 */
|
|
42
33
|
manifestPathAlt: 'VUE_APP_WEB_PLUGIN_MANIFEST_PATH'
|
|
43
34
|
};
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// `manifestMode` 未配置且环境未指定时的默认值
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
35
|
const defaultManifestMode = 'api';
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
// `manifest-fetch-composer` 中间件默认(中间件 options 可逐项覆盖)
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
36
|
const defaultManifestFetchCache = {
|
|
52
37
|
storageKeyPrefix: 'wep.manifestFetch.v1',
|
|
53
38
|
maxEntries: 50
|
|
54
39
|
};
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
// 路由合成名前缀(`createHostApi` 为无 name 的路由生成 `__wep_${pluginId}_${seq}`)
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
40
|
const routeSynthNamePrefix = '__wep_';
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// 宿主可通过 `resolveRuntimeOptions` 覆盖的运行时默认值(与 README / index.d.ts 描述一致)
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
41
|
const defaultWebExtendPluginRuntime = {
|
|
63
42
|
manifestBase: '/fp-api',
|
|
64
43
|
manifestListPath: '/api/frontend-plugins',
|
|
@@ -70,11 +49,7 @@ const defaultWebExtendPluginRuntime = {
|
|
|
70
49
|
defaultImplicitDevPluginIds: [],
|
|
71
50
|
allowedScriptHosts: ['localhost', '127.0.0.1', '::1'],
|
|
72
51
|
bridgeAllowedPathPrefixes: ['/api/'],
|
|
73
|
-
/** 与 `hostLayoutComponent` 同时使用时默认父路由 name */
|
|
74
|
-
pluginRoutesParentName: '__wepPluginHost',
|
|
75
|
-
/** 插件壳路径(与菜单、ensurePluginHostRoute 一致) */
|
|
76
52
|
pluginMountPath: '/plugin',
|
|
77
|
-
/** `manifestMode=api` 且开发环境下,API 失败或 plugins 为空时尝试的静态 JSON(可放于 `public/web-plugins/`) */
|
|
78
53
|
devFallbackStaticManifestUrl: '/web-plugins/plugins.manifest.json'
|
|
79
54
|
};
|
|
80
55
|
|
|
@@ -338,15 +313,9 @@ function resolveRuntimeOptions$1(user = {}) {
|
|
|
338
313
|
return String(DEF.devFallbackStaticManifestUrl).trim();
|
|
339
314
|
})();
|
|
340
315
|
const hostLayoutComponent = user.hostLayoutComponent;
|
|
341
|
-
const pluginRoutesParentName = (()
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
if (hostLayoutComponent != null) {
|
|
346
|
-
return String(DEF.pluginRoutesParentName).trim();
|
|
347
|
-
}
|
|
348
|
-
return '';
|
|
349
|
-
})();
|
|
316
|
+
const pluginRoutesParentName = user.pluginRoutesParentName !== undefined && String(user.pluginRoutesParentName).trim() !== ''
|
|
317
|
+
? String(user.pluginRoutesParentName).trim()
|
|
318
|
+
: '';
|
|
350
319
|
const pluginMountRaw = user.pluginMountPath !== undefined && String(user.pluginMountPath).trim() !== ''
|
|
351
320
|
? String(user.pluginMountPath).trim()
|
|
352
321
|
: String(resolveBundledEnv(EK.mountPath, '') || DEF.pluginMountPath).trim();
|
|
@@ -354,7 +323,7 @@ function resolveRuntimeOptions$1(user = {}) {
|
|
|
354
323
|
const pluginHostRouteMeta = user.pluginHostRouteMeta !== undefined && user.pluginHostRouteMeta !== null
|
|
355
324
|
? user.pluginHostRouteMeta
|
|
356
325
|
: undefined;
|
|
357
|
-
const ensurePluginHostRoute = user.ensurePluginHostRoute
|
|
326
|
+
const ensurePluginHostRoute = user.ensurePluginHostRoute === true;
|
|
358
327
|
const devManifestFallback = (() => {
|
|
359
328
|
if (manifestMode === 'static') {
|
|
360
329
|
return false;
|
|
@@ -417,11 +386,8 @@ function resolveRuntimeOptions$1(user = {}) {
|
|
|
417
386
|
...(typeof user.adaptRouteDeclarations === 'function'
|
|
418
387
|
? { adaptRouteDeclarations: user.adaptRouteDeclarations }
|
|
419
388
|
: {}),
|
|
420
|
-
...(typeof user.
|
|
421
|
-
? {
|
|
422
|
-
: {}),
|
|
423
|
-
...(typeof user.revokePluginMenuItems === 'function'
|
|
424
|
-
? { revokePluginMenuItems: user.revokePluginMenuItems }
|
|
389
|
+
...(typeof user.onPluginRoutesContributed === 'function'
|
|
390
|
+
? { onPluginRoutesContributed: user.onPluginRoutesContributed }
|
|
425
391
|
: {}),
|
|
426
392
|
...(user.hostContext !== undefined &&
|
|
427
393
|
user.hostContext !== null &&
|
|
@@ -3214,23 +3180,220 @@ var semverExports = requireSemver();
|
|
|
3214
3180
|
var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
|
|
3215
3181
|
|
|
3216
3182
|
/**
|
|
3217
|
-
*
|
|
3183
|
+
* 引导时注入的 router 引用,供 disposeWebPlugin 卸载插件动态路由。
|
|
3184
|
+
*/
|
|
3185
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3186
|
+
let bootstrapRouter;
|
|
3187
|
+
/** 由 bootstrapPlugins 在浏览器环境调用 */
|
|
3188
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3189
|
+
function setPluginBootstrapRouter(router) {
|
|
3190
|
+
bootstrapRouter = router;
|
|
3191
|
+
}
|
|
3192
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3193
|
+
function getPluginBootstrapRouter() {
|
|
3194
|
+
return bootstrapRouter;
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
/**
|
|
3198
|
+
* 记录各插件通过 registerRoutes 挂到 router 上的顶层路由,
|
|
3199
|
+
* 优先使用官方 `addRoute()` 返回的 disposer 做卸载;仅在 disposer 不可用时回退到 name。
|
|
3200
|
+
*/
|
|
3201
|
+
const pluginIdToTopRoutes = new Map();
|
|
3202
|
+
function recordPluginTopRoutes(pluginId, routes) {
|
|
3203
|
+
if (!pluginId || routes.length === 0) {
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
const normalized = routes.filter((route) => route && route.name);
|
|
3207
|
+
if (normalized.length === 0) {
|
|
3208
|
+
return;
|
|
3209
|
+
}
|
|
3210
|
+
const cur = pluginIdToTopRoutes.get(pluginId) || [];
|
|
3211
|
+
pluginIdToTopRoutes.set(pluginId, cur.concat(normalized));
|
|
3212
|
+
}
|
|
3213
|
+
function tryRemoveRouteByName(
|
|
3214
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3215
|
+
router, pluginId, name) {
|
|
3216
|
+
if (!router || typeof router.removeRoute !== 'function') {
|
|
3217
|
+
console.warn('[wep] route disposer unavailable,且 router.removeRoute 不可用,动态路由可能残留', pluginId, name);
|
|
3218
|
+
return;
|
|
3219
|
+
}
|
|
3220
|
+
try {
|
|
3221
|
+
router.removeRoute(name);
|
|
3222
|
+
}
|
|
3223
|
+
catch (e) {
|
|
3224
|
+
console.warn('[wep] removeRoute fallback failed', name, e);
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
/**
|
|
3228
|
+
* 从 matcher 移除该插件登记过的顶层路由(含其子树)。
|
|
3229
|
+
* 优先走 `router.addRoute()` 返回的 disposer;仅在 disposer 不可用时回退 `router.removeRoute(name)`。
|
|
3218
3230
|
*/
|
|
3219
|
-
|
|
3220
|
-
function
|
|
3221
|
-
|
|
3231
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3232
|
+
function removeRegisteredRoutesForPlugin(router, pluginId) {
|
|
3233
|
+
const routes = pluginIdToTopRoutes.get(pluginId);
|
|
3234
|
+
if (!routes || routes.length === 0) {
|
|
3235
|
+
return;
|
|
3236
|
+
}
|
|
3237
|
+
for (let i = routes.length - 1; i >= 0; i--) {
|
|
3238
|
+
const route = routes[i];
|
|
3239
|
+
if (typeof route.dispose === 'function') {
|
|
3240
|
+
try {
|
|
3241
|
+
route.dispose();
|
|
3242
|
+
continue;
|
|
3243
|
+
}
|
|
3244
|
+
catch (e) {
|
|
3245
|
+
console.warn('[wep] route disposer failed', route.name, e);
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
tryRemoveRouteByName(router, pluginId, route.name);
|
|
3249
|
+
}
|
|
3250
|
+
pluginIdToTopRoutes.delete(pluginId);
|
|
3251
|
+
}
|
|
3252
|
+
/** 调试或宿主高级场景:查询已登记 name(勿依赖顺序语义) */
|
|
3253
|
+
function getRegisteredTopRouteNamesForPlugin(pluginId) {
|
|
3254
|
+
return (pluginIdToTopRoutes.get(pluginId) || []).map((route) => route.name);
|
|
3222
3255
|
}
|
|
3223
|
-
|
|
3224
|
-
|
|
3256
|
+
|
|
3257
|
+
/**
|
|
3258
|
+
* 宿主侧响应式注册表:扩展点槽位(供布局与 `ExtensionPoint` 消费)。
|
|
3259
|
+
* 菜单/侧栏树完全由宿主基于路由快照自行映射,框架不维护平行菜单列表。
|
|
3260
|
+
*/
|
|
3261
|
+
const registries = Vue.observable({
|
|
3262
|
+
slots: {},
|
|
3263
|
+
slotRevision: 0
|
|
3264
|
+
});
|
|
3265
|
+
|
|
3266
|
+
/**
|
|
3267
|
+
* 按插件 id 收集 `onTeardown` 回调,供 `disposeWebPlugin` 统一执行。
|
|
3268
|
+
*/
|
|
3269
|
+
const _byPlugin = new Map();
|
|
3270
|
+
function registerPluginTeardown(pluginId, fn) {
|
|
3271
|
+
if (typeof fn !== 'function') {
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
let list = _byPlugin.get(pluginId);
|
|
3275
|
+
if (!list) {
|
|
3276
|
+
list = [];
|
|
3277
|
+
_byPlugin.set(pluginId, list);
|
|
3278
|
+
}
|
|
3279
|
+
list.push(fn);
|
|
3280
|
+
}
|
|
3281
|
+
function runPluginTeardowns(pluginId) {
|
|
3282
|
+
const list = _byPlugin.get(pluginId);
|
|
3283
|
+
if (!list) {
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
_byPlugin.delete(pluginId);
|
|
3287
|
+
for (const fn of list) {
|
|
3225
3288
|
try {
|
|
3226
|
-
|
|
3289
|
+
fn();
|
|
3227
3290
|
}
|
|
3228
3291
|
catch (e) {
|
|
3229
|
-
console.warn('[wep]
|
|
3292
|
+
console.warn('[wep] teardown failed', pluginId, e);
|
|
3230
3293
|
}
|
|
3231
3294
|
}
|
|
3232
3295
|
}
|
|
3233
3296
|
|
|
3297
|
+
/**
|
|
3298
|
+
* 动态加载脚本(去重与并发合并)。
|
|
3299
|
+
*/
|
|
3300
|
+
const loadScriptMemo = new Map();
|
|
3301
|
+
function markScriptOwnership(script, pluginId) {
|
|
3302
|
+
if (pluginId) {
|
|
3303
|
+
script.setAttribute('data-plugin-asset', pluginId);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
function clearLoadedScriptMemo(src) {
|
|
3307
|
+
if (typeof src === 'string' && src) {
|
|
3308
|
+
loadScriptMemo.delete(src);
|
|
3309
|
+
return;
|
|
3310
|
+
}
|
|
3311
|
+
loadScriptMemo.clear();
|
|
3312
|
+
}
|
|
3313
|
+
function loadScript(src, pluginId) {
|
|
3314
|
+
if (typeof document === 'undefined') {
|
|
3315
|
+
return Promise.reject(new Error('loadScript: no document'));
|
|
3316
|
+
}
|
|
3317
|
+
if (loadScriptMemo.has(src)) {
|
|
3318
|
+
return loadScriptMemo.get(src);
|
|
3319
|
+
}
|
|
3320
|
+
const p = new Promise((resolve, reject) => {
|
|
3321
|
+
const scripts = document.getElementsByTagName('script');
|
|
3322
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
3323
|
+
const el = scripts[i];
|
|
3324
|
+
if (el.src === src) {
|
|
3325
|
+
markScriptOwnership(el, pluginId);
|
|
3326
|
+
if (el.getAttribute('data-wep-loaded') === 'true') {
|
|
3327
|
+
resolve();
|
|
3328
|
+
return;
|
|
3329
|
+
}
|
|
3330
|
+
el.addEventListener('load', () => {
|
|
3331
|
+
el.setAttribute('data-wep-loaded', 'true');
|
|
3332
|
+
resolve();
|
|
3333
|
+
}, { once: true });
|
|
3334
|
+
el.addEventListener('error', () => reject(new Error('loadScript failed: ' + src)), { once: true });
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
const s = document.createElement('script');
|
|
3339
|
+
s.async = true;
|
|
3340
|
+
s.src = src;
|
|
3341
|
+
markScriptOwnership(s, pluginId);
|
|
3342
|
+
s.onload = () => {
|
|
3343
|
+
s.setAttribute('data-wep-loaded', 'true');
|
|
3344
|
+
resolve();
|
|
3345
|
+
};
|
|
3346
|
+
s.onerror = () => reject(new Error('loadScript failed: ' + src));
|
|
3347
|
+
document.head.appendChild(s);
|
|
3348
|
+
});
|
|
3349
|
+
loadScriptMemo.set(src, p);
|
|
3350
|
+
p.catch(() => loadScriptMemo.delete(src));
|
|
3351
|
+
return p;
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
/**
|
|
3355
|
+
* 卸载单个插件:执行 teardown、移除该插件登记的动态路由、清理注册表与 activator、移除带 `data-plugin-asset` 的 DOM。
|
|
3356
|
+
* 优先使用 `addRoute()` 返回的 disposer;必要时回退 `router.removeRoute(name)`。
|
|
3357
|
+
*/
|
|
3358
|
+
function disposeWebPlugin(pluginId) {
|
|
3359
|
+
if (!pluginId || typeof pluginId !== 'string') {
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
runPluginTeardowns(pluginId);
|
|
3363
|
+
removeRegisteredRoutesForPlugin(getPluginBootstrapRouter(), pluginId);
|
|
3364
|
+
const slots = registries.slots;
|
|
3365
|
+
for (const pointId of Object.keys(slots)) {
|
|
3366
|
+
const list = slots[pointId];
|
|
3367
|
+
if (!Array.isArray(list)) {
|
|
3368
|
+
continue;
|
|
3369
|
+
}
|
|
3370
|
+
const next = list.filter((x) => x.pluginId !== pluginId);
|
|
3371
|
+
if (next.length === 0) {
|
|
3372
|
+
Vue.delete(slots, pointId);
|
|
3373
|
+
}
|
|
3374
|
+
else if (next.length !== list.length) {
|
|
3375
|
+
Vue.set(slots, pointId, next);
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
registries.slotRevision++;
|
|
3379
|
+
if (typeof window !== 'undefined' && window.__PLUGIN_ACTIVATORS__) {
|
|
3380
|
+
delete window.__PLUGIN_ACTIVATORS__[pluginId];
|
|
3381
|
+
}
|
|
3382
|
+
if (typeof document !== 'undefined') {
|
|
3383
|
+
document.querySelectorAll('[data-plugin-asset]').forEach((el) => {
|
|
3384
|
+
if (el.getAttribute('data-plugin-asset') === pluginId) {
|
|
3385
|
+
if (el.tagName === 'SCRIPT') {
|
|
3386
|
+
const src = el.src;
|
|
3387
|
+
if (src) {
|
|
3388
|
+
clearLoadedScriptMemo(src);
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
el.remove();
|
|
3392
|
+
}
|
|
3393
|
+
});
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3234
3397
|
/**
|
|
3235
3398
|
* 开发模式插件 URL 映射(显式 JSON + 隐式 dev 探测)。
|
|
3236
3399
|
*/
|
|
@@ -3393,49 +3556,6 @@ function startPluginDevSseForMap(devMap, isDev, hostSet, ssePath) {
|
|
|
3393
3556
|
}
|
|
3394
3557
|
}
|
|
3395
3558
|
|
|
3396
|
-
/**
|
|
3397
|
-
* 动态加载脚本(去重与并发合并)。
|
|
3398
|
-
*/
|
|
3399
|
-
const loadScriptMemo = new Map();
|
|
3400
|
-
function loadScript(src) {
|
|
3401
|
-
if (typeof document === 'undefined') {
|
|
3402
|
-
return Promise.reject(new Error('loadScript: no document'));
|
|
3403
|
-
}
|
|
3404
|
-
if (loadScriptMemo.has(src)) {
|
|
3405
|
-
return loadScriptMemo.get(src);
|
|
3406
|
-
}
|
|
3407
|
-
const p = new Promise((resolve, reject) => {
|
|
3408
|
-
const scripts = document.getElementsByTagName('script');
|
|
3409
|
-
for (let i = 0; i < scripts.length; i++) {
|
|
3410
|
-
const el = scripts[i];
|
|
3411
|
-
if (el.src === src) {
|
|
3412
|
-
if (el.getAttribute('data-wep-loaded') === 'true') {
|
|
3413
|
-
resolve();
|
|
3414
|
-
return;
|
|
3415
|
-
}
|
|
3416
|
-
el.addEventListener('load', () => {
|
|
3417
|
-
el.setAttribute('data-wep-loaded', 'true');
|
|
3418
|
-
resolve();
|
|
3419
|
-
}, { once: true });
|
|
3420
|
-
el.addEventListener('error', () => reject(new Error('loadScript failed: ' + src)), { once: true });
|
|
3421
|
-
return;
|
|
3422
|
-
}
|
|
3423
|
-
}
|
|
3424
|
-
const s = document.createElement('script');
|
|
3425
|
-
s.async = true;
|
|
3426
|
-
s.src = src;
|
|
3427
|
-
s.onload = () => {
|
|
3428
|
-
s.setAttribute('data-wep-loaded', 'true');
|
|
3429
|
-
resolve();
|
|
3430
|
-
};
|
|
3431
|
-
s.onerror = () => reject(new Error('loadScript failed: ' + src));
|
|
3432
|
-
document.head.appendChild(s);
|
|
3433
|
-
});
|
|
3434
|
-
loadScriptMemo.set(src, p);
|
|
3435
|
-
p.catch(() => loadScriptMemo.delete(src));
|
|
3436
|
-
return p;
|
|
3437
|
-
}
|
|
3438
|
-
|
|
3439
3559
|
let _printed = false;
|
|
3440
3560
|
/** 在首次引导插件时打印一行运行时标识(非大块 ASCII art)。 */
|
|
3441
3561
|
function printRuntimeBannerOnce() {
|
|
@@ -3485,7 +3605,8 @@ async function fetchStaticManifestViaHttp(ctx) {
|
|
|
3485
3605
|
}
|
|
3486
3606
|
|
|
3487
3607
|
/**
|
|
3488
|
-
*
|
|
3608
|
+
* 当 `ensurePluginHostRoute === true` 且提供 `pluginRoutesParentName` + `hostLayoutComponent` 时,
|
|
3609
|
+
* 注册 `pluginMountPath` + Layout 的命名父路由,供 `router.addRoute(parentName, child)` 挂载插件页。
|
|
3489
3610
|
*/
|
|
3490
3611
|
function routeNameExists(router, name) {
|
|
3491
3612
|
if (!name) {
|
|
@@ -3512,7 +3633,7 @@ function walkRouteNames(routes, name) {
|
|
|
3512
3633
|
return false;
|
|
3513
3634
|
}
|
|
3514
3635
|
function ensurePluginHostRoute$1(router, opts) {
|
|
3515
|
-
if (opts.ensurePluginHostRoute
|
|
3636
|
+
if (opts.ensurePluginHostRoute !== true) {
|
|
3516
3637
|
return;
|
|
3517
3638
|
}
|
|
3518
3639
|
if (!router || typeof router.addRoute !== 'function') {
|
|
@@ -3583,6 +3704,29 @@ function resolveStaticManifestUrlForFetch(url, origin) {
|
|
|
3583
3704
|
}
|
|
3584
3705
|
}
|
|
3585
3706
|
|
|
3707
|
+
/**
|
|
3708
|
+
* 记录当前已成功激活的插件 id,供宿主做只读观测。
|
|
3709
|
+
*/
|
|
3710
|
+
const activatedPluginIds = new Set();
|
|
3711
|
+
function clearActivatedPluginIds() {
|
|
3712
|
+
activatedPluginIds.clear();
|
|
3713
|
+
}
|
|
3714
|
+
function markPluginActivated(pluginId) {
|
|
3715
|
+
if (!pluginId) {
|
|
3716
|
+
return;
|
|
3717
|
+
}
|
|
3718
|
+
activatedPluginIds.add(pluginId);
|
|
3719
|
+
}
|
|
3720
|
+
function markPluginDeactivated(pluginId) {
|
|
3721
|
+
if (!pluginId) {
|
|
3722
|
+
return;
|
|
3723
|
+
}
|
|
3724
|
+
activatedPluginIds.delete(pluginId);
|
|
3725
|
+
}
|
|
3726
|
+
function getActivatedPluginIds$1() {
|
|
3727
|
+
return [...activatedPluginIds];
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3586
3730
|
/**
|
|
3587
3731
|
* 拉取插件清单、加载入口脚本并调用各插件 `activator`。
|
|
3588
3732
|
*/
|
|
@@ -3618,7 +3762,7 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
|
|
|
3618
3762
|
catch (e) {
|
|
3619
3763
|
console.warn('[wep] dev import failed, trying manifest entryUrl', p.id, e);
|
|
3620
3764
|
if (entryUrl && isScriptHostAllowed(entryUrl, hostSet)) {
|
|
3621
|
-
await loadScript(entryUrl);
|
|
3765
|
+
await loadScript(entryUrl, p.id);
|
|
3622
3766
|
}
|
|
3623
3767
|
return;
|
|
3624
3768
|
}
|
|
@@ -3628,7 +3772,40 @@ async function loadPluginEntry(p, entryUrl, devMap, hostSet) {
|
|
|
3628
3772
|
console.warn('[wep] skip (entryUrl not allowed)', p.id, entryUrl);
|
|
3629
3773
|
return;
|
|
3630
3774
|
}
|
|
3631
|
-
await loadScript(entryUrl);
|
|
3775
|
+
await loadScript(entryUrl, p.id);
|
|
3776
|
+
}
|
|
3777
|
+
function coercePriority(value) {
|
|
3778
|
+
if (value === null || value === undefined) {
|
|
3779
|
+
return null;
|
|
3780
|
+
}
|
|
3781
|
+
const n = Number(value);
|
|
3782
|
+
return Number.isFinite(n) ? n : null;
|
|
3783
|
+
}
|
|
3784
|
+
function sortByPriority(plugins) {
|
|
3785
|
+
return plugins
|
|
3786
|
+
.map((entry, index) => ({
|
|
3787
|
+
entry,
|
|
3788
|
+
priority: coercePriority(entry.priority),
|
|
3789
|
+
index
|
|
3790
|
+
}))
|
|
3791
|
+
.sort((a, b) => {
|
|
3792
|
+
const aHas = a.priority !== null;
|
|
3793
|
+
const bHas = b.priority !== null;
|
|
3794
|
+
if (!aHas && !bHas) {
|
|
3795
|
+
return a.index - b.index;
|
|
3796
|
+
}
|
|
3797
|
+
if (!aHas) {
|
|
3798
|
+
return 1;
|
|
3799
|
+
}
|
|
3800
|
+
if (!bHas) {
|
|
3801
|
+
return -1;
|
|
3802
|
+
}
|
|
3803
|
+
if (a.priority !== b.priority) {
|
|
3804
|
+
return a.priority - b.priority;
|
|
3805
|
+
}
|
|
3806
|
+
return a.index - b.index;
|
|
3807
|
+
})
|
|
3808
|
+
.map((decorated) => decorated.entry);
|
|
3632
3809
|
}
|
|
3633
3810
|
async function bootstrapPlugins$1(
|
|
3634
3811
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -3640,8 +3817,9 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3640
3817
|
return;
|
|
3641
3818
|
}
|
|
3642
3819
|
printRuntimeBannerOnce();
|
|
3820
|
+
clearActivatedPluginIds();
|
|
3643
3821
|
const opts = resolveRuntimeOptions$1(runtimeOptions || {});
|
|
3644
|
-
|
|
3822
|
+
setPluginBootstrapRouter(router);
|
|
3645
3823
|
ensurePluginHostRoute$1(router, opts);
|
|
3646
3824
|
const base = String(opts.manifestBase).replace(/\/$/, '');
|
|
3647
3825
|
const isStatic = opts.manifestMode === 'static';
|
|
@@ -3719,11 +3897,8 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3719
3897
|
...(typeof opts.adaptRouteDeclarations === 'function'
|
|
3720
3898
|
? { adaptRouteDeclarations: opts.adaptRouteDeclarations }
|
|
3721
3899
|
: {}),
|
|
3722
|
-
...(typeof opts.
|
|
3723
|
-
? {
|
|
3724
|
-
: {}),
|
|
3725
|
-
...(typeof opts.revokePluginMenuItems === 'function'
|
|
3726
|
-
? { revokePluginMenuItems: opts.revokePluginMenuItems }
|
|
3900
|
+
...(typeof opts.onPluginRoutesContributed === 'function'
|
|
3901
|
+
? { onPluginRoutesContributed: opts.onPluginRoutesContributed }
|
|
3727
3902
|
: {})
|
|
3728
3903
|
};
|
|
3729
3904
|
if (!manifestResult.ok) {
|
|
@@ -3752,22 +3927,30 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3752
3927
|
const maj = coerced ? coerced.major : 0;
|
|
3753
3928
|
const range = `^${maj}.0.0`;
|
|
3754
3929
|
if (!semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
|
|
3755
|
-
console.warn('[wep] host API version mismatch', {
|
|
3930
|
+
console.warn('[wep] manifest host API version mismatch; skip bootstrap', {
|
|
3756
3931
|
host: HOST_PLUGIN_API_VERSION,
|
|
3757
3932
|
manifest: apiVer
|
|
3758
3933
|
});
|
|
3934
|
+
if (shouldShowBootstrapSummary(opts)) {
|
|
3935
|
+
console.info('[wep] bootstrap_summary', {
|
|
3936
|
+
ok: false,
|
|
3937
|
+
reason: 'manifest_host_api_version_mismatch'
|
|
3938
|
+
});
|
|
3939
|
+
}
|
|
3940
|
+
return;
|
|
3759
3941
|
}
|
|
3760
3942
|
}
|
|
3761
3943
|
window.__PLUGIN_ACTIVATORS__ = window.__PLUGIN_ACTIVATORS__ || {};
|
|
3762
|
-
const
|
|
3944
|
+
const originalPlugins = data.plugins || [];
|
|
3945
|
+
const plugins = sortByPriority(originalPlugins);
|
|
3763
3946
|
if (plugins.length === 0) {
|
|
3764
3947
|
const hint = isStatic ? 'check static JSON file and plugins[]' : 'check backend and URL';
|
|
3765
3948
|
console.info('[wep] empty plugin manifest — ' + hint, manifestUrlUsed);
|
|
3766
3949
|
}
|
|
3767
3950
|
const summary = {
|
|
3768
|
-
manifestCount:
|
|
3951
|
+
manifestCount: originalPlugins.length,
|
|
3769
3952
|
activated: 0,
|
|
3770
|
-
|
|
3953
|
+
skipApiVersion: 0,
|
|
3771
3954
|
skipLoad: 0,
|
|
3772
3955
|
skipNoActivator: 0,
|
|
3773
3956
|
activateFail: 0
|
|
@@ -3775,8 +3958,10 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3775
3958
|
for (const p of plugins) {
|
|
3776
3959
|
const range = p.engines && p.engines.host;
|
|
3777
3960
|
if (range && !semver.satisfies(HOST_PLUGIN_API_VERSION, range, { includePrerelease: true })) {
|
|
3778
|
-
console.warn('[wep] skip plugin (engines.host)', p.id, range
|
|
3779
|
-
|
|
3961
|
+
console.warn('[wep] skip plugin (engines.host)', p.id, range, {
|
|
3962
|
+
host: HOST_PLUGIN_API_VERSION
|
|
3963
|
+
});
|
|
3964
|
+
summary.skipApiVersion++;
|
|
3780
3965
|
continue;
|
|
3781
3966
|
}
|
|
3782
3967
|
const entryUrl = p.entryUrl;
|
|
@@ -3812,6 +3997,11 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3812
3997
|
const hostApi = createHostApiFactory(p.id, router, hostKit);
|
|
3813
3998
|
try {
|
|
3814
3999
|
await Promise.resolve(activator(hostApi, { pluginRecord }));
|
|
4000
|
+
markPluginActivated(p.id);
|
|
4001
|
+
const teardownCapableHostApi = hostApi;
|
|
4002
|
+
if (typeof teardownCapableHostApi.onTeardown === 'function') {
|
|
4003
|
+
teardownCapableHostApi.onTeardown(p.id, () => markPluginDeactivated(p.id));
|
|
4004
|
+
}
|
|
3815
4005
|
summary.activated++;
|
|
3816
4006
|
if (typeof opts.onAfterPluginActivate === 'function') {
|
|
3817
4007
|
await Promise.resolve(opts.onAfterPluginActivate({
|
|
@@ -3824,6 +4014,12 @@ createHostApiFactory, runtimeOptions) {
|
|
|
3824
4014
|
}
|
|
3825
4015
|
catch (e) {
|
|
3826
4016
|
console.error('[wep] activate failed', p.id, e);
|
|
4017
|
+
try {
|
|
4018
|
+
disposeWebPlugin(p.id);
|
|
4019
|
+
}
|
|
4020
|
+
catch (disposeErr) {
|
|
4021
|
+
console.warn('[wep] rollback failed after activation error', p.id, disposeErr);
|
|
4022
|
+
}
|
|
3827
4023
|
summary.activateFail++;
|
|
3828
4024
|
if (typeof opts.onPluginActivateError === 'function') {
|
|
3829
4025
|
try {
|
|
@@ -3854,6 +4050,7 @@ var pluginRuntime = /*#__PURE__*/Object.freeze({
|
|
|
3854
4050
|
bootstrapPlugins: bootstrapPlugins$1,
|
|
3855
4051
|
defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
|
|
3856
4052
|
ensurePluginHostRoute: ensurePluginHostRoute$1,
|
|
4053
|
+
getActivatedPluginIds: getActivatedPluginIds$1,
|
|
3857
4054
|
resolveRuntimeOptions: resolveRuntimeOptions$1
|
|
3858
4055
|
});
|
|
3859
4056
|
|
|
@@ -4012,48 +4209,40 @@ var manifestComposer = /*#__PURE__*/Object.freeze({
|
|
|
4012
4209
|
});
|
|
4013
4210
|
|
|
4014
4211
|
/**
|
|
4015
|
-
*
|
|
4016
|
-
* 菜单数据由宿主在 `applyPluginMenuItems` 中自行并入其路由/菜单 state,框架不维护平行菜单列表。
|
|
4017
|
-
*/
|
|
4018
|
-
const registries = Vue.observable({
|
|
4019
|
-
slots: {},
|
|
4020
|
-
slotRevision: 0
|
|
4021
|
-
});
|
|
4022
|
-
|
|
4023
|
-
/**
|
|
4024
|
-
* 按插件 id 收集 `onTeardown` 回调,供 `disposeWebPlugin` 统一执行。
|
|
4212
|
+
* 插件访问后端的受控通道:`fetch` 仅允许落在配置的路径前缀下(默认 `/api/`),默认 `credentials: 'same-origin'`。
|
|
4025
4213
|
*/
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
return;
|
|
4214
|
+
function normalizeBridgePath(input) {
|
|
4215
|
+
if (typeof window === 'undefined') {
|
|
4216
|
+
throw new Error('[wep:bridge] window is unavailable');
|
|
4030
4217
|
}
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
list = [];
|
|
4034
|
-
_byPlugin.set(pluginId, list);
|
|
4218
|
+
if (typeof input !== 'string' || !input.startsWith('/')) {
|
|
4219
|
+
throw new Error('[wep:bridge] path must start with /');
|
|
4035
4220
|
}
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
function runPluginTeardowns(pluginId) {
|
|
4039
|
-
const list = _byPlugin.get(pluginId);
|
|
4040
|
-
if (!list) {
|
|
4041
|
-
return;
|
|
4221
|
+
if (input.includes('\\')) {
|
|
4222
|
+
throw new Error('[wep:bridge] path must not contain backslashes');
|
|
4042
4223
|
}
|
|
4043
|
-
|
|
4044
|
-
|
|
4224
|
+
const url = new URL(input, window.location.origin);
|
|
4225
|
+
if (url.origin !== window.location.origin) {
|
|
4226
|
+
throw new Error('[wep:bridge] cross-origin path is not allowed');
|
|
4227
|
+
}
|
|
4228
|
+
const normalized = url.pathname;
|
|
4229
|
+
const decodedPath = (() => {
|
|
4045
4230
|
try {
|
|
4046
|
-
|
|
4231
|
+
return decodeURIComponent(normalized);
|
|
4047
4232
|
}
|
|
4048
|
-
catch
|
|
4049
|
-
|
|
4233
|
+
catch {
|
|
4234
|
+
return normalized;
|
|
4050
4235
|
}
|
|
4236
|
+
})();
|
|
4237
|
+
const hasTraversalSegment = decodedPath
|
|
4238
|
+
.split('/')
|
|
4239
|
+
.filter(Boolean)
|
|
4240
|
+
.some((segment) => segment === '.' || segment === '..');
|
|
4241
|
+
if (hasTraversalSegment) {
|
|
4242
|
+
throw new Error('[wep:bridge] path traversal is not allowed');
|
|
4051
4243
|
}
|
|
4244
|
+
return normalized + url.search;
|
|
4052
4245
|
}
|
|
4053
|
-
|
|
4054
|
-
/**
|
|
4055
|
-
* 插件访问后端的受控通道:`fetch` 仅允许落在配置的路径前缀下(默认 `/api/`),默认 `credentials: 'same-origin'`。
|
|
4056
|
-
*/
|
|
4057
4246
|
function createRequestBridge(config = {}) {
|
|
4058
4247
|
const raw = Array.isArray(config.allowedPathPrefixes) && config.allowedPathPrefixes.length > 0
|
|
4059
4248
|
? config.allowedPathPrefixes
|
|
@@ -4061,14 +4250,13 @@ function createRequestBridge(config = {}) {
|
|
|
4061
4250
|
const allowedPathPrefixes = raw.map((p) => ensureLeadingPath(p));
|
|
4062
4251
|
return {
|
|
4063
4252
|
async request(path, init = {}) {
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
}
|
|
4067
|
-
const allowed = allowedPathPrefixes.some((p) => path.startsWith(p));
|
|
4253
|
+
const normalizedPath = normalizeBridgePath(path);
|
|
4254
|
+
const pathnameOnly = normalizedPath.split('?')[0];
|
|
4255
|
+
const allowed = allowedPathPrefixes.some((p) => pathnameOnly === p || pathnameOnly.startsWith(`${p.replace(/\/$/, '')}/`));
|
|
4068
4256
|
if (!allowed) {
|
|
4069
|
-
throw new Error('[wep:bridge] path not allowed: ' +
|
|
4257
|
+
throw new Error('[wep:bridge] path not allowed: ' + normalizedPath);
|
|
4070
4258
|
}
|
|
4071
|
-
return fetch(
|
|
4259
|
+
return fetch(normalizedPath, {
|
|
4072
4260
|
credentials: 'same-origin',
|
|
4073
4261
|
...init
|
|
4074
4262
|
});
|
|
@@ -4077,10 +4265,83 @@ function createRequestBridge(config = {}) {
|
|
|
4077
4265
|
}
|
|
4078
4266
|
|
|
4079
4267
|
/**
|
|
4080
|
-
*
|
|
4268
|
+
* 按插件记录已贡献路由的可序列化快照,避免宿主依赖 router 内部 matcher 形态。
|
|
4269
|
+
*/
|
|
4270
|
+
const contributedRoutesByPlugin = new Map();
|
|
4271
|
+
function sanitizeValue(value) {
|
|
4272
|
+
if (value == null) {
|
|
4273
|
+
return value;
|
|
4274
|
+
}
|
|
4275
|
+
if (typeof value === 'function') {
|
|
4276
|
+
return undefined;
|
|
4277
|
+
}
|
|
4278
|
+
if (Array.isArray(value)) {
|
|
4279
|
+
return value
|
|
4280
|
+
.map((item) => sanitizeValue(item))
|
|
4281
|
+
.filter((item) => item !== undefined);
|
|
4282
|
+
}
|
|
4283
|
+
if (typeof value !== 'object') {
|
|
4284
|
+
return value;
|
|
4285
|
+
}
|
|
4286
|
+
const out = {};
|
|
4287
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
4288
|
+
if (key === 'component' || key === 'components') {
|
|
4289
|
+
continue;
|
|
4290
|
+
}
|
|
4291
|
+
const sanitized = sanitizeValue(raw);
|
|
4292
|
+
if (sanitized !== undefined) {
|
|
4293
|
+
out[key] = sanitized;
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
return out;
|
|
4297
|
+
}
|
|
4298
|
+
function sanitizeContributedRoutes(routes) {
|
|
4299
|
+
return routes
|
|
4300
|
+
.map((route) => sanitizeValue(route))
|
|
4301
|
+
.filter((route) => !!route && typeof route === 'object');
|
|
4302
|
+
}
|
|
4303
|
+
function recordContributedRoutesForPlugin(pluginId, routes) {
|
|
4304
|
+
const sanitized = sanitizeContributedRoutes(routes);
|
|
4305
|
+
const current = contributedRoutesByPlugin.get(pluginId) || [];
|
|
4306
|
+
const next = current.concat(sanitized);
|
|
4307
|
+
contributedRoutesByPlugin.set(pluginId, next);
|
|
4308
|
+
return next.map((route) => sanitizeValue(route));
|
|
4309
|
+
}
|
|
4310
|
+
function getContributedRoutesForPlugin(pluginId) {
|
|
4311
|
+
const routes = contributedRoutesByPlugin.get(pluginId) || [];
|
|
4312
|
+
return routes.map((route) => sanitizeValue(route));
|
|
4313
|
+
}
|
|
4314
|
+
function clearContributedRoutesForPlugin(pluginId) {
|
|
4315
|
+
contributedRoutesByPlugin.delete(pluginId);
|
|
4316
|
+
}
|
|
4317
|
+
|
|
4318
|
+
/**
|
|
4319
|
+
* 构造插件 `activator(hostApi)` 使用的宿主 API:路由、扩展点、资源与受控请求桥。
|
|
4081
4320
|
*/
|
|
4082
4321
|
let slotItemKeySeq = 0;
|
|
4083
4322
|
let routeSynthSeq = 0;
|
|
4323
|
+
function decorateRouteTreeWithPluginMeta(pluginId, route) {
|
|
4324
|
+
const meta = route.meta && typeof route.meta === 'object' && !Array.isArray(route.meta)
|
|
4325
|
+
? route.meta
|
|
4326
|
+
: {};
|
|
4327
|
+
const wepMeta = meta.__wep && typeof meta.__wep === 'object' && !Array.isArray(meta.__wep)
|
|
4328
|
+
? meta.__wep
|
|
4329
|
+
: {};
|
|
4330
|
+
const nextChildren = Array.isArray(route.children)
|
|
4331
|
+
? route.children.map((child) => decorateRouteTreeWithPluginMeta(pluginId, child))
|
|
4332
|
+
: route.children;
|
|
4333
|
+
return {
|
|
4334
|
+
...route,
|
|
4335
|
+
meta: {
|
|
4336
|
+
...meta,
|
|
4337
|
+
__wep: {
|
|
4338
|
+
...wepMeta,
|
|
4339
|
+
pluginId
|
|
4340
|
+
}
|
|
4341
|
+
},
|
|
4342
|
+
...(nextChildren ? { children: nextChildren } : {})
|
|
4343
|
+
};
|
|
4344
|
+
}
|
|
4084
4345
|
function analyzeRouteInputTree(nodes) {
|
|
4085
4346
|
let hasDecl = false;
|
|
4086
4347
|
let hasCfg = false;
|
|
@@ -4122,25 +4383,71 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4122
4383
|
const parentName = typeof hostKitOptions.pluginRoutesParentName === 'string'
|
|
4123
4384
|
? hostKitOptions.pluginRoutesParentName.trim()
|
|
4124
4385
|
: '';
|
|
4386
|
+
function rollbackRegisteredTopRoutes(routes) {
|
|
4387
|
+
for (let i = routes.length - 1; i >= 0; i--) {
|
|
4388
|
+
const route = routes[i];
|
|
4389
|
+
if (typeof route.dispose === 'function') {
|
|
4390
|
+
try {
|
|
4391
|
+
route.dispose();
|
|
4392
|
+
continue;
|
|
4393
|
+
}
|
|
4394
|
+
catch (e) {
|
|
4395
|
+
console.warn('[wep] rollback route disposer failed', route.name, e);
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
if (typeof router.removeRoute === 'function') {
|
|
4399
|
+
try {
|
|
4400
|
+
router.removeRoute(route.name);
|
|
4401
|
+
}
|
|
4402
|
+
catch (e) {
|
|
4403
|
+
console.warn('[wep] rollback removeRoute failed', route.name, e);
|
|
4404
|
+
}
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4125
4408
|
function applyInternalRegister(rawRouteConfigs) {
|
|
4126
4409
|
const wrapped = rawRouteConfigs.map((r) => ({
|
|
4127
|
-
...r,
|
|
4128
|
-
name: r.name || `${routeSynthNamePrefix}${pluginId}_${routeSynthSeq++}
|
|
4129
|
-
meta: { ...(r.meta || {}), pluginId }
|
|
4410
|
+
...decorateRouteTreeWithPluginMeta(pluginId, r),
|
|
4411
|
+
name: r.name || `${routeSynthNamePrefix}${pluginId}_${routeSynthSeq++}`
|
|
4130
4412
|
}));
|
|
4131
4413
|
if (typeof router.addRoute !== 'function') {
|
|
4132
4414
|
throw new Error('[wep] vue-router 3.5+ 必需:请使用 router.addRoute(不再支持 addRoutes)');
|
|
4133
4415
|
}
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4416
|
+
const registeredTopRoutes = [];
|
|
4417
|
+
try {
|
|
4418
|
+
if (parentName) {
|
|
4419
|
+
for (const r of wrapped) {
|
|
4420
|
+
const dispose = router.addRoute(parentName, r);
|
|
4421
|
+
registeredTopRoutes.push({
|
|
4422
|
+
name: String(r.name),
|
|
4423
|
+
dispose: typeof dispose === 'function' ? dispose : undefined
|
|
4424
|
+
});
|
|
4425
|
+
}
|
|
4137
4426
|
}
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4427
|
+
else {
|
|
4428
|
+
for (const r of wrapped) {
|
|
4429
|
+
const dispose = router.addRoute(r);
|
|
4430
|
+
registeredTopRoutes.push({
|
|
4431
|
+
name: String(r.name),
|
|
4432
|
+
dispose: typeof dispose === 'function' ? dispose : undefined
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4142
4435
|
}
|
|
4143
4436
|
}
|
|
4437
|
+
catch (e) {
|
|
4438
|
+
rollbackRegisteredTopRoutes(registeredTopRoutes);
|
|
4439
|
+
throw e;
|
|
4440
|
+
}
|
|
4441
|
+
recordPluginTopRoutes(pluginId, registeredTopRoutes);
|
|
4442
|
+
const contributedRoutes = recordContributedRoutesForPlugin(pluginId, wrapped);
|
|
4443
|
+
if (contributedRoutes.length > 0 && typeof hostKitOptions.onPluginRoutesContributed === 'function') {
|
|
4444
|
+
hostKitOptions.onPluginRoutesContributed({
|
|
4445
|
+
pluginId,
|
|
4446
|
+
router,
|
|
4447
|
+
routes: wrapped,
|
|
4448
|
+
contributedRoutes
|
|
4449
|
+
});
|
|
4450
|
+
}
|
|
4144
4451
|
}
|
|
4145
4452
|
function injectStylesheet(href) {
|
|
4146
4453
|
const link = document.createElement('link');
|
|
@@ -4163,6 +4470,9 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4163
4470
|
const hostContext = hostKitOptions.hostContext != null && typeof hostKitOptions.hostContext === 'object'
|
|
4164
4471
|
? hostKitOptions.hostContext
|
|
4165
4472
|
: Object.freeze({});
|
|
4473
|
+
registerPluginTeardown(pluginId, () => {
|
|
4474
|
+
clearContributedRoutesForPlugin(pluginId);
|
|
4475
|
+
});
|
|
4166
4476
|
return {
|
|
4167
4477
|
hostPluginApiVersion: HOST_PLUGIN_API_VERSION,
|
|
4168
4478
|
/** 宿主注入的只读依赖(store、router、开放能力等),见 `resolveRuntimeOptions.hostContext` */
|
|
@@ -4210,16 +4520,6 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4210
4520
|
applyInternalRegister(configs);
|
|
4211
4521
|
}
|
|
4212
4522
|
},
|
|
4213
|
-
registerMenuItems(items) {
|
|
4214
|
-
const apply = hostKitOptions.applyPluginMenuItems;
|
|
4215
|
-
if (typeof apply !== 'function') {
|
|
4216
|
-
throw new Error('[wep] registerMenuItems 需要宿主在 resolveRuntimeOptions 中提供 applyPluginMenuItems,将菜单数据并入宿主侧栏/目录 state(框架不再维护 registries.menus)');
|
|
4217
|
-
}
|
|
4218
|
-
const list = Array.isArray(items) ? items : [];
|
|
4219
|
-
const enriched = list.map((item) => ({ ...item, pluginId }));
|
|
4220
|
-
enriched.sort((a, b) => (a.order != null ? Number(a.order) : 0) - (b.order != null ? Number(b.order) : 0));
|
|
4221
|
-
apply({ pluginId, items: enriched });
|
|
4222
|
-
},
|
|
4223
4523
|
registerSlotComponents(pointId, components) {
|
|
4224
4524
|
if (!pointId) {
|
|
4225
4525
|
return;
|
|
@@ -4253,6 +4553,7 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4253
4553
|
registerSanitizedHtmlSnippet() {
|
|
4254
4554
|
throw new Error('registerSanitizedHtmlSnippet is not enabled');
|
|
4255
4555
|
},
|
|
4556
|
+
getContributedRoutes: () => getContributedRoutesForPlugin(pluginId),
|
|
4256
4557
|
getBridge: () => bridge,
|
|
4257
4558
|
onTeardown(_pluginId, fn) {
|
|
4258
4559
|
if (typeof fn === 'function') {
|
|
@@ -4262,43 +4563,6 @@ function createHostApi(pluginId, router, hostKitOptions = {}) {
|
|
|
4262
4563
|
};
|
|
4263
4564
|
}
|
|
4264
4565
|
|
|
4265
|
-
/**
|
|
4266
|
-
* 卸载单个插件:执行 teardown、清理注册表与 activator、移除带 `data-plugin-asset` 的 DOM。
|
|
4267
|
-
* 注意:Vue Router 3 无公开 `removeRoute`,动态路由通常需整页刷新或宿主自行维护。
|
|
4268
|
-
*/
|
|
4269
|
-
function disposeWebPlugin(pluginId) {
|
|
4270
|
-
if (!pluginId || typeof pluginId !== 'string') {
|
|
4271
|
-
return;
|
|
4272
|
-
}
|
|
4273
|
-
runPluginTeardowns(pluginId);
|
|
4274
|
-
revokePluginMenusIfConfigured(pluginId);
|
|
4275
|
-
const slots = registries.slots;
|
|
4276
|
-
for (const pointId of Object.keys(slots)) {
|
|
4277
|
-
const list = slots[pointId];
|
|
4278
|
-
if (!Array.isArray(list)) {
|
|
4279
|
-
continue;
|
|
4280
|
-
}
|
|
4281
|
-
const next = list.filter((x) => x.pluginId !== pluginId);
|
|
4282
|
-
if (next.length === 0) {
|
|
4283
|
-
Vue.delete(slots, pointId);
|
|
4284
|
-
}
|
|
4285
|
-
else if (next.length !== list.length) {
|
|
4286
|
-
Vue.set(slots, pointId, next);
|
|
4287
|
-
}
|
|
4288
|
-
}
|
|
4289
|
-
registries.slotRevision++;
|
|
4290
|
-
if (typeof window !== 'undefined' && window.__PLUGIN_ACTIVATORS__) {
|
|
4291
|
-
delete window.__PLUGIN_ACTIVATORS__[pluginId];
|
|
4292
|
-
}
|
|
4293
|
-
if (typeof document !== 'undefined') {
|
|
4294
|
-
document.querySelectorAll('[data-plugin-asset]').forEach((el) => {
|
|
4295
|
-
if (el.getAttribute('data-plugin-asset') === pluginId) {
|
|
4296
|
-
el.remove();
|
|
4297
|
-
}
|
|
4298
|
-
});
|
|
4299
|
-
}
|
|
4300
|
-
}
|
|
4301
|
-
|
|
4302
4566
|
/**
|
|
4303
4567
|
* 布局中的扩展点占位;插件通过 `hostApi.registerSlotComponents(pointId, ...)` 注入组件。
|
|
4304
4568
|
* 样式由宿主全局 CSS 覆盖 `.extension-point` / `.plugin-point-error`。
|
|
@@ -4351,31 +4615,15 @@ var ExtensionPoint = Vue.extend({
|
|
|
4351
4615
|
}
|
|
4352
4616
|
});
|
|
4353
4617
|
|
|
4354
|
-
/**
|
|
4355
|
-
* 注册全局 `ExtensionPoint` 并异步拉取清单、激活插件。
|
|
4356
|
-
*/
|
|
4357
|
-
/**
|
|
4358
|
-
* @param Vue Vue 构造函数
|
|
4359
|
-
* @param router vue-router 实例
|
|
4360
|
-
* @param options 传给 `resolveRuntimeOptions`;可含 `env` 以读取 `VITE_*`
|
|
4361
|
-
*/
|
|
4362
4618
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4363
4619
|
function installWebExtendPluginVue2(Vue, router, options) {
|
|
4364
|
-
const opts = options || {};
|
|
4365
|
-
const { env: injectedEnv, ...runtimeUser } = opts;
|
|
4366
|
-
if (injectedEnv && typeof injectedEnv === 'object') {
|
|
4367
|
-
setWebExtendPluginEnv(injectedEnv);
|
|
4368
|
-
}
|
|
4369
4620
|
if (Vue && ExtensionPoint) {
|
|
4370
4621
|
Vue.component('ExtensionPoint', ExtensionPoint);
|
|
4371
4622
|
}
|
|
4372
|
-
const runtime = resolveRuntimeOptions$1(
|
|
4623
|
+
const runtime = resolveRuntimeOptions$1(options || {});
|
|
4373
4624
|
return bootstrapPlugins$1(router, (id, r, kit) => createHostApi(id, r, kit || {}), runtime);
|
|
4374
4625
|
}
|
|
4375
4626
|
|
|
4376
|
-
/**
|
|
4377
|
-
* Vue CLI + 统一 axios(如 RuoYi `utils/request`)场景的 `install` 预设。
|
|
4378
|
-
*/
|
|
4379
4627
|
function resolveManifestPathUnderApiBase(manifestUrl, apiBase) {
|
|
4380
4628
|
const base = String(apiBase !== undefined
|
|
4381
4629
|
? apiBase
|
|
@@ -4385,18 +4633,13 @@ function resolveManifestPathUnderApiBase(manifestUrl, apiBase) {
|
|
|
4385
4633
|
if (typeof window === 'undefined') {
|
|
4386
4634
|
return '/api/frontend-plugins';
|
|
4387
4635
|
}
|
|
4388
|
-
const
|
|
4389
|
-
let path =
|
|
4636
|
+
const url = new URL(manifestUrl, window.location.origin);
|
|
4637
|
+
let path = url.pathname + url.search;
|
|
4390
4638
|
if (base && path.startsWith(base)) {
|
|
4391
4639
|
path = path.slice(base.length) || '/';
|
|
4392
4640
|
}
|
|
4393
4641
|
return path;
|
|
4394
4642
|
}
|
|
4395
|
-
/**
|
|
4396
|
-
* 常见 Java 清单路径:与 `VUE_APP_BASE_API` 拼接为 `${base}/frontend-plugins`。
|
|
4397
|
-
* 若后端使用 `/api/frontend-plugins` 段,请在 extra 中显式传入 `manifestListPath`。
|
|
4398
|
-
*/
|
|
4399
|
-
const defaultVueCliJavaManifestListPath = '/frontend-plugins';
|
|
4400
4643
|
function bridgePrefixesFromVueCliEnv() {
|
|
4401
4644
|
const base = (typeof process !== 'undefined' && process.env && process.env.VUE_APP_BASE_API
|
|
4402
4645
|
? String(process.env.VUE_APP_BASE_API)
|
|
@@ -4417,13 +4660,10 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
|
|
|
4417
4660
|
? String(extra.manifestBase).replace(/\/$/, '')
|
|
4418
4661
|
: '';
|
|
4419
4662
|
const stripBase = userBase || envBase;
|
|
4420
|
-
const manifestMode = resolveManifestModeFromInputs(extraManifestMode);
|
|
4421
|
-
const staticManifestUrl = resolveStaticManifestUrlFromInputs(extraStaticManifestUrl);
|
|
4422
4663
|
const fetchManifestApi = async (ctx) => {
|
|
4423
4664
|
try {
|
|
4424
|
-
const url = resolveManifestPathUnderApiBase(ctx.manifestUrl, stripBase);
|
|
4425
4665
|
const body = await request({
|
|
4426
|
-
url,
|
|
4666
|
+
url: resolveManifestPathUnderApiBase(ctx.manifestUrl, stripBase),
|
|
4427
4667
|
method: 'get'
|
|
4428
4668
|
});
|
|
4429
4669
|
const data = unwrapNestedManifestBody(body);
|
|
@@ -4436,17 +4676,18 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
|
|
|
4436
4676
|
}
|
|
4437
4677
|
return { ok: true, data };
|
|
4438
4678
|
}
|
|
4439
|
-
catch (
|
|
4440
|
-
return { ok: false, error
|
|
4679
|
+
catch (error) {
|
|
4680
|
+
return { ok: false, error, data: null };
|
|
4441
4681
|
}
|
|
4442
4682
|
};
|
|
4443
|
-
const
|
|
4683
|
+
const manifestMode = resolveManifestModeFromInputs(extraManifestMode);
|
|
4684
|
+
const staticManifestUrl = resolveStaticManifestUrlFromInputs(extraStaticManifestUrl);
|
|
4444
4685
|
const fetchManifest = typeof userFetchManifest === 'function'
|
|
4445
4686
|
? userFetchManifest
|
|
4446
4687
|
: manifestMode === 'static'
|
|
4447
|
-
?
|
|
4688
|
+
? fetchStaticManifestViaHttp
|
|
4448
4689
|
: fetchManifestApi;
|
|
4449
|
-
const
|
|
4690
|
+
const options = {
|
|
4450
4691
|
manifestBase: stripBase || undefined,
|
|
4451
4692
|
bridgeAllowedPathPrefixes: bridgePrefixesFromVueCliEnv(),
|
|
4452
4693
|
manifestMode,
|
|
@@ -4457,105 +4698,14 @@ function createVueCliAxiosInstallOptions(deps, extra = {}) {
|
|
|
4457
4698
|
const listPath = typeof process !== 'undefined' &&
|
|
4458
4699
|
process.env &&
|
|
4459
4700
|
process.env[webExtendPluginEnvKeys.manifestPathAlt];
|
|
4460
|
-
if (listPath &&
|
|
4461
|
-
|
|
4701
|
+
if (listPath && options.manifestListPath === undefined && extra.manifestListPath === undefined) {
|
|
4702
|
+
options.manifestListPath = String(process.env[webExtendPluginEnvKeys.manifestPathAlt]);
|
|
4462
4703
|
}
|
|
4463
|
-
return
|
|
4704
|
+
return options;
|
|
4464
4705
|
}
|
|
4465
|
-
/**
|
|
4466
|
-
* 少样板接入:`hostContext` 自动含 `router`(及可选 `store`)、`isDev` 与常见 `manifestListPath` 默认值。
|
|
4467
|
-
* 更多运行时字段仍可通过 `extra` 覆盖。
|
|
4468
|
-
*/
|
|
4469
|
-
function createVueCliAxiosQuickInstallOptions(
|
|
4470
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4471
|
-
router, deps, extra = {}) {
|
|
4472
|
-
const { request, hostLayoutComponent, store, hostContext: depHostContext, applyPluginMenuItems, revokePluginMenuItems } = deps;
|
|
4473
|
-
const { hostContext: extraHostContext, isDev: extraIsDev, manifestListPath: extraListPath, ...restExtra } = extra;
|
|
4474
|
-
const hostContext = {
|
|
4475
|
-
router,
|
|
4476
|
-
...(store !== undefined ? { store } : {}),
|
|
4477
|
-
...(depHostContext && typeof depHostContext === 'object' ? depHostContext : {}),
|
|
4478
|
-
...(extraHostContext && typeof extraHostContext === 'object' ? extraHostContext : {})
|
|
4479
|
-
};
|
|
4480
|
-
const manifestListPath = extraListPath !== undefined && String(extraListPath).trim() !== ''
|
|
4481
|
-
? String(extraListPath)
|
|
4482
|
-
: defaultVueCliJavaManifestListPath;
|
|
4483
|
-
return createVueCliAxiosInstallOptions({ request }, {
|
|
4484
|
-
hostLayoutComponent,
|
|
4485
|
-
hostContext,
|
|
4486
|
-
applyPluginMenuItems,
|
|
4487
|
-
revokePluginMenuItems,
|
|
4488
|
-
isDev: extraIsDev !== undefined ? Boolean(extraIsDev) : resolveBundledIsDev(),
|
|
4489
|
-
manifestListPath,
|
|
4490
|
-
...restExtra
|
|
4491
|
-
});
|
|
4492
|
-
}
|
|
4493
|
-
const presetVueCliAxios = Object.freeze({
|
|
4494
|
-
id: 'vue-cli-axios',
|
|
4495
|
-
description: 'Vue CLI + axios request for API manifest; optional manifestMode=static uses fetch',
|
|
4496
|
-
createInstallOptions: createVueCliAxiosInstallOptions,
|
|
4497
|
-
createQuickInstallOptions: createVueCliAxiosQuickInstallOptions,
|
|
4498
|
-
defaultJavaManifestListPath: defaultVueCliJavaManifestListPath,
|
|
4499
|
-
manifestPathForApiBase: resolveManifestPathUnderApiBase,
|
|
4500
|
-
unwrapManifestBody: unwrapNestedManifestBody
|
|
4501
|
-
});
|
|
4502
4706
|
|
|
4503
|
-
|
|
4504
|
-
* Vue CLI + axios 场景的一键安装:合并 hostContext、默认清单路径与 IIFE 全局 Vue。
|
|
4505
|
-
*/
|
|
4506
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4507
|
-
function installVueCliAxiosWebPlugins(Vue, router, deps, extra) {
|
|
4508
|
-
const exposeGlobalVue = extra?.exposeGlobalVue !== false;
|
|
4509
|
-
if (exposeGlobalVue && typeof window !== 'undefined') {
|
|
4510
|
-
window.Vue = Vue;
|
|
4511
|
-
}
|
|
4512
|
-
const { exposeGlobalVue: _skip, ...restExtra } = extra || {};
|
|
4513
|
-
const opts = createVueCliAxiosQuickInstallOptions(router, deps, restExtra);
|
|
4514
|
-
return installWebExtendPluginVue2(Vue, router, opts);
|
|
4515
|
-
}
|
|
4516
|
-
|
|
4517
|
-
/**
|
|
4518
|
-
* 包对外稳定导出与 `WebExtendPluginVue2` 命名空间。
|
|
4519
|
-
*/
|
|
4520
|
-
const { bootstrapPlugins, defaultFetchWebPluginManifest, resolveRuntimeOptions, ensurePluginHostRoute } = pluginRuntime;
|
|
4707
|
+
const { bootstrapPlugins, defaultFetchWebPluginManifest, resolveRuntimeOptions, ensurePluginHostRoute, getActivatedPluginIds } = pluginRuntime;
|
|
4521
4708
|
const { composeManifestFetch, manifestFetchCacheMiddleware, wrapManifestFetchWithCache } = manifestComposer;
|
|
4522
|
-
const WebExtendPluginVue2 = Object.freeze({
|
|
4523
|
-
install: installWebExtendPluginVue2,
|
|
4524
|
-
runtime: Object.freeze({
|
|
4525
|
-
bootstrapPlugins: bootstrapPlugins$1,
|
|
4526
|
-
resolveRuntimeOptions: resolveRuntimeOptions$1,
|
|
4527
|
-
ensurePluginHostRoute: ensurePluginHostRoute$1,
|
|
4528
|
-
defaultFetchWebPluginManifest: defaultFetchWebPluginManifest$1,
|
|
4529
|
-
composeManifestFetch: composeManifestFetch$1,
|
|
4530
|
-
manifestFetchCacheMiddleware: manifestFetchCacheMiddleware$1,
|
|
4531
|
-
wrapManifestFetchWithCache: wrapManifestFetchWithCache$1
|
|
4532
|
-
}),
|
|
4533
|
-
host: Object.freeze({
|
|
4534
|
-
createHostApi,
|
|
4535
|
-
disposeWebPlugin,
|
|
4536
|
-
createRequestBridge,
|
|
4537
|
-
registries
|
|
4538
|
-
}),
|
|
4539
|
-
config: Object.freeze({
|
|
4540
|
-
defaultWebExtendPluginRuntime,
|
|
4541
|
-
setWebExtendPluginEnv,
|
|
4542
|
-
webExtendPluginEnvKeys,
|
|
4543
|
-
defaultManifestFetchCache,
|
|
4544
|
-
defaultManifestMode,
|
|
4545
|
-
routeSynthNamePrefix,
|
|
4546
|
-
peerMinimumVersions
|
|
4547
|
-
}),
|
|
4548
|
-
constants: Object.freeze({
|
|
4549
|
-
HOST_PLUGIN_API_VERSION,
|
|
4550
|
-
RUNTIME_CONSOLE_LABEL
|
|
4551
|
-
}),
|
|
4552
|
-
components: Object.freeze({
|
|
4553
|
-
ExtensionPoint
|
|
4554
|
-
}),
|
|
4555
|
-
presets: Object.freeze({
|
|
4556
|
-
vueCliAxios: presetVueCliAxios
|
|
4557
|
-
})
|
|
4558
|
-
});
|
|
4559
4709
|
|
|
4560
|
-
export { ExtensionPoint, HOST_PLUGIN_API_VERSION, RUNTIME_CONSOLE_LABEL,
|
|
4710
|
+
export { ExtensionPoint, HOST_PLUGIN_API_VERSION, RUNTIME_CONSOLE_LABEL, bootstrapPlugins, composeManifestFetch, createHostApi, createRequestBridge, createVueCliAxiosInstallOptions, defaultFetchWebPluginManifest, defaultManifestFetchCache, defaultManifestMode, defaultWebExtendPluginRuntime, disposeWebPlugin, ensurePluginHostRoute, getActivatedPluginIds, getContributedRoutesForPlugin, getRegisteredTopRouteNamesForPlugin, installWebExtendPluginVue2, manifestFetchCacheMiddleware, peerMinimumVersions, registries, resolveRuntimeOptions, routeSynthNamePrefix, setWebExtendPluginEnv, webExtendPluginEnvKeys, wrapManifestFetchWithCache };
|
|
4561
4711
|
//# sourceMappingURL=index.mjs.map
|