@fulgurjs/federation 4.0.0 → 4.1.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/CHANGELOG.md +17 -0
- package/README.md +286 -95
- package/dist/{chunk-X4LZDAZQ.js → chunk-75PP6EAK.js} +1 -1
- package/dist/chunk-NUZKZUTD.js +144 -0
- package/dist/chunk-TSXZ7TWA.js +144 -0
- package/dist/{chunk-3VBYOVH4.js → chunk-WBCXLR2N.js} +9 -1
- package/dist/cli.js +584 -127
- package/dist/config.cjs +63 -4
- package/dist/config.d.cts +31 -4
- package/dist/config.d.ts +31 -4
- package/dist/config.js +62 -4
- package/dist/context.cjs +12 -2
- package/dist/context.d.cts +21 -5
- package/dist/context.d.ts +21 -5
- package/dist/context.js +12 -3
- package/dist/index.cjs +72 -21
- package/dist/index.d.cts +2 -92
- package/dist/index.d.ts +2 -92
- package/dist/index.js +72 -21
- package/dist/options-CRGpSmVj.d.cts +100 -0
- package/dist/options-CRGpSmVj.d.ts +100 -0
- package/dist/pages.js +4 -139
- package/dist/runtime-entry.d.ts +86 -5
- package/dist/runtime-entry.js +3 -3
- package/dist/runtime.js +10 -3
- package/dist/vue-adapter.cjs +241 -0
- package/dist/vue-adapter.d.cts +47 -1
- package/dist/vue-adapter.d.ts +47 -1
- package/dist/vue-adapter.js +4 -1
- package/dist/vue.cjs +402 -3
- package/dist/vue.d.cts +6 -2
- package/dist/vue.d.ts +6 -2
- package/dist/vue.js +2 -1
- package/docs//350/277/201/347/247/273/346/214/207/345/215/227.md +11 -8
- package/package.json +1 -1
- package/dist/chunk-AYS56IRF.js +0 -42
package/dist/runtime-entry.d.ts
CHANGED
|
@@ -93,6 +93,23 @@ interface RemoteDebugInfo {
|
|
|
93
93
|
status: 'idle' | 'loading' | 'loaded' | 'failed';
|
|
94
94
|
lastLoadMs?: number;
|
|
95
95
|
error?: string;
|
|
96
|
+
/** setup 生命周期状态(§3.3.1):none=未配置 / pending=执行中 / ready=应用级已完成 / failed=失败可重试 */
|
|
97
|
+
setup?: 'none' | 'pending' | 'ready' | 'failed';
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 远程初始化上下文(federation({ setup }) 模块的默认导出与具名 onSession 收到的参数)。
|
|
101
|
+
* appContext 是调用时从页面级 AppContext 取得的当前快照(不缓存旧引用);
|
|
102
|
+
* signal 在登录代次变化或退出清理时失效,业务异步写回前必须检查 aborted。
|
|
103
|
+
*/
|
|
104
|
+
interface RemoteSetupContext {
|
|
105
|
+
appContext: Readonly<Record<string, any>>;
|
|
106
|
+
sessionKey?: string;
|
|
107
|
+
signal: AbortSignal;
|
|
108
|
+
}
|
|
109
|
+
/** setup 模块的固定导出契约:默认导出必为函数;onSession 可选且必须是函数 */
|
|
110
|
+
interface RemoteSetupModule {
|
|
111
|
+
default: (ctx: RemoteSetupContext) => void | Promise<void>;
|
|
112
|
+
onSession?: (ctx: RemoteSetupContext) => void | Promise<void>;
|
|
96
113
|
}
|
|
97
114
|
declare function createRuntime(): {
|
|
98
115
|
shareScopeMap: ShareScopeMap;
|
|
@@ -113,6 +130,7 @@ declare function createRuntime(): {
|
|
|
113
130
|
remote: string;
|
|
114
131
|
module: string;
|
|
115
132
|
};
|
|
133
|
+
clearSessionState: () => void;
|
|
116
134
|
};
|
|
117
135
|
type FgRuntime = ReturnType<typeof createRuntime>;
|
|
118
136
|
/** 插件版本(与 package.json 同步维护于 src/version.ts,测试拦截漂移;用于跨源副本一致性诊断) */
|
|
@@ -170,26 +188,42 @@ interface AppContext {
|
|
|
170
188
|
locale?: unknown;
|
|
171
189
|
/** 事件/方法池:events.main.* 宿主提供、events.bpm.* / events.lowcode.* 子应用反向注册 */
|
|
172
190
|
events?: Record<string, any>;
|
|
191
|
+
/**
|
|
192
|
+
* 非敏感登录代次 ID:宿主每次成功登录/重新登录生成新值,token 刷新但会话未变时沿用。
|
|
193
|
+
* 远程 onSession 按它去重(同一代次只执行一次)。不是用户 ID、不是 token、不作为授权凭证。
|
|
194
|
+
* 远程声明了 onSession 时必填(缺省即 MFU-013)。
|
|
195
|
+
*/
|
|
196
|
+
sessionKey?: string;
|
|
173
197
|
/** 项目扩展位(formUrl/baseUrl 等自定义键,按需自行提供) */
|
|
174
198
|
[key: string]: unknown;
|
|
175
199
|
}
|
|
176
200
|
/**
|
|
177
201
|
* 宿主写入跨应用上下文(merge 语义,幂等可多次调用,后写覆盖同键)。
|
|
178
202
|
*
|
|
179
|
-
* 宿主桥(host/src/fulgurjs/host/bridge.ts
|
|
180
|
-
* provideAppContext({ user,
|
|
203
|
+
* 宿主桥(host/src/fulgurjs/host/bridge.ts)在登录完成后调用:
|
|
204
|
+
* provideAppContext({ user, getToken, store, hostApp, locale, sessionKey, events: { main: mainEvents }, ... })
|
|
205
|
+
* 登录态变化(换账号/token 刷新)时再次调用即以最新值覆盖同键——不要用布尔闩锁把桥封成只跑一次。
|
|
181
206
|
*/
|
|
182
207
|
declare function provideAppContext(config: Partial<AppContext> & Record<string, unknown>): void;
|
|
208
|
+
/**
|
|
209
|
+
* 宿主退出登录时清理:删除整个 context 对象(旧 user/getToken 引用一并失效),并通知
|
|
210
|
+
* 运行时单例作废全部远程的会话信号与 onSession 去重状态(下次登录必须重新执行 onSession)。
|
|
211
|
+
*
|
|
212
|
+
* 边界(契约):只清 AppContext 与会话初始化状态;不重置远程模块缓存、共享模块图与
|
|
213
|
+
* 应用级 setup 注册(同一页面内组件与共享实例继续复用)。页面无运行时单例时静默幂等
|
|
214
|
+
* (退出动作不依赖联邦形态,不得因缺运行时而打断登出流程)。
|
|
215
|
+
*/
|
|
216
|
+
declare function clearAppContext(): void;
|
|
183
217
|
/**
|
|
184
218
|
* 读上下文快照(传输层快照:顶层 merge 结果 + 嵌套对象引用共享)。
|
|
185
219
|
* 页面无运行时单例(独立直开远程页)→ CC-002 显式抛错,不静默回空。
|
|
186
220
|
*/
|
|
187
221
|
declare function getAppContext(): AppContext;
|
|
188
222
|
/**
|
|
189
|
-
* 显式校验读取(远程 boot 消费入口):任一键缺失 → CC-001 三段式。
|
|
223
|
+
* 显式校验读取(远程 setup/boot 消费入口):任一键缺失 → CC-001 三段式。
|
|
190
224
|
*
|
|
191
225
|
* 用法:const { store, user, hostApp } = requireAppContext('store', 'user', 'hostApp')
|
|
192
|
-
*
|
|
226
|
+
* 时序契约:宿主桥先 provide,远程 setup/onSession 后 require——违反即在初始化处显式失败。
|
|
193
227
|
*/
|
|
194
228
|
declare function requireAppContext(...keys: string[]): AppContext;
|
|
195
229
|
|
|
@@ -267,8 +301,55 @@ interface RemoteComponentOptions {
|
|
|
267
301
|
delay?: number;
|
|
268
302
|
timeout?: number;
|
|
269
303
|
}
|
|
304
|
+
interface HostPagesOptions {
|
|
305
|
+
/** 页面表(宿主路由与布局共用的唯一数据源;definePages R1–R5 校验照常执行) */
|
|
306
|
+
pages: PageRouteLike[];
|
|
307
|
+
/** 路由前缀 → remote 名(最长前缀匹配;页面路由无匹配前缀时创建期即报错) */
|
|
308
|
+
remotePrefixes: Record<string, string>;
|
|
309
|
+
/** 缺省 spec 推导(缺省 = 去首段前缀 + 剥 :参数 段,同 definePages 默认) */
|
|
310
|
+
deriveSpec?: (route: string) => string;
|
|
311
|
+
/** 远程 exposes 清单(dev 由 remoteSchema 提供;build 为空表时按语义诚实降级) */
|
|
312
|
+
schema?: Record<string, RemoteSchemaEntry>;
|
|
313
|
+
/** ERROR 级校验失败是否 throw(默认 true) */
|
|
314
|
+
strict?: boolean;
|
|
315
|
+
/** 站点 base 前缀(如 '/main'):resolve 时先剥离再匹配路由空间 */
|
|
316
|
+
base?: string;
|
|
317
|
+
/** 每次页面模块实际加载前执行(同步或异步);宿主在此提供最新 context */
|
|
318
|
+
beforeLoad?: () => void | Promise<void>;
|
|
319
|
+
/** 加载期占位组件(骨架屏) */
|
|
320
|
+
loadingComponent?: Component;
|
|
321
|
+
/** 错误占位组件(缺省 = 内置三段式错误占位:错误码+根因+修法) */
|
|
322
|
+
errorComponent?: Component;
|
|
323
|
+
/** 骨架屏延迟 ms(默认 200,防闪) */
|
|
324
|
+
delay?: number;
|
|
325
|
+
}
|
|
326
|
+
interface ResolvedHostPage {
|
|
327
|
+
/** 原页面记录(含宿主自由扩展字段 name/title/keepAlive/...) */
|
|
328
|
+
page: PageRouteLike;
|
|
329
|
+
/** 命中的 remote 名 */
|
|
330
|
+
remote: string;
|
|
331
|
+
/** 完整加载 spec(`<remote>/<exposes 键>`,loadRemote 直用) */
|
|
332
|
+
spec: string;
|
|
333
|
+
/** 路径参数(解码失败只让该次匹配失败,不让页面初始化崩溃) */
|
|
334
|
+
params: Record<string, string>;
|
|
335
|
+
}
|
|
336
|
+
interface HostPages {
|
|
337
|
+
/** 原页面记录 */
|
|
338
|
+
pages: PageRouteLike[];
|
|
339
|
+
/** 路径 → 页面解析(兼容 base 前缀与深链;无匹配返回 null) */
|
|
340
|
+
resolve(path: string): ResolvedHostPage | null;
|
|
341
|
+
/** 按 spec 取异步页面组件(同 spec 复用;换登录代次后重建以触发会话同步) */
|
|
342
|
+
component(spec: string): Component;
|
|
343
|
+
/** 保活白名单:keepAlive 页面的组件 name(与实际被 KeepAlive 缓存的包装组件一致) */
|
|
344
|
+
keepAliveNames: string[];
|
|
345
|
+
}
|
|
346
|
+
declare function createHostPages$1(options: HostPagesOptions, load: (spec: string, opts?: {
|
|
347
|
+
retries?: number;
|
|
348
|
+
}) => Promise<any>): HostPages;
|
|
270
349
|
|
|
271
350
|
declare const remoteComponent: (spec: string, opts?: RemoteComponentOptions) => vue.Component;
|
|
351
|
+
/** 宿主页面适配器(绑定本包运行时的 loadRemote;选项与返回值类型见 vue-adapter) */
|
|
352
|
+
declare function createHostPages(options: Parameters<typeof createHostPages$1>[0]): HostPages;
|
|
272
353
|
|
|
273
354
|
/** 浏览器应用唯一公开入口。 */
|
|
274
355
|
|
|
@@ -276,4 +357,4 @@ type RemoteSchema = Record<string, RemoteSchemaEntry>;
|
|
|
276
357
|
/** 无插件转换的场景(构建、Node 导入)如实返回空清单。 */
|
|
277
358
|
declare const remoteSchema: RemoteSchema;
|
|
278
359
|
|
|
279
|
-
export { type AppContext, type FgRuntime, type LoadRemoteOptions, type LoadShareOptions, type PageRouteLike, type PageViolation, type PagesOptions, type PreloadRemoteOptions, type RemoteComponentOptions, type RemoteConfig, type RemoteDebugInfo, type RemoteInput, type RemoteSchema, type RemoteSchemaEntry, type RuntimeHooks, type RuntimePlugin, type ShareEntry, type ShareScope, type ShareScopeMap, definePages, getAppContext, getContainer, getRuntime, initSharing, loadRemote, loadShare, parseSpec, preloadRemote, provideAppContext, registerPlugins, registerRemote, registerRemotes, registerShare, remoteComponent, remoteSchema, requireAppContext, shareScopeMap, unwrapDefault, validatePages, version };
|
|
360
|
+
export { type AppContext, type FgRuntime, type HostPages, type HostPagesOptions, type LoadRemoteOptions, type LoadShareOptions, type PageRouteLike, type PageViolation, type PagesOptions, type PreloadRemoteOptions, type RemoteComponentOptions, type RemoteConfig, type RemoteDebugInfo, type RemoteInput, type RemoteSchema, type RemoteSchemaEntry, type RemoteSetupContext, type RemoteSetupModule, type ResolvedHostPage, type RuntimeHooks, type RuntimePlugin, type ShareEntry, type ShareScope, type ShareScopeMap, clearAppContext, createHostPages, definePages, getAppContext, getContainer, getRuntime, initSharing, loadRemote, loadShare, parseSpec, preloadRemote, provideAppContext, registerPlugins, registerRemote, registerRemotes, registerShare, remoteComponent, remoteSchema, requireAppContext, shareScopeMap, unwrapDefault, validatePages, version };
|
package/dist/runtime-entry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { initSharing, registerShare, registerRemotes, registerRemote, registerPlugins, loadShare, loadRemote, getContainer, preloadRemote, parseSpec, getRuntime, shareScopeMap, unwrapDefault, version } from "./runtime.js";
|
|
2
|
-
export { provideAppContext, getAppContext, requireAppContext } from "./context.js";
|
|
1
|
+
export { initSharing, registerShare, registerRemotes, registerRemote, registerPlugins, loadShare, loadRemote, getContainer, preloadRemote, parseSpec, getRuntime, shareScopeMap, unwrapDefault, version, clearSessionState } from "./runtime.js";
|
|
2
|
+
export { provideAppContext, getAppContext, requireAppContext, clearAppContext } from "./context.js";
|
|
3
3
|
export { definePages, validatePages } from "./pages.js";
|
|
4
|
-
export { remoteComponent } from "./vue.js";
|
|
4
|
+
export { remoteComponent, createHostPages } from "./vue.js";
|
|
5
5
|
export const remoteSchema = {};
|
package/dist/runtime.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
var pe=/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-((?:[0-9A-Za-z-]+)(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.-]+)?$/;function L(s){let c=pe.exec(String(s).trim());return c?{major:Number(c[1]),minor:Number(c[2]??0),patch:Number(c[3]??0),pre:c[4]?c[4].split("."):[]}:null}function de(s,c){if(s.length===0&&c.length===0)return 0;if(s.length===0)return 1;if(c.length===0)return-1;let l=Math.max(s.length,c.length);for(let n=0;n<l;n++){let g=s[n],y=c[n];if(g===void 0)return-1;if(y===void 0)return 1;let w=/^\d+$/.test(g),b=/^\d+$/.test(y);if(w&&b){let T=Number(g)-Number(y);if(T!==0)return T}else{if(w!==b)return w?-1:1;if(g!==y)return g<y?-1:1}}return 0}function x(s,c){let l=L(s),n=L(c);return!l||!n?s<c?-1:s>c?1:0:l.major!==n.major?l.major-n.major:l.minor!==n.minor?l.minor-n.minor:l.patch!==n.patch?l.patch-n.patch:de(l.pre,n.pre)}var ge=/^[a-z]+[+:-]/i;function k(s,c){if(c===!1||c==null||c==="")return!0;let l=String(c).trim();return!l||l==="*"||l==="latest"||ge.test(l)?!0:l.split("||").some(n=>he(s,n.trim()))}function he(s,c){if(!c||c==="*")return!L(s)?.pre.length;let l=c.split(/\s+-\s+/);if(l.length===2){let y=l[0].trim(),w=l[1].trim();return!(!k(s,`>=${y}`)||!k(s,`<=${w}`))}let n=L(s);if(!n)return!1;let g=ye(c);if(g===null)return!0;for(let y of g)if(!be(n,s,y))return!1;return!(n.pre.length>0&&!g.some(y=>y.ver.pre.length>0)&&!g.some(w=>w.ver.major===n.major&&w.ver.minor===n.minor&&w.ver.patch===n.patch))}function ye(s){let c=[],l=s.trim().split(/\s+/).filter(Boolean);if(l.length===0)return null;for(let n of l){let g=/^(>=|<=|>|<|=|\^|~)?(.*)$/.exec(n);if(!g)return null;let y=g[1],w=g[2],b=Se(y,w);if(b===null)return null;c.push(...b)}return c}function Se(s,c){let l=Ee(c);if(!l)return null;let{ver:n,partial:g}=l;switch(s){case"^":{let y=Re(n);return y?[{op:">=",ver:n},{op:"<",ver:y}]:null}case"~":{let y=g>=2?{major:n.major,minor:n.minor+1,patch:0,pre:[]}:{major:n.major+1,minor:0,patch:0,pre:[]};return[{op:">=",ver:n},{op:"<",ver:y}]}case">=":return[{op:">=",ver:n}];case">":return g===1?[{op:">=",ver:{major:n.major+1,minor:0,patch:0,pre:[]}}]:g===2?[{op:">=",ver:{major:n.major,minor:n.minor+1,patch:0,pre:[]}}]:[{op:">",ver:n}];case"<":return g===1?[{op:"<",ver:{major:n.major,minor:0,patch:0,pre:[]}}]:g===2?[{op:"<",ver:{major:n.major,minor:n.minor,patch:0,pre:[]}}]:[{op:"<",ver:n}];case"<=":return[{op:"<=",ver:n}];case"=":case void 0:return g===1?[{op:">=",ver:n},{op:"<",ver:{major:n.major+1,minor:0,patch:0,pre:[]}}]:g===2?[{op:">=",ver:n},{op:"<",ver:{major:n.major,minor:n.minor+1,patch:0,pre:[]}}]:[{op:"=",ver:n}];default:return null}}function Re(s){return s.major>0?{major:s.major+1,minor:0,patch:0,pre:[]}:s.minor>0?{major:0,minor:s.minor+1,patch:0,pre:[]}:{major:0,minor:0,patch:s.patch+1,pre:[]}}function Ee(s){let c=s.replace(/^v/,"").trim();if(!c||c==="*"||c==="x"||c==="X")return{ver:{major:0,minor:0,patch:0,pre:[]},partial:0};let l=c.split("."),n=[],g=0;for(let b of l.slice(0,3))if(/^\d+$/.test(b))n.push(Number(b)),g++;else{if(b==="x"||b==="X"||b==="*")break;break}let y=c.split("-"),w=y.length>1?y.slice(1).join("-").split("+")[0].split("."):[];return g===0&&w.length===0?{ver:{major:0,minor:0,patch:0,pre:[]},partial:0}:{ver:{major:n[0]??0,minor:n[1]??0,patch:n[2]??0,pre:w},partial:g}}function we(s,c){switch(c){case">=":return s>=0;case">":return s>0;case"<":return s<0;case"<=":return s<=0;case"=":return s===0}}function be(s,c,l){let n=`${s.major}.${s.minor}.${s.patch}`+(s.pre.length?`-${s.pre.join(".")}`:"");if(l.upper)return!(!(x(n,`${l.ver.major}.${l.ver.minor}.${l.ver.patch}`+(l.ver.pre.length?`-${l.ver.pre.join(".")}`:""))>=0)||!(x(n,`${l.upper.major}.${l.upper.minor}.${l.upper.patch}`)<0));let g=x(n,`${l.ver.major}.${l.ver.minor}.${l.ver.patch}`+(l.ver.pre.length?`-${l.ver.pre.join(".")}`:"")),y=we(g,l.op);return l.ver.pre.length>0&&s.pre.length===0&&(l.op===">"||l.op==="<")?!1:y}var S=class extends Error{code;details;constructor(c,l,n){super(`[fulgurjs:${c}] ${l}`),this.name="FgError",this.code=c,this.details=n}},R={REMOTE_LOAD_FAILED:"MFU-001",REMOTE_NAME_MISMATCH:"MFU-002",SHARE_STRICT_VERSION:"MFU-003",SHARE_NOT_AVAILABLE:"MFU-004",CONTAINER_REINIT_CONFLICT:"MFU-005",MODULE_NOT_EXPOSED:"MFU-006",PRELOAD_FAILED:"MFU-007",REMOTE_UNKNOWN:"MFU-008",EMPTY_EXPORTS:"MFU-009",SINGLETON_SKEW:"MFU-010",SETUP_INVALID_EXPORT:"MFU-011",SETUP_RUN_FAILED:"MFU-012",SETUP_SESSION_KEY_MISSING:"MFU-013",SETUP_RECURSION:"MFU-014"};var J="4.1.0";var Pe="__fulgurjsSetup",_e="__FULGURJS_APP_CONFIG__",D=15e3,B=2,ve=5,Me=3e4,$e=4e3,xe=.2;function Ie(){let s=Object.create(null),c=new Map,l=[],n=new Map,g=new Map,y=new WeakMap,w=new WeakMap,b={},T=()=>{for(let e of l)try{e.init?.(b)}catch(r){console.warn("[fulgurjs] runtimePlugin init failed:",e.name,r)}},U=new Map,j,Y=e=>{let r=U.get(e);return r||U.set(e,r={}),r},V=()=>globalThis[_e]??{};async function K(e,r,t,o){let a=u=>u instanceof S?u:new S(R.SETUP_RUN_FAILED,`${o} of "${e}" threw: ${String(u?.message??u)}
|
|
2
|
+
\u4FEE\u6CD5: \u4FEE\u590D\u8BE5 ${o} \u7684\u521D\u59CB\u5316\u903B\u8F91\u540E\u91CD\u65B0\u52A0\u8F7D\uFF08\u5931\u8D25\u7684\u521D\u59CB\u5316\u7F13\u5B58\u5DF2\u6E05\u9664\uFF0C\u53EF\u76F4\u63A5\u91CD\u8BD5\uFF09`,{remote:e,phase:o});j=e;let i;try{i=r(t)}catch(u){throw a(u)}finally{j=void 0}try{await i}catch(u){throw a(u)}}async function Z(e,r,t,o){e.debug.setup="pending";try{if(e.manifestUrl)try{await F(`${e.name}/${t}`)}catch{}let a=await r.get(t),i=a?.default;if(typeof i!="function")throw new S(R.SETUP_INVALID_EXPORT,`setup module "${t}" of "${e.name}": default export is ${a==null?String(a):typeof i}, expected a function
|
|
3
|
+
expected: export default async function setup(context: RemoteSetupContext) { ... }
|
|
4
|
+
\u4FEE\u6CD5: \u5728 federation({ setup }) \u6307\u5411\u7684\u6587\u4EF6\u8865\u9ED8\u8BA4\u5BFC\u51FA\u51FD\u6570\uFF08\u5177\u540D onSession \u53EF\u9009\uFF1B\u5176\u4ED6\u5BFC\u51FA\u4E0D\u4F5C\u4E3A\u751F\u547D\u5468\u671F\u5165\u53E3\uFF09`,{remote:e.name,module:t});if(a.onSession!==void 0&&typeof a.onSession!="function")throw new S(R.SETUP_INVALID_EXPORT,`setup module "${t}" of "${e.name}": export "onSession" is ${typeof a.onSession}, expected a function
|
|
5
|
+
\u4FEE\u6CD5: \u5C06 onSession \u4FEE\u6B63\u4E3A\u51FD\u6570\u5BFC\u51FA\uFF0C\u6216\u5220\u9664\u8BE5\u5177\u540D\u5BFC\u51FA\uFF08\u5176\u4ED6\u5BFC\u51FA\u540D\u4E0D\u4F5C\u4E3A\u751F\u547D\u5468\u671F\u5165\u53E3\uFF09`,{remote:e.name,module:t});o.setupExports=a,await K(e.name,i,{appContext:V(),signal:(o.controller??=new AbortController).signal},"setup"),e.debug.setup="ready"}catch(a){throw e.debug.setup="failed",o.setupExports=void 0,a}}async function Q(e,r,t){let o=V(),a=o?.sessionKey;if(typeof a!="string"||a==="")throw new S(R.SETUP_SESSION_KEY_MISSING,`remote "${e.name}" declares onSession but AppContext.sessionKey is ${a===void 0?"missing":JSON.stringify(a)}
|
|
6
|
+
expected: \u5BBF\u4E3B\u767B\u5F55\u540E provideAppContext({ ..., sessionKey: '<\u975E\u654F\u611F\u767B\u5F55\u4EE3\u6B21 ID>' })
|
|
7
|
+
\u4FEE\u6CD5: \u5BBF\u4E3B\u767B\u5F55\u6D41\u7A0B\u6BCF\u6B21\u6210\u529F\u767B\u5F55/\u91CD\u767B\u751F\u6210\u65B0\u4EE3\u6B21 ID\uFF08\u7981\u6B62\u7528 token \u5145\u5F53\uFF09\uFF0C\u5E76\u5728 loadRemote \u524D\u63D0\u4F9B`,{remote:e.name});if(r.sessionPromise&&r.sessionKey===a)return r.sessionPromise;r.controller?.abort();let i=new AbortController;r.controller=i,r.sessionKey=a;let u=r.sessionPromise?r.sessionPromise.catch(()=>{}):Promise.resolve(),p=(async()=>{await u,!i.signal.aborted&&await K(e.name,t,{appContext:o,sessionKey:a,signal:i.signal},"onSession")})();return r.sessionPromise=p,p.catch(()=>{r.sessionKey===a&&(r.sessionKey=void 0,r.sessionPromise=void 0)}),p}async function ee(e,r){let t=r?.[Pe];if(typeof t!="string"||t==="")return;let o=Y(e.name);o.setupPromise||(o.setupPromise=Z(e,r,t,o).catch(i=>{throw o.setupPromise=void 0,i})),await o.setupPromise;let a=o.setupExports?.onSession;typeof a=="function"&&await Q(e,o,a)}function re(){for(let e of U.values()){try{e.controller?.abort()}catch{}e.sessionKey=void 0,e.sessionPromise=void 0}}let A=e=>s[e]??=Object.create(null);function _(e){try{b.onRemoteError?.(e)}catch{}typeof window<"u"&&window.dispatchEvent(new CustomEvent("fulgurjs:error",{detail:e}))}function te(e="default"){return A(e),s}function ne(e,r,t,o,a={}){let i=A(e||"default"),u=Object.hasOwn(i,r)?i[r]:void 0,p=u&&typeof u=="object"?u:void 0;p||(p=Object.create(null),i[r]=p),!(Object.hasOwn(p,t)&&p[t])&&(p[t]={version:t,get:o,from:a.from??"unknown",eager:!!a.eager,loaded:!!a.loaded})}function oe(e){let r=(t,o,a)=>{if(t===void 0)return;if(!(typeof t=="number"&&(a?Number.isInteger(t)&&t>=0&&t<=10:Number.isFinite(t)&&t>0)))throw new Error(`[fulgurjs] registerRemote("${e.name}"): ${o} must be a ${a?"integer 0..10":"finite positive number"}, got ${String(t)}`)};r(e.timeout,"timeout"),r(e.retries,"retries",!0),e.breaker&&(r(e.breaker.threshold,"breaker.threshold"),r(e.breaker.resetMs,"breaker.resetMs"))}function H(e){for(let r of e){oe(r);let t=c.get(r.name);c.set(r.name,{timeout:D,retries:B,...t,...r,breakerThreshold:r.breaker?.threshold??t?.breakerThreshold??ve,breakerResetMs:r.breaker?.resetMs??t?.breakerResetMs??Me,state:t?.state??"idle",breakerState:t?.breakerState??{fails:0,openUntil:0},entryInflight:t?.entryInflight??new Map,debug:t?.debug??{entry:r.entry,status:"idle",setup:"none"}})}}function ie(e){H([e])}function se(e){l.push(...e),T()}async function ae(e,r={}){let t=r.shareScope||"default",o=r.shareKey||e,i=A(t)[o]||{},u=Object.keys(i),p=r.requiredVersion,h=u.filter(f=>p===void 0||p===!1||k(f,p)),d;if(r.singleton&&u.length>0){let f=u.filter(m=>i[m].loaded);if(d=[...f.length?f:u].sort(x).pop(),u.length>1||!h.includes(d)){let m=i[d],$=`singleton skew "${o}": req "${p??"any"}" use ${d} from ${m.from}`;if(r.strictVersion){let v=new S(R.SHARE_STRICT_VERSION,$,{shareKey:o,requiredVersion:p});throw _({remote:m.from,error:v}),v}console.warn(`[fulgurjs:MFU-010] ${$}`)}}else if(d=[...h].sort(x).pop(),!d){if(r.strictVersion){let m=new S(R.SHARE_STRICT_VERSION,`no satisfying version for "${o}" (required "${p}") in share scope "${t}"`);throw _({remote:o,error:m}),m}if(r.fallback)return r.fallback();let f=new S(R.SHARE_NOT_AVAILABLE,`shared "${o}" (${p??"any"}) unavailable in scope "${t}" (no local fallback)`,{shareKey:o,requiredVersion:p});throw _({remote:o,error:f}),f}let E=i[d];if(b.resolveShare){let f=await b.resolveShare({shareKey:o,shareScope:t,requiredVersion:p,picked:E,available:Object.values(i)});f&&(E=f)}return E.loaded=!0,E.get()}function ce(e){try{let r=new URL(e,typeof location<"u"?location.href:"http://f.invalid");return r.protocol+"//"+r.host+r.pathname}catch{return String(e).replace(/[?#].*$/,"")}}function q(e,r,t){return new Promise((o,a)=>{let i=setTimeout(()=>a(new Error(`timeout after ${r}ms: ${t}`)),r);e.then(u=>{clearTimeout(i),o(u)},u=>{clearTimeout(i),a(u)})})}let le=e=>new Promise(r=>setTimeout(r,e));function N(e,r){if(!e||typeof e.get!="function")throw new Error(`remoteEntry of "${r.name}" does not export a container interface (get/init)`);if(e.name&&r.name&&e.name!==r.name){let t=`remoteEntry self-reported name "${e.name}" mismatches configured name "${r.name}"`;if(r.promise)console.warn(`[fulgurjs] ${t} (promise-based remote, continuing)`);else throw Object.assign(new Error(t),{code:R.REMOTE_NAME_MISMATCH})}}async function G(e,r){let t=r.entryInflight.get(e),o=t??import(e);return t||(r.entryInflight.set(e,o),o.catch(()=>r.entryInflight.delete(e))),q(o,r.timeout??D,`load remoteEntry ${e}`)}async function X(e,r){let t=e.shareScope||"default";if(e.container){let i=w.get(e.container);if(i&&i!==t)throw new S(R.CONTAINER_REINIT_CONFLICT,`container "${e.name}" already initialized with share scope "${i}", cannot re-init with "${t}"`);return e.container}if(e.containerPromise)return e.containerPromise;let o=e.breakerState;if(o.openUntil>Date.now()){let i=new S(R.REMOTE_LOAD_FAILED,`circuit breaker open for remote "${e.name}" until ${new Date(o.openUntil).toISOString()}`,{remote:e.name});throw _({remote:e.name,error:i}),i}e.state="loading",e.debug.status="loading";let a=(async()=>{let i=[];if(e.promise)i.push(async()=>{let f=await q(e.promise(),e.timeout??D,`resolve promise remote "${e.name}"`),m=typeof f=="string"?await G(f,e):f;return N(m,e),m});else for(let f of[e.entry,...e.fallback??[]])i.push(async()=>{let m=await G(f,e);return N(m,e),m});let u,p,h=!1,d=r?.retries??e.retries??B;for(let f of i){for(let m=0;m<=d;m++){let $=performance.now();try{u=await f(),e.lastLoadMs=Math.round(performance.now()-$),e.debug.lastLoadMs=e.lastLoadMs,h=!0;break}catch(v){if(p=v,v?.code)throw v;m<d&&await le(Math.min($e,200*2**m)*(1-xe*(1-2*Math.random())))}}if(h)break}if(!h||!u){let f=ce(e.entry),m=[];throw/^https?:\/\//.test(e.entry)&&typeof location<"u"?(m.push(`1. is the remote dev server running? open ${f} in a browser \u2014 it must return JS, not HTML`),m.push("2. is the URL correct in federation({ remotes })? dev/prod entries can differ"),m.push("3. CORS: the remote dev server must allow cross-origin requests (check its server.cors)")):(m.push(`1. is the remote deployed? open ${f} in a browser \u2014 it must return JS`),m.push("2. NGINX/CDN routing: the entry path must serve the remoteEntry JS (check try_files)")),new S(R.REMOTE_LOAD_FAILED,`failed to load remote "${e.name}" from ${f}: ${String(p?.message??p)}
|
|
8
|
+
`+m.join(`
|
|
9
|
+
`),{remote:e.name})}N(u,e);let E=w.get(u);if(E&&E!==t)throw new S(R.CONTAINER_REINIT_CONFLICT,`container "${e.name}" already initialized with share scope "${E}", cannot re-init with "${t}"`);return typeof u.init=="function"&&await u.init(s),w.set(u,t),e.container=u,e.state="loaded",e.debug.status="loaded",o.fails=0,u})();e.containerPromise=a;try{return await a}catch(i){if(e.containerPromise=void 0,e.state="failed",e.debug.status="failed",e.debug.error=String(i?.message??i),o.fails+=1,o.fails>=e.breakerThreshold&&(o.openUntil=Date.now()+e.breakerResetMs,o.fails=0),i instanceof S)throw _({remote:e.name,error:i}),i;if(i?.code)throw _({remote:e.name,error:i}),i;let u=new S(R.REMOTE_LOAD_FAILED,String(i?.message??i),{remote:e.name});throw _({remote:e.name,error:u}),u}}function C(e){let r=e.indexOf("/");return r===-1?{remote:e,module:""}:{remote:e.slice(0,r),module:z(e.slice(r+1))}}function z(e){return e?e.startsWith(".")?e:`./${e}`:""}async function ue(e,r){let{remote:t,module:o}=C(e);if(o&&j===t)throw new S(R.SETUP_RECURSION,`loadRemote("${e}") re-entered from setup/onSession of "${t}" \u2014 the initialization promise would await itself (deadlock)
|
|
10
|
+
\u4FEE\u6CD5: setup/onSession \u5185\u4E0D loadRemote \u540C\u4E00\u8FDC\u7A0B\u7684\u6A21\u5757\uFF1B\u9700\u8981\u7684\u4E1A\u52A1\u6A21\u5757\u6539\u4E3A\u5728\u9875\u9762\u7EC4\u4EF6\u5185\u52A0\u8F7D\uFF0C\u6216\u7ECF context \u901A\u9053\u89E3\u8026`,{remote:t});try{b.beforeLoadRemote?.({remote:t,module:o})}catch(h){console.warn("[fulgurjs] beforeLoadRemote hook error (ignored):",h)}let a=c.get(t);if(!a)throw new S(R.REMOTE_UNKNOWN,`unknown remote "${t}". Register it via federation({ remotes }) or registerRemote().`,{remote:t});let i;try{let h=o&&a.manifestUrl?F(e):Promise.resolve();[i]=await Promise.all([X(a,{retries:r?.retries}),h])}catch(h){if(r?.fallbackModule)return console.error(`[fulgurjs] loadRemote("${e}") failed; returning fallbackModule (\u663E\u5F0F\u964D\u7EA7\uFF0C\u9519\u8BEF\u5DF2\u900F\u51FA)`,h),_({remote:t,error:h}),await r.fallbackModule();throw h}if(!o)return i;await ee(a,i);let u=`${t}@${a.shareScope||"default"}#${o}`;n.has(u)||n.set(u,i.get(o).catch(h=>{throw n.delete(u),h instanceof S||h?.code?h:new S(R.REMOTE_LOAD_FAILED,`failed to load module "${o}" from remote "${t}": ${String(h?.message??h)}`,{remote:t,module:o})}));let p;try{p=await n.get(u)}catch(h){if(r?.fallbackModule)return console.error(`[fulgurjs] loadRemote("${e}") failed; returning fallbackModule (\u663E\u5F0F\u964D\u7EA7\uFF0C\u9519\u8BEF\u5DF2\u900F\u51FA)`,h),_({remote:t,error:h}),await r.fallbackModule();throw h}try{b.afterLoadRemote?.({remote:t,module:o,module_ns:p})}catch(h){console.warn("[fulgurjs] afterLoadRemote hook error (ignored):",h)}if(p&&typeof p=="object"&&Object.keys(p).length===0){let h=new S(R.EMPTY_EXPORTS,`no exports: ${o} @ ${t}`,{remote:t,module:o});_({remote:t,error:h}),console.error(`[fulgurjs:MFU-009] ${h.message}`)}return p}function fe(e){let r=c.get(e);if(!r)throw new S(R.REMOTE_UNKNOWN,`unknown remote "${e}"`,{remote:e});return X(r)}async function F(e,r={}){let{remote:t,module:o}=C(e),a=c.get(t);if(!a)throw new S(R.REMOTE_UNKNOWN,`unknown remote "${t}"`,{remote:t});let i=r.mode??"preload",u=(d,E)=>{if(d.sheet)return Promise.resolve();let f=y.get(d);if(f)return f;let m=new Promise($=>{let v=()=>{d.removeEventListener("load",v),d.removeEventListener("error",I),$()},I=()=>{_({remote:t,error:new S(R.PRELOAD_FAILED,`failed to load remote stylesheet ${E}`,{remote:t})}),v()};d.addEventListener("load",v,{once:!0}),d.addEventListener("error",I,{once:!0}),d.sheet&&v()});return y.set(d,m),m},p=(d,E)=>{if(typeof document>"u")return Promise.resolve();let f=document.querySelector(`link[href="${d}"]`);if(f)return E==="style"&&i==="preload"?y.get(f)??Promise.resolve():Promise.resolve();let m=document.createElement("link");m.rel=E==="style"?"stylesheet":"modulepreload",m.href=d,i==="prefetch"&&(m.fetchPriority="low");let $=E==="style"?u(m,d):Promise.resolve();return document.head.appendChild(m),E==="style"&&i==="preload"?$:Promise.resolve()},h=d=>{let E=typeof document<"u"?document.baseURI:void 0,f=new URL(a.entry,E||"http://fulgurjs.invalid/");return!f.pathname.endsWith("/")&&!f.pathname.split("/").pop().includes(".")&&(f.pathname+="/"),new URL(d,f).href};try{if(a.manifestUrl){let d=g.get(a.manifestUrl);d||(d=fetch(a.manifestUrl,{cache:"no-cache",signal:AbortSignal.timeout(8e3)}).then(f=>f.ok?f.json():void 0),g.set(a.manifestUrl,d),d.then(f=>{f||g.delete(a.manifestUrl)},()=>g.delete(a.manifestUrl)));let E=await d;if(E){let f=E.schemaVersion;if(typeof f=="number"&&(!Number.isInteger(f)||f>1)){_({remote:t,error:new S(R.PRELOAD_FAILED,`unsupported manifest schemaVersion ${String(f)} (expected 1)`,{remote:t})}),await p(a.entry,"modulepreload");return}await p(a.entry,"modulepreload");let m=E.exposes,$=Array.isArray(m)?m.filter(M=>M&&typeof M=="object").map(M=>[String(M.name??""),M]):m&&typeof m=="object"?Object.entries(m):[],v=o?$.filter(([M,O])=>z(String(O?.name??M))===o):$,I=[];for(let[,M]of v)if(typeof M?.file=="string"&&await p(h(M.file),"modulepreload"),Array.isArray(M?.css))for(let O of M.css){if(typeof O!="string")continue;let me=p(h(O),"style");i==="preload"&&I.push(me)}await Promise.all(I);return}}await p(a.entry,"modulepreload")}catch(d){_({remote:t,error:new S(R.PRELOAD_FAILED,String(d?.message??d),{remote:t})})}}return typeof window>"u"||(window.__FULGURJS_SCOPE__=s,window.__FULGURJS_INFO__={get remotes(){let e={};for(let[r,t]of c)e[r]={...t.debug,entry:t.entry};return e},errors:[]}),{shareScopeMap:s,initSharing:te,registerShare:ne,registerRemotes:H,registerRemote:ie,registerPlugins:se,loadShare:ae,loadRemote:ue,getContainer:fe,preloadRemote:F,parseSpec:C,clearSessionState:re}}var W=globalThis,P=W.__FULGURJS_RUNTIME__??Ie();W.__FULGURJS_RUNTIME__=P;try{Object.freeze(P)}catch{}var Ne=J;function Ce(){return P}var Fe=P.initSharing,De=P.registerShare,Ve=P.registerRemotes,Ke=P.registerRemote,He=P.registerPlugins,qe=P.loadShare,Ge=P.loadRemote,Xe=P.getContainer,ze=P.preloadRemote,Je=P.parseSpec,Be=P.clearSessionState,We=P.shareScopeMap;function Ye(s){if(s&&typeof s=="object"&&"default"in s){let c=s.default;return c!==void 0?c:s}return s}var Ze=P;export{Be as clearSessionState,Ze as default,Xe as getContainer,Ce as getRuntime,Fe as initSharing,Ge as loadRemote,qe as loadShare,Je as parseSpec,ze as preloadRemote,He as registerPlugins,Ke as registerRemote,Ve as registerRemotes,De as registerShare,P as runtime,We as shareScopeMap,Ye as unwrapDefault,Ne as version};
|
package/dist/vue-adapter.cjs
CHANGED
|
@@ -20,10 +20,153 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/vue-adapter.ts
|
|
21
21
|
var vue_adapter_exports = {};
|
|
22
22
|
__export(vue_adapter_exports, {
|
|
23
|
+
createHostPages: () => createHostPages,
|
|
23
24
|
createRemoteComponent: () => createRemoteComponent
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(vue_adapter_exports);
|
|
26
27
|
var import_vue = require("vue");
|
|
28
|
+
|
|
29
|
+
// src/pages.ts
|
|
30
|
+
var normSpec = (s) => s.replace(/^[./]+/, "").replace(/\/+$/, "");
|
|
31
|
+
var hasParam = (route) => /(^|\/):[\w]+/.test(route);
|
|
32
|
+
var segs = (route) => route.split("/").filter(Boolean);
|
|
33
|
+
function defaultDeriveSpec(route) {
|
|
34
|
+
return segs(route).slice(1).filter((seg) => !seg.startsWith(":")).join("/");
|
|
35
|
+
}
|
|
36
|
+
function shadows(earlier, later) {
|
|
37
|
+
const a = segs(earlier);
|
|
38
|
+
const b = segs(later);
|
|
39
|
+
if (a.length !== b.length) return null;
|
|
40
|
+
if (earlier === later) return "full";
|
|
41
|
+
let hasParam2 = false;
|
|
42
|
+
for (let i = 0; i < a.length; i++) {
|
|
43
|
+
if (a[i] === b[i]) continue;
|
|
44
|
+
if (a[i].startsWith(":")) {
|
|
45
|
+
hasParam2 = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return hasParam2 ? "param" : null;
|
|
51
|
+
}
|
|
52
|
+
function remoteOfRoute(route, remotes) {
|
|
53
|
+
if (!remotes) return null;
|
|
54
|
+
for (const [prefix, name] of Object.entries(remotes)) {
|
|
55
|
+
if (route === prefix.replace(/\/+$/, "") || route.startsWith(prefix)) return name;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function validatePages(pages, options = {}) {
|
|
60
|
+
const deriveSpec = options.deriveSpec ?? defaultDeriveSpec;
|
|
61
|
+
const violations = [];
|
|
62
|
+
const effective = pages.map((p) => normSpec(p.spec ?? deriveSpec(p.route)));
|
|
63
|
+
pages.forEach((p, i) => {
|
|
64
|
+
if (p.spec || !hasParam(p.route)) return;
|
|
65
|
+
pages.forEach((q, j) => {
|
|
66
|
+
if (i === j) return;
|
|
67
|
+
if (effective[j] !== effective[i]) return;
|
|
68
|
+
violations.push({
|
|
69
|
+
rule: "R1",
|
|
70
|
+
level: "error",
|
|
71
|
+
message: `[R1] \u8DEF\u7531 "${p.route}" \u5265\u53C2\u63A8\u5BFC spec "${effective[i]}" \u4E0E\u8DEF\u7531 "${q.route}"${q.spec ? "\uFF08\u663E\u5F0F spec\uFF09" : ""}\u6536\u655B\u76F8\u540C\u3002
|
|
72
|
+
\u6839\u56E0\uFF1A\u53C2\u6570\u6BB5\u5265\u79BB\u540E expose \u952E\u4E0E\u5176\u5B83\u6761\u76EE\u76F8\u540C\uFF0C\u8BE5\u8DEF\u7531\u4F1A\u9759\u9ED8\u52A0\u8F7D\u9519\u8BEF\u7EC4\u4EF6
|
|
73
|
+
\uFF08\u771F\u5B9E\u4E8B\u6545\uFF1A\u5E26\u53C2\u52A8\u4F5C\u9875\u8BEF\u8D70\u5217\u8868/\u66F4\u65B0\u8BED\u4E49\uFF0C\u6570\u636E\u98CE\u9669\uFF09\u3002
|
|
74
|
+
\u4FEE\u6CD5\uFF1A\u4E3A\u8BE5\u6761\u76EE\u663E\u5F0F\u58F0\u660E spec: '...'\uFF08\u6307\u5411\u5E26\u53C2\u9875\u9762\u81EA\u5DF1\u7684 expose\uFF09\uFF0C\u6216\u8C03\u6574\u8DEF\u7531\u62C6\u5206\u3002`
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
const seenSpec = /* @__PURE__ */ new Map();
|
|
79
|
+
pages.forEach((p, i) => {
|
|
80
|
+
const key = effective[i];
|
|
81
|
+
if (seenSpec.has(key)) {
|
|
82
|
+
const first = pages[seenSpec.get(key)];
|
|
83
|
+
const bothExplicit = !!p.spec && !!first.spec;
|
|
84
|
+
violations.push({
|
|
85
|
+
rule: "R2",
|
|
86
|
+
level: "warn",
|
|
87
|
+
message: `[R2] \u8DEF\u7531 "${p.route}" \u4E0E "${first.route}" \u6709\u6548 spec \u76F8\u540C\uFF08"${key}"\uFF09\u3002${bothExplicit ? "\u4E24\u6761\u5747\u4E3A\u663E\u5F0F spec" : "\u5176\u4E2D\u542B\u63A8\u5BFC spec"}\u2014\u2014\u82E5\u4E3A\u523B\u610F\u7684\u83DC\u5355\u522B\u540D\u53EF\u5FFD\u7565\u672C\u8B66\u544A\uFF0C\u5426\u5219\u8BF7\u6838\u5BF9\u3002`
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
seenSpec.set(key, i);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
if (options.schema && options.remotes) {
|
|
94
|
+
pages.forEach((p, i) => {
|
|
95
|
+
const remote = remoteOfRoute(p.route, options.remotes);
|
|
96
|
+
if (!remote) return;
|
|
97
|
+
const entry = options.schema[remote];
|
|
98
|
+
if (!entry || entry.exists === false) return;
|
|
99
|
+
const exposes = new Set((entry.exposes ?? []).map(normSpec));
|
|
100
|
+
if (!exposes.has(effective[i])) {
|
|
101
|
+
violations.push({
|
|
102
|
+
rule: "R3",
|
|
103
|
+
level: "error",
|
|
104
|
+
message: `[R3] \u8DEF\u7531 "${p.route}" \u7684 spec "${effective[i]}" \u4E0D\u5728\u8FDC\u7A0B "${remote}" \u7684 exposes \u6E05\u5355\u4E2D\u3002
|
|
105
|
+
\u6839\u56E0\uFF1Aspec \u62FC\u5199\u4E0E\u8FDC\u7A0B exposes \u952E\u4E0D\u4E00\u81F4\uFF08\u8FD0\u884C\u65F6\u5C06 MFU-006 \u52A0\u8F7D\u5931\u8D25\uFF09\u3002
|
|
106
|
+
\u4FEE\u6CD5\uFF1A\u6838\u5BF9\u8FDC\u7A0B vite.config exposes \u952E\u540D\uFF0C\u6216\u4FEE\u6B63\u672C\u8868 spec\u3002
|
|
107
|
+
\u8FDC\u7A0B\u5B9E\u9645 exposes\uFF08\u524D 10\uFF09\uFF1A${[...exposes].slice(0, 10).join("\u3001") || "\uFF08\u7A7A\uFF09"}`
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
pages.forEach((p, i) => {
|
|
113
|
+
pages.forEach((q, j) => {
|
|
114
|
+
if (j <= i) return;
|
|
115
|
+
const kind = shadows(p.route, q.route);
|
|
116
|
+
if (!kind) return;
|
|
117
|
+
violations.push({
|
|
118
|
+
rule: "R4",
|
|
119
|
+
level: "error",
|
|
120
|
+
message: kind === "full" ? `[R4] \u8DEF\u7531\u5B8C\u5168\u91CD\u590D\uFF1A"${p.route}"\uFF08\u7B2C ${i + 1} \u6761\uFF09\u4E0E "${q.route}"\uFF08\u7B2C ${j + 1} \u6761\uFF09\u3002` : `[R4] \u8DEF\u7531 "${q.route}"\uFF08\u7B2C ${j + 1} \u6761\uFF09\u4F1A\u88AB\u66F4\u9760\u524D\u7684 "${p.route}" \u906E\u853D\uFF08\u5339\u914D\u5148\u5230\u5148\u5F97\uFF0C\u540E\u8005\u6210\u6B7B\u8DEF\u7531\uFF09\u3002
|
|
121
|
+
\u4FEE\u6CD5\uFF1A\u8C03\u6574\u6570\u7EC4\u987A\u5E8F\uFF08\u66F4\u5177\u4F53\u7684\u9759\u6001\u8DEF\u7531\u653E\u524D\u9762\uFF09\u6216\u5408\u5E76\u6761\u76EE\u3002`
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
const seenName = /* @__PURE__ */ new Map();
|
|
126
|
+
pages.forEach((p, i) => {
|
|
127
|
+
const name = typeof p.name === "string" ? p.name : "";
|
|
128
|
+
if (!name) return;
|
|
129
|
+
if (seenName.has(name)) {
|
|
130
|
+
violations.push({
|
|
131
|
+
rule: "R5",
|
|
132
|
+
level: "warn",
|
|
133
|
+
message: `[R5] name "${name}" \u91CD\u590D\uFF08"${pages[seenName.get(name)].route}" \u4E0E "${p.route}"\uFF09\u2014\u2014vue-router \u547D\u540D\u8DF3\u8F6C push({name}) \u5C06\u547D\u4E2D\u524D\u8005\uFF0C\u8BF7\u6838\u5BF9\u3002`
|
|
134
|
+
});
|
|
135
|
+
} else {
|
|
136
|
+
seenName.set(name, i);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return violations;
|
|
140
|
+
}
|
|
141
|
+
function formatViolations(violations) {
|
|
142
|
+
const errors = violations.filter((v) => v.level === "error");
|
|
143
|
+
const warns = violations.filter((v) => v.level === "warn");
|
|
144
|
+
let out = "";
|
|
145
|
+
if (errors.length) {
|
|
146
|
+
out += `[fulgurjs] \u8DEF\u7531\u8868\u6821\u9A8C\u5931\u8D25\uFF08${errors.length} \u9879\uFF0CERROR\uFF09\uFF1A
|
|
147
|
+
` + errors.map((v) => v.message).join("\n") + "\n";
|
|
148
|
+
}
|
|
149
|
+
if (warns.length) {
|
|
150
|
+
out += `[fulgurjs] \u8DEF\u7531\u8868\u6821\u9A8C\u8B66\u544A\uFF08${warns.length} \u9879\uFF0CWARN\uFF09\uFF1A
|
|
151
|
+
` + warns.map((v) => v.message).join("\n");
|
|
152
|
+
}
|
|
153
|
+
return out.trim();
|
|
154
|
+
}
|
|
155
|
+
function definePages(pages, options = {}) {
|
|
156
|
+
const violations = validatePages(pages, options);
|
|
157
|
+
const errors = violations.filter((v) => v.level === "error");
|
|
158
|
+
const warns = violations.filter((v) => v.level === "warn");
|
|
159
|
+
const text = formatViolations(violations);
|
|
160
|
+
if (text) {
|
|
161
|
+
if (errors.length && options.strict !== false) {
|
|
162
|
+
throw new Error(text);
|
|
163
|
+
}
|
|
164
|
+
console[errors.length ? "error" : "warn"](text);
|
|
165
|
+
}
|
|
166
|
+
return pages;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/vue-adapter.ts
|
|
27
170
|
var ERROR_STYLE = {
|
|
28
171
|
padding: "16px",
|
|
29
172
|
border: "1px solid #fde2e2",
|
|
@@ -60,7 +203,105 @@ function createRemoteComponent(loadRemote) {
|
|
|
60
203
|
});
|
|
61
204
|
};
|
|
62
205
|
}
|
|
206
|
+
var cleanCompName = (spec) => "Fulgurjs_" + spec.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
207
|
+
function defaultDeriveSpec2(route) {
|
|
208
|
+
return route.split("/").filter(Boolean).slice(1).filter((seg) => !seg.startsWith(":")).join("/");
|
|
209
|
+
}
|
|
210
|
+
function createHostPages(options, load) {
|
|
211
|
+
const { pages, remotePrefixes, deriveSpec, schema, strict, base, beforeLoad, loadingComponent, errorComponent, delay } = options;
|
|
212
|
+
definePages(pages, { deriveSpec, remotes: remotePrefixes, schema, strict });
|
|
213
|
+
const prefixes = Object.entries(remotePrefixes).map(([p, name]) => [p.endsWith("/") ? p : `${p}/`, name]).sort((a, b) => b[0].length - a[0].length);
|
|
214
|
+
const remoteOf = (route) => {
|
|
215
|
+
for (const [prefix, name] of prefixes) {
|
|
216
|
+
if (route.startsWith(prefix) || `${route}/` === prefix) return name;
|
|
217
|
+
}
|
|
218
|
+
throw new Error(
|
|
219
|
+
`[fulgurjs] page route "${route}" matches none of remotePrefixes { ${Object.entries(remotePrefixes).map(([k, v]) => `'${k}': '${v}'`).join(", ")} }
|
|
220
|
+
\u6839\u56E0: \u9875\u9762\u8868\u6761\u76EE\u7684\u8DEF\u7531\u4E0D\u5728\u4EFB\u4F55\u5DF2\u58F0\u660E\u7684\u8FDC\u7A0B\u524D\u7F00\u4E0B\u3002
|
|
221
|
+
\u4FEE\u6CD5: \u5728 remotePrefixes \u8865\u5145\u8BE5\u8DEF\u7531\u524D\u7F00 \u2192 \u8FDC\u7A0B\u540D\u7684\u6620\u5C04\uFF0C\u6216\u4FEE\u6B63\u9875\u9762\u8868 route\u3002`
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
for (const page of pages) remoteOf(page.route);
|
|
225
|
+
const specOf = (page) => {
|
|
226
|
+
const s = page.spec ?? (deriveSpec ? deriveSpec(page.route) : defaultDeriveSpec2(page.route));
|
|
227
|
+
return `${remoteOf(page.route)}/${String(s).replace(/^[./]+/, "")}`;
|
|
228
|
+
};
|
|
229
|
+
const baseNorm = base && base !== "/" ? base.endsWith("/") ? base : `${base}/` : "";
|
|
230
|
+
const matchSegments = (pattern, path) => {
|
|
231
|
+
const patSegs = pattern.split("/").filter(Boolean);
|
|
232
|
+
const pathSegs = path.split("/").filter(Boolean);
|
|
233
|
+
if (patSegs.length !== pathSegs.length) return null;
|
|
234
|
+
const params = {};
|
|
235
|
+
for (let i = 0; i < patSegs.length; i++) {
|
|
236
|
+
const seg = patSegs[i];
|
|
237
|
+
if (seg.startsWith(":")) {
|
|
238
|
+
try {
|
|
239
|
+
params[seg.slice(1)] = decodeURIComponent(pathSegs[i]);
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
} else if (seg !== pathSegs[i]) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return params;
|
|
248
|
+
};
|
|
249
|
+
let cacheSession;
|
|
250
|
+
const compCache = /* @__PURE__ */ new Map();
|
|
251
|
+
const resolve = (rawPath) => {
|
|
252
|
+
let clean = String(rawPath ?? "");
|
|
253
|
+
if (baseNorm) {
|
|
254
|
+
if (clean === baseNorm.replace(/\/$/, "")) clean = "/";
|
|
255
|
+
else if (clean.startsWith(baseNorm)) clean = `/${clean.slice(baseNorm.length)}`;
|
|
256
|
+
}
|
|
257
|
+
clean = (clean.replace(/\/+$/, "") || "/").split("?")[0].split("#")[0];
|
|
258
|
+
for (const page of pages) {
|
|
259
|
+
const params = matchSegments(page.route, clean);
|
|
260
|
+
if (params) return { page, remote: remoteOf(page.route), spec: specOf(page), params };
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
};
|
|
264
|
+
const component = (spec) => {
|
|
265
|
+
const sk = (globalThis.__FULGURJS_APP_CONFIG__ ?? {}).sessionKey;
|
|
266
|
+
const sessionKey = typeof sk === "string" ? sk : void 0;
|
|
267
|
+
if (sessionKey !== cacheSession) {
|
|
268
|
+
compCache.clear();
|
|
269
|
+
cacheSession = sessionKey;
|
|
270
|
+
}
|
|
271
|
+
let comp = compCache.get(spec);
|
|
272
|
+
if (!comp) {
|
|
273
|
+
const name = cleanCompName(spec);
|
|
274
|
+
const asyncComp = (0, import_vue.defineAsyncComponent)({
|
|
275
|
+
loader: async () => {
|
|
276
|
+
await beforeLoad?.();
|
|
277
|
+
const mod = await load(spec);
|
|
278
|
+
const inner = mod?.default ?? mod;
|
|
279
|
+
if (!inner) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
`[fulgurjs] remote module "${spec}" did not export a component (got ${inner === null ? "null" : typeof inner})\u3002
|
|
282
|
+
\u4FEE\u6CD5: \u6838\u5BF9\u8FDC\u7A0B exposes \u952E\u540D\u4E0E\u9875\u9762\u8868 spec \u662F\u5426\u4E00\u81F4\uFF08MFU-006 \u8BED\u4E49\uFF09`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
return inner;
|
|
286
|
+
},
|
|
287
|
+
loadingComponent,
|
|
288
|
+
errorComponent: errorComponent ?? RemoteErrorPlaceholder,
|
|
289
|
+
delay
|
|
290
|
+
});
|
|
291
|
+
comp = (0, import_vue.defineComponent)({
|
|
292
|
+
name,
|
|
293
|
+
inheritAttrs: false,
|
|
294
|
+
setup: (_props, ctx) => () => (0, import_vue.h)(asyncComp, ctx.attrs, ctx.slots)
|
|
295
|
+
});
|
|
296
|
+
compCache.set(spec, comp);
|
|
297
|
+
}
|
|
298
|
+
return comp;
|
|
299
|
+
};
|
|
300
|
+
const keepAliveNames = pages.filter((p) => p.keepAlive).map((p) => cleanCompName(specOf(p)));
|
|
301
|
+
return { pages, resolve, component, keepAliveNames };
|
|
302
|
+
}
|
|
63
303
|
// Annotate the CommonJS export names for ESM import in node:
|
|
64
304
|
0 && (module.exports = {
|
|
305
|
+
createHostPages,
|
|
65
306
|
createRemoteComponent
|
|
66
307
|
});
|
package/dist/vue-adapter.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Component } from 'vue';
|
|
2
|
+
import { PageRouteLike, RemoteSchemaEntry } from './pages.cjs';
|
|
2
3
|
|
|
3
4
|
/** Vue 适配层只接收加载函数,不静态引用运行时内核。 */
|
|
4
5
|
|
|
@@ -12,5 +13,50 @@ interface RemoteComponentOptions {
|
|
|
12
13
|
declare function createRemoteComponent(loadRemote: (spec: string, opts?: {
|
|
13
14
|
retries?: number;
|
|
14
15
|
}) => Promise<any>): (spec: string, opts?: RemoteComponentOptions) => Component;
|
|
16
|
+
interface HostPagesOptions {
|
|
17
|
+
/** 页面表(宿主路由与布局共用的唯一数据源;definePages R1–R5 校验照常执行) */
|
|
18
|
+
pages: PageRouteLike[];
|
|
19
|
+
/** 路由前缀 → remote 名(最长前缀匹配;页面路由无匹配前缀时创建期即报错) */
|
|
20
|
+
remotePrefixes: Record<string, string>;
|
|
21
|
+
/** 缺省 spec 推导(缺省 = 去首段前缀 + 剥 :参数 段,同 definePages 默认) */
|
|
22
|
+
deriveSpec?: (route: string) => string;
|
|
23
|
+
/** 远程 exposes 清单(dev 由 remoteSchema 提供;build 为空表时按语义诚实降级) */
|
|
24
|
+
schema?: Record<string, RemoteSchemaEntry>;
|
|
25
|
+
/** ERROR 级校验失败是否 throw(默认 true) */
|
|
26
|
+
strict?: boolean;
|
|
27
|
+
/** 站点 base 前缀(如 '/main'):resolve 时先剥离再匹配路由空间 */
|
|
28
|
+
base?: string;
|
|
29
|
+
/** 每次页面模块实际加载前执行(同步或异步);宿主在此提供最新 context */
|
|
30
|
+
beforeLoad?: () => void | Promise<void>;
|
|
31
|
+
/** 加载期占位组件(骨架屏) */
|
|
32
|
+
loadingComponent?: Component;
|
|
33
|
+
/** 错误占位组件(缺省 = 内置三段式错误占位:错误码+根因+修法) */
|
|
34
|
+
errorComponent?: Component;
|
|
35
|
+
/** 骨架屏延迟 ms(默认 200,防闪) */
|
|
36
|
+
delay?: number;
|
|
37
|
+
}
|
|
38
|
+
interface ResolvedHostPage {
|
|
39
|
+
/** 原页面记录(含宿主自由扩展字段 name/title/keepAlive/...) */
|
|
40
|
+
page: PageRouteLike;
|
|
41
|
+
/** 命中的 remote 名 */
|
|
42
|
+
remote: string;
|
|
43
|
+
/** 完整加载 spec(`<remote>/<exposes 键>`,loadRemote 直用) */
|
|
44
|
+
spec: string;
|
|
45
|
+
/** 路径参数(解码失败只让该次匹配失败,不让页面初始化崩溃) */
|
|
46
|
+
params: Record<string, string>;
|
|
47
|
+
}
|
|
48
|
+
interface HostPages {
|
|
49
|
+
/** 原页面记录 */
|
|
50
|
+
pages: PageRouteLike[];
|
|
51
|
+
/** 路径 → 页面解析(兼容 base 前缀与深链;无匹配返回 null) */
|
|
52
|
+
resolve(path: string): ResolvedHostPage | null;
|
|
53
|
+
/** 按 spec 取异步页面组件(同 spec 复用;换登录代次后重建以触发会话同步) */
|
|
54
|
+
component(spec: string): Component;
|
|
55
|
+
/** 保活白名单:keepAlive 页面的组件 name(与实际被 KeepAlive 缓存的包装组件一致) */
|
|
56
|
+
keepAliveNames: string[];
|
|
57
|
+
}
|
|
58
|
+
declare function createHostPages(options: HostPagesOptions, load: (spec: string, opts?: {
|
|
59
|
+
retries?: number;
|
|
60
|
+
}) => Promise<any>): HostPages;
|
|
15
61
|
|
|
16
|
-
export { type RemoteComponentOptions, createRemoteComponent };
|
|
62
|
+
export { type HostPages, type HostPagesOptions, type RemoteComponentOptions, type ResolvedHostPage, createHostPages, createRemoteComponent };
|
package/dist/vue-adapter.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Component } from 'vue';
|
|
2
|
+
import { PageRouteLike, RemoteSchemaEntry } from './pages.js';
|
|
2
3
|
|
|
3
4
|
/** Vue 适配层只接收加载函数,不静态引用运行时内核。 */
|
|
4
5
|
|
|
@@ -12,5 +13,50 @@ interface RemoteComponentOptions {
|
|
|
12
13
|
declare function createRemoteComponent(loadRemote: (spec: string, opts?: {
|
|
13
14
|
retries?: number;
|
|
14
15
|
}) => Promise<any>): (spec: string, opts?: RemoteComponentOptions) => Component;
|
|
16
|
+
interface HostPagesOptions {
|
|
17
|
+
/** 页面表(宿主路由与布局共用的唯一数据源;definePages R1–R5 校验照常执行) */
|
|
18
|
+
pages: PageRouteLike[];
|
|
19
|
+
/** 路由前缀 → remote 名(最长前缀匹配;页面路由无匹配前缀时创建期即报错) */
|
|
20
|
+
remotePrefixes: Record<string, string>;
|
|
21
|
+
/** 缺省 spec 推导(缺省 = 去首段前缀 + 剥 :参数 段,同 definePages 默认) */
|
|
22
|
+
deriveSpec?: (route: string) => string;
|
|
23
|
+
/** 远程 exposes 清单(dev 由 remoteSchema 提供;build 为空表时按语义诚实降级) */
|
|
24
|
+
schema?: Record<string, RemoteSchemaEntry>;
|
|
25
|
+
/** ERROR 级校验失败是否 throw(默认 true) */
|
|
26
|
+
strict?: boolean;
|
|
27
|
+
/** 站点 base 前缀(如 '/main'):resolve 时先剥离再匹配路由空间 */
|
|
28
|
+
base?: string;
|
|
29
|
+
/** 每次页面模块实际加载前执行(同步或异步);宿主在此提供最新 context */
|
|
30
|
+
beforeLoad?: () => void | Promise<void>;
|
|
31
|
+
/** 加载期占位组件(骨架屏) */
|
|
32
|
+
loadingComponent?: Component;
|
|
33
|
+
/** 错误占位组件(缺省 = 内置三段式错误占位:错误码+根因+修法) */
|
|
34
|
+
errorComponent?: Component;
|
|
35
|
+
/** 骨架屏延迟 ms(默认 200,防闪) */
|
|
36
|
+
delay?: number;
|
|
37
|
+
}
|
|
38
|
+
interface ResolvedHostPage {
|
|
39
|
+
/** 原页面记录(含宿主自由扩展字段 name/title/keepAlive/...) */
|
|
40
|
+
page: PageRouteLike;
|
|
41
|
+
/** 命中的 remote 名 */
|
|
42
|
+
remote: string;
|
|
43
|
+
/** 完整加载 spec(`<remote>/<exposes 键>`,loadRemote 直用) */
|
|
44
|
+
spec: string;
|
|
45
|
+
/** 路径参数(解码失败只让该次匹配失败,不让页面初始化崩溃) */
|
|
46
|
+
params: Record<string, string>;
|
|
47
|
+
}
|
|
48
|
+
interface HostPages {
|
|
49
|
+
/** 原页面记录 */
|
|
50
|
+
pages: PageRouteLike[];
|
|
51
|
+
/** 路径 → 页面解析(兼容 base 前缀与深链;无匹配返回 null) */
|
|
52
|
+
resolve(path: string): ResolvedHostPage | null;
|
|
53
|
+
/** 按 spec 取异步页面组件(同 spec 复用;换登录代次后重建以触发会话同步) */
|
|
54
|
+
component(spec: string): Component;
|
|
55
|
+
/** 保活白名单:keepAlive 页面的组件 name(与实际被 KeepAlive 缓存的包装组件一致) */
|
|
56
|
+
keepAliveNames: string[];
|
|
57
|
+
}
|
|
58
|
+
declare function createHostPages(options: HostPagesOptions, load: (spec: string, opts?: {
|
|
59
|
+
retries?: number;
|
|
60
|
+
}) => Promise<any>): HostPages;
|
|
15
61
|
|
|
16
|
-
export { type RemoteComponentOptions, createRemoteComponent };
|
|
62
|
+
export { type HostPages, type HostPagesOptions, type RemoteComponentOptions, type ResolvedHostPage, createHostPages, createRemoteComponent };
|