Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: unexpected suspense from useRouteMeta #2019

Merged
merged 2 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions src/client/theme-api/useRouteMeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
} from 'dumi';
import { useCallback, useState } from 'react';
import type { IRouteMeta, IRoutesById } from './types';
import { use, useIsomorphicLayoutEffect } from './utils';
import { useIsomorphicLayoutEffect } from './utils';

const cache = new Map<string, Promise<IRouteMeta | undefined>>();
const cache = new Map<string, IRouteMeta | Promise<IRouteMeta>>();
const EMPTY_META = {
frontmatter: {},
toc: [],
Expand All @@ -32,15 +32,14 @@ function getCachedRouteMeta(route: IRoutesById[string]) {
return meta;
};
const meta = merge(getRouteMetaById(route.id, { syncOnly: true }));
const ret: Parameters<typeof use<IRouteMeta>>[0] = Promise.resolve(meta);
const proxyGetter = (target: any, prop: string) => {
if (ASYNC_META_PROPS.includes(prop)) {
if (!cache.get(pendingCacheKey)) {
// load async meta then replace cache
cache.set(
pendingCacheKey,
getRouteMetaById(route.id).then((full) =>
cache.set(cacheKey, Promise.resolve(merge(full))).get(cacheKey),
cache.set(cacheKey, merge(full)).get(cacheKey),
),
);
}
Expand All @@ -52,16 +51,13 @@ function getCachedRouteMeta(route: IRoutesById[string]) {
return target[prop];
};

// return sync meta by default
ret.status = 'fulfilled';

// load async meta if property accessed
meta.tabs?.forEach((tab) => {
tab.meta = new Proxy(tab.meta, {
get: proxyGetter,
});
});
ret.value = new Proxy(meta, {
const ret = new Proxy(meta, {
get: proxyGetter,
});

Expand Down Expand Up @@ -93,11 +89,11 @@ export const useRouteMeta = () => {
return ret;
}, [clientRoutes.length, pathname]);
const [matchedRoute, setMatchedRoute] = useState(getter);
const meta = use(getCachedRouteMeta(matchedRoute));
const meta = getCachedRouteMeta(matchedRoute);

useIsomorphicLayoutEffect(() => {
setMatchedRoute(getter);
}, [clientRoutes.length, pathname]);

return meta!;
return meta;
};
33 changes: 0 additions & 33 deletions src/client/theme-api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,36 +146,3 @@ export const pickRouteSortMeta = (
export function getLocaleNav(nav: IUserNavValue | INav, locale: ILocale) {
return Array.isArray(nav) ? nav : nav[locale.id];
}

// Copy from React official demo.
type ReactPromise<T> = Promise<T> & {
status?: 'pending' | 'fulfilled' | 'rejected';
value?: T;
reason?: any;
};

/**
* @private Internal usage. Safe to remove
*/
export function use<T>(promise: ReactPromise<T>): T {
if (promise.status === 'fulfilled') {
return promise.value!;
} else if (promise.status === 'rejected') {
throw promise.reason;
} else if (promise.status === 'pending') {
throw promise;
} else {
promise.status = 'pending';
promise.then(
(result) => {
promise.status = 'fulfilled';
promise.value = result;
},
(reason) => {
promise.status = 'rejected';
promise.reason = reason;
},
);
throw promise;
}
}
2 changes: 0 additions & 2 deletions src/features/exports.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { IApi } from '@/types';
import path from 'path';
import { winPath } from 'umi/plugin-utils';

export default (api: IApi) => {
Expand All @@ -8,7 +7,6 @@ export default (api: IApi) => {
// allow import from dumi
api.modifyConfig((memo) => {
memo.alias['dumi$'] = '@@/dumi/exports';
memo.alias['dumi/dist'] = winPath(path.join(__dirname, '..'));

return memo;
});
Expand Down
34 changes: 33 additions & 1 deletion src/templates/meta/exports.ts.tpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
import { filesMeta, tabsMeta } from '.';
import type { IDemoData, IRouteMeta } from 'dumi/dist/client/theme-api/types';
import { use } from 'dumi/dist/client/theme-api/utils';

// Copy from React official demo.
type ReactPromise<T> = Promise<T> & {
status?: 'pending' | 'fulfilled' | 'rejected';
value?: T;
reason?: any;
};

/**
* @private Internal usage. Safe to remove
*/
export function use<T>(promise: ReactPromise<T>): T {
if (promise.status === 'fulfilled') {
return promise.value!;
} else if (promise.status === 'rejected') {
throw promise.reason;
} else if (promise.status === 'pending') {
throw promise;
} else {
promise.status = 'pending';
promise.then(
(result) => {
promise.status = 'fulfilled';
promise.value = result;
},
(reason) => {
promise.status = 'rejected';
promise.reason = reason;
},
);
throw promise;
}
}

const demoIdMap = Object.keys(filesMeta).reduce((total, current) => {
if (filesMeta[current].demoIndex) {
Expand Down
Loading