Skip to content

Commit c8d0f0c

Browse files
committed
chore: update usage
1 parent 8a072b6 commit c8d0f0c

File tree

8 files changed

+221
-67
lines changed

8 files changed

+221
-67
lines changed

apps/modern-component-data-fetch/host/src/routes/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const RemoteSSRComponent = createRemoteSSRComponent({
77
loading: 'loading...',
88
export: 'default',
99
fallback: ({ error }) => {
10+
console.log(33333333333);
11+
console.error(error);
1012
if (error instanceof Error && error.message.includes('not exist')) {
1113
return <div>fallback - not existed id</div>;
1214
}

apps/modern-component-data-fetch/provider/src/components/Content.data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const fetchData = async (): Promise<Data> => {
66
return new Promise((resolve) => {
77
setTimeout(() => {
88
resolve({
9-
data: 'fetch data from provider',
9+
data: `fetch data from provider ${new Date()}`,
1010
});
1111
}, 1000);
1212
});

apps/modern-component-data-fetch/provider/src/components/Content.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import stuff from './stuff.module.css';
44
import type { Data } from './Content.data';
55

66
const Content = (props: { _mfData: Data }): JSX.Element => {
7-
console.log(333, props);
87
return (
98
<div
109
id="nested-remote-components"

packages/modernjs/src/cli/mfRuntimePlugins/auto-fetch-data.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,26 @@ const autoFetchData: () => FederationRuntimePlugin = () => ({
4545
return args;
4646
}
4747

48+
const fetchData = host
49+
.loadRemote(dataFetchId)
50+
.then((m) => {
51+
if (
52+
m &&
53+
typeof m === 'object' &&
54+
'fetchData' in m &&
55+
typeof m.fetchData === 'function'
56+
) {
57+
return m.fetchData as () => Promise<unknown>;
58+
}
59+
})
60+
.catch((e) => {
61+
console.log('======= auto fetch plugin fetchData error', e);
62+
return undefined;
63+
});
64+
4865
helpers.global.nativeGlobal.__FEDERATION__.__DATA_FETCH_MAP__.set(
4966
key,
50-
host
51-
.loadRemote(dataFetchId)
52-
.then((m) => {
53-
if (
54-
m &&
55-
typeof m === 'object' &&
56-
'fetchData' in m &&
57-
typeof m.fetchData === 'function'
58-
) {
59-
console.log('======= auto fetch plugin fetchData', m.fetchData);
60-
return m.fetchData();
61-
}
62-
})
63-
.catch((e) => {
64-
console.log('======= auto fetch plugin fetchData error', e);
65-
return null;
66-
}),
67+
fetchData,
6768
);
6869

6970
return args;
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import React, { MutableRefObject, ReactNode, Suspense, useRef } from 'react';
2+
3+
function isPromise<T>(obj: any): obj is PromiseLike<T> {
4+
return (
5+
!!obj &&
6+
(typeof obj === 'object' || typeof obj === 'function') &&
7+
typeof obj.then === 'function'
8+
);
9+
}
10+
export const AWAIT_ERROR_PREFIX =
11+
'<Await /> caught the following error during render: ';
12+
13+
export const IsAwaitErrorText = (text: string) =>
14+
typeof text === 'string' && text.indexOf(AWAIT_ERROR_PREFIX) === 0;
15+
16+
export interface AwaitProps<T> {
17+
resolve: T | Promise<T>;
18+
loading?: ReactNode;
19+
errorElement?: ReactNode | ((data?: string) => ReactNode);
20+
children: (data: T) => ReactNode;
21+
}
22+
23+
export interface AwaitErrorHandlerProps<T = any>
24+
extends Omit<AwaitProps<T>, 'resolve'> {
25+
resolve: () => T | string;
26+
}
27+
28+
const DefaultLoading = <></>;
29+
const DefaultErrorElement = (_data: any) => <div>Error</div>;
30+
31+
export function Await<T>({
32+
resolve,
33+
loading = DefaultLoading,
34+
errorElement = DefaultErrorElement,
35+
children,
36+
}: AwaitProps<T>) {
37+
const dataRef = useRef<T>();
38+
const data = dataRef.current || resolve;
39+
const getData = isPromise(data) ? fetchData(data, dataRef) : () => data;
40+
41+
return (
42+
<AwaitSuspense
43+
loading={loading}
44+
errorElement={errorElement}
45+
// @ts-ignore
46+
resolve={getData}
47+
>
48+
{children}
49+
</AwaitSuspense>
50+
);
51+
}
52+
53+
function AwaitSuspense<T>({
54+
resolve,
55+
children,
56+
loading = DefaultLoading,
57+
errorElement = DefaultErrorElement,
58+
}: AwaitErrorHandlerProps<T>) {
59+
return (
60+
<Suspense fallback={loading}>
61+
<ResolveAwait
62+
resolve={resolve}
63+
loading={loading}
64+
errorElement={errorElement}
65+
>
66+
{children}
67+
</ResolveAwait>
68+
</Suspense>
69+
);
70+
}
71+
72+
function ResolveAwait<T>({
73+
children,
74+
resolve,
75+
loading,
76+
errorElement,
77+
}: AwaitErrorHandlerProps<T>) {
78+
const data = resolve();
79+
if (typeof data === 'string' && data.indexOf(AWAIT_ERROR_PREFIX) === 0) {
80+
return (
81+
<>
82+
{typeof errorElement === 'function' ? errorElement(data) : errorElement}
83+
</>
84+
);
85+
}
86+
const toRender =
87+
typeof children === 'function' ? children(data as T) : children;
88+
return <>{toRender}</>;
89+
}
90+
91+
// return string when promise is rejected
92+
const fetchData = <T,>(promise: Promise<T>, ref: MutableRefObject<T>) => {
93+
let data: T | string;
94+
let status: 'pending' | 'success' = 'pending';
95+
const suspender = promise
96+
.then((res) => {
97+
status = 'success';
98+
data = res;
99+
ref.current = res;
100+
})
101+
.catch((e) => {
102+
status = 'success';
103+
console.warn(e);
104+
data = AWAIT_ERROR_PREFIX + e;
105+
});
106+
return () => {
107+
if (status === 'pending') {
108+
throw suspender;
109+
}
110+
return data;
111+
};
112+
};

packages/modernjs/src/runtime/createRemoteSSRComponent.tsx

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import {
44
getInstance,
55
type FederationHost,
66
} from '@module-federation/enhanced/runtime';
7+
import { isBrowserEnv } from '@module-federation/sdk';
78
import {
89
ErrorBoundary,
910
ErrorBoundaryPropsWithComponent,
1011
} from 'react-error-boundary';
1112
import { getDataFetchInfo } from './utils';
13+
import { Await } from './Await';
1214

1315
type IProps = {
1416
id: string;
@@ -18,15 +20,31 @@ type IProps = {
1820

1921
type ReactKey = { key?: React.Key | null };
2022

21-
async function fetchData(id: string): Promise<unknown | undefined> {
22-
if (typeof window !== 'undefined') {
23-
// @ts-ignore
24-
return window._ssd;
23+
async function fetchData(
24+
id: string,
25+
uid: string,
26+
): Promise<unknown | undefined> {
27+
if (isBrowserEnv()) {
28+
if (!globalThis._MF__DATA_FETCH_ID_MAP__) {
29+
globalThis._MF__DATA_FETCH_ID_MAP__ = {};
30+
}
31+
if (globalThis._MF__DATA_FETCH_ID_MAP__[uid]) {
32+
return;
33+
}
34+
let res;
35+
let rej;
36+
const p = new Promise((resolve, reject) => {
37+
res = resolve;
38+
rej = reject;
39+
});
40+
globalThis._MF__DATA_FETCH_ID_MAP__[uid] = [p, res, rej];
41+
return globalThis._MF__DATA_FETCH_ID_MAP__[uid][0];
2542
}
2643
const instance = getInstance();
2744
if (!instance) {
2845
return;
2946
}
47+
3048
const { name } = instance.remoteHandler.idToRemoteMap[id] || {};
3149
if (!name) {
3250
return;
@@ -44,12 +62,18 @@ async function fetchData(id: string): Promise<unknown | undefined> {
4462
return;
4563
}
4664
const key = `${name}@${module.remoteInfo.version}@${dataFetchInfo.dataFetchName}`;
47-
console.log('------key', key);
48-
console.log(
49-
'------ helpers.global.nativeGlobal.__FEDERATION__.__DATA_FETCH_MAP__[key];',
50-
helpers.global.nativeGlobal.__FEDERATION__.__DATA_FETCH_MAP__,
51-
);
52-
return helpers.global.nativeGlobal.__FEDERATION__.__DATA_FETCH_MAP__.get(key);
65+
const fetchDataPromise =
66+
helpers.global.nativeGlobal.__FEDERATION__.__DATA_FETCH_MAP__.get(key);
67+
if (!fetchDataPromise) {
68+
return;
69+
}
70+
71+
const fetchDataFn = await fetchDataPromise;
72+
if (!fetchDataFn) {
73+
return;
74+
}
75+
console.log('fetchDataFn: ', fetchDataFn.toString());
76+
return fetchDataFn();
5377
}
5478

5579
function getLoadedRemoteInfos(instance: FederationHost, id: string) {
@@ -175,49 +199,60 @@ export function createRemoteSSRComponent<T, E extends keyof T>(info: {
175199
fallback: ErrorBoundaryPropsWithComponent['FallbackComponent'];
176200
export?: E;
177201
}) {
202+
// const [uid,setUid] = useState('')
203+
console.log('createRemoteSSRComponent trigger');
204+
178205
type ComponentType = T[E] extends (...args: any) => any
179206
? Parameters<T[E]>[0] extends undefined
180207
? ReactKey
181208
: Parameters<T[E]>[0] & ReactKey
182209
: ReactKey;
183210
const exportName = info?.export || 'default';
184211

212+
const callLoader = async () => {
213+
const m = (await info.loader()) as Record<string, React.FC> &
214+
Record<symbol, string>;
215+
if (!m) {
216+
throw new Error('load remote failed');
217+
}
218+
return m;
219+
};
220+
221+
const getData = async () => {
222+
// const nodeUid = isBrowserEnv () ? document.get : uid
223+
const m = await callLoader();
224+
const moduleId = m && m[Symbol.for('mf_module_id')];
225+
const data = await fetchData(moduleId, moduleId);
226+
console.log('data: ', data);
227+
return data;
228+
};
229+
185230
const LazyComponent = React.lazy(async () => {
186231
try {
187-
const m = (await info.loader()) as Record<string, React.FC> &
188-
Record<symbol, string>;
189-
if (!m) {
190-
throw new Error('load remote failed');
191-
}
232+
const m = await callLoader();
192233
const moduleId = m && m[Symbol.for('mf_module_id')];
193234

194235
const assets = collectSSRAssets({
195236
id: moduleId,
196237
});
197-
const data =
198-
//@ts-ignore
199-
typeof window !== 'undefined' ? window._ssd : await fetchData(moduleId);
200-
201-
// const data = {data:'fetch data from provider'}
202-
203-
// console.log('assets: ', assets);
204-
console.log(assets.length);
205-
console.log('mfData: ', data);
206238

207239
const Com = m[exportName] as React.FC<ComponentType>;
208240
if (exportName in m && typeof Com === 'function') {
209241
return {
210-
default: (props: Omit<ComponentType, 'key'>) => (
242+
default: (
243+
props: Omit<ComponentType, 'key'> & { _mfData: unknown },
244+
) => (
211245
<>
212246
<script
247+
suppressHydrationWarning
213248
dangerouslySetInnerHTML={{
214249
__html: String.raw`
215-
window._ssd=${JSON.stringify(data)}
250+
globalThis._MF__DATA_FETCH_ID_MAP__['${moduleId}'][1](${JSON.stringify(props._mfData)})
216251
`,
217252
}}
218253
></script>
219254
{assets}
220-
<Com {...props} _mfData={data} />
255+
<Com {...props} />
221256
</>
222257
),
223258
};
@@ -246,14 +281,16 @@ export function createRemoteSSRComponent<T, E extends keyof T>(info: {
246281
};
247282
}
248283
});
284+
249285
return (props: ComponentType) => {
250286
const { key, ...args } = props;
287+
251288
return (
252289
<ErrorBoundary FallbackComponent={info.fallback}>
253-
<React.Suspense fallback={info.loading}>
290+
<Await resolve={getData()} loading={info.loading}>
254291
{/* @ts-ignore */}
255-
<LazyComponent {...args} />
256-
</React.Suspense>
292+
{(data) => <LazyComponent {...args} _mfData={data} />}
293+
</Await>
257294
</ErrorBoundary>
258295
);
259296
};

packages/runtime-core/src/global.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ export interface Federation {
2424
__SHARE__: GlobalShareScopeMap;
2525
__MANIFEST_LOADING__: Record<string, Promise<ModuleInfo>>;
2626
__PRELOADED_MAP__: Map<string, boolean>;
27-
__DATA_FETCH_MAP__: Map<string, Promise<unknown>>;
27+
__DATA_FETCH_MAP__: Map<
28+
string,
29+
Promise<(() => Promise<unknown>) | undefined>
30+
>;
2831
}
2932
export const CurrentGlobal =
3033
typeof globalThis === 'object' ? globalThis : window;

0 commit comments

Comments
 (0)