Skip to content
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
33 changes: 17 additions & 16 deletions packages/core/src/runtime/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {
DataContext,
isEqualPath,
matchRoutes,
normalizeRoutePath,
pathnameToRouteService,
useLocation,
} from '@rspress/runtime';
import {
Expand Down Expand Up @@ -31,40 +30,42 @@ type PageMeta = {
};

export async function initPageData(routePath: string): Promise<PageData> {
const { routes } = process.env.__SSR__
? // biome-ignore format: Biome format will add an extra comma.
((await import('virtual-routes-ssr')) as typeof import(
'virtual-routes-ssr'
))
: ((await import('virtual-routes')) as typeof import('virtual-routes'));
const matched = matchRoutes(routes, routePath)!;
if (matched) {
const matchedRoute = pathnameToRouteService(routePath)!;
if (matchedRoute) {
// Preload route component
const matchedRoute = matched[0].route;
const mod = await matchedRoute.preload();
const pagePath = cleanUrl(matched[0].route.filePath);
const pagePath = cleanUrl(matchedRoute.filePath);
const extractPageInfo = siteData.pages.find(page => {
const normalize = (p: string) =>
// compat the path that has no / suffix and ignore case
p
.replace(/\/$/, '')
.toLowerCase();
return isEqualPath(normalize(page.routePath), normalize(routePath));
return isEqualPath(
normalize(page.routePath),
normalize(matchedRoute.path),
);
});

// FIXME: when sidebar item is configured as link string, the sidebar text won't updated when page title changed
// Reason: The sidebar item text depends on pageData, which is not updated when page title changed, because the pageData is computed once when build
const encodedPagePath = encodeURIComponent(pagePath);
const meta: PageMeta =
mod.default.__RSPRESS_PAGE_META?.[encodedPagePath] || {};
(
mod.default as unknown as {
__RSPRESS_PAGE_META: Record<string, PageMeta>;
}
).__RSPRESS_PAGE_META?.[encodedPagePath] || ({} as PageMeta);
// mdx loader will generate __RSPRESS_PAGE_META,
// if the filePath don't match it, we can get meta from j(t)sx if we customize it
const {
toc = [],
title = '',
frontmatter = {},
...rest
} = MDX_REGEXP.test(matchedRoute.filePath) ? meta : mod;
} = MDX_REGEXP.test(matchedRoute.filePath)
? meta
: (mod as unknown as PageMeta);
return {
siteData,
page: {
Expand Down Expand Up @@ -138,7 +139,7 @@ export function App({ helmetContext }: { helmetContext?: object }) {
useLayoutEffect(() => {
async function refetchData() {
try {
const pageData = await initPageData(normalizeRoutePath(pathname));
const pageData = await initPageData(pathname);
setPageData(pageData);
} catch (e) {
console.log(e);
Expand Down
12 changes: 3 additions & 9 deletions packages/core/src/runtime/clientEntry.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
BrowserRouter,
DataContext,
ThemeContext,
normalizeRoutePath,
} from '@rspress/runtime';
import { BrowserRouter, DataContext, ThemeContext } from '@rspress/runtime';
import { isProduction } from '@rspress/shared';
import { useMemo, useState } from 'react';
import siteData from 'virtual-site-data';
Expand All @@ -18,9 +13,8 @@ export async function renderInBrowser() {
const container = document.getElementById('root')!;

const enhancedApp = async () => {
const initialPageData = await initPageData(
normalizeRoutePath(window.location.pathname),
);
const initialPageData = await initPageData(window.location.pathname);

return function RootApp() {
const [data, setData] = useState(initialPageData);
const [theme, setTheme] = useThemeState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,6 @@ export default (props: {

const onClickOutside = useCallback(
(e: MouseEvent) => {
console.log(
!contains(triggerRef.current, e.target),
triggerRef.current,
e.target,
);
if (!contains(triggerRef.current, e.target)) {
setShowQRCode(false);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/rslib.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default defineConfig({
{
bundle: false,
source: {
entry: { index: 'src/**' },
entry: { index: ['src/**', '!src/**.test.ts'] },
},
dts: true,
format: 'esm',
Expand Down
16 changes: 4 additions & 12 deletions packages/runtime/src/Content.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
// @ts-expect-error __VIRTUAL_ROUTES__ will be determined at build time
import { routes } from '__VIRTUAL_ROUTES__';
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable @typescript-eslint/no-require-imports */
import { type ReactElement, type ReactNode, Suspense, memo } from 'react';
import { matchRoutes, useLocation } from 'react-router-dom';
import { useLocation } from 'react-router-dom';
import siteData from 'virtual-site-data';
import { useViewTransition } from './hooks';
import { normalizeRoutePath } from './utils';
import { pathnameToRouteService } from './route';

function TransitionContentImpl(props: { el: ReactElement }) {
let element = props.el;
if (siteData?.themeConfig?.enableContentAnimation) {
// eslint-disable-next-line react-hooks/rules-of-hooks
element = useViewTransition(props.el);
}
return element;
Expand All @@ -24,14 +19,11 @@ const TransitionContent = memo(

export const Content = ({ fallback = <></> }: { fallback?: ReactNode }) => {
const { pathname } = useLocation();
const matched = matchRoutes(
routes as typeof import('virtual-routes')['routes'],
normalizeRoutePath(pathname),
);
const matched = pathnameToRouteService(pathname);
if (!matched) {
return <div></div>;
}
const routesElement = matched[0].route.element;
const routesElement = matched.element;

// React 17 Suspense SSR is not supported
if (!process.env.__REACT_GTE_18__ && process.env.__SSR__) {
Expand Down
15 changes: 12 additions & 3 deletions packages/runtime/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
export * from './hooks';
export * from './Content';
export {
DataContext,
ThemeContext,
useDark,
useI18n,
useLang,
usePageData,
useVersion,
useViewTransition,
} from './hooks';
export { Content } from './Content';
export {
normalizeHrefInRuntime,
normalizeImagePath,
Expand All @@ -9,7 +18,6 @@ export {
removeTrailingSlash,
normalizeSlash,
isProduction,
normalizeRoutePath,
isEqualPath,
} from './utils';
export {
Expand All @@ -19,5 +27,6 @@ export {
BrowserRouter,
useSearchParams,
} from 'react-router-dom';
export { pathnameToRouteService } from './route';
export { Helmet } from 'react-helmet-async';
export { NoSSR } from './NoSSR';
70 changes: 70 additions & 0 deletions packages/runtime/src/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, expect, it, vi } from 'vitest';
import { pathnameToRouteService } from './route';

vi.mock('__VIRTUAL_ROUTES__', () => {
const element = vi.fn();
const routes = [
{
path: '/api/config',
element,
filePath: 'api/config.mdx',
preload: async () => {},
lang: '',
version: '',
},
{
path: '/api/config/',
element,
filePath: 'api/config/index.mdx',
preload: async () => {},
lang: '',
version: '',
},
{
path: '/',
element,
filePath: 'index.mdx',
preload: async () => {},
lang: '',
version: '',
},
];
return { routes };
});

describe('pathnameToRouteService', () => {
it('0. /api/config', () => {
const pathname = '/api/config';
// currently we do not support both /api/config.mdx and /api/config/index.mdx
expect(pathnameToRouteService(pathname)?.path).toMatchInlineSnapshot(
`"/api/config/"`,
);
});

it('1. /api/config.html', () => {
const pathname = '/api/config.html';
expect(pathnameToRouteService(pathname)?.path).toMatchInlineSnapshot(
`"/api/config/"`,
);
});

it('2. /api/config/', () => {
const pathname = '/api/config/';
expect(pathnameToRouteService(pathname)?.path).toMatchInlineSnapshot(
`"/api/config/"`,
);
});

it('3. /api/config/index', () => {
const pathname = '/api/config/index';
expect(pathnameToRouteService(pathname)?.path).toMatchInlineSnapshot(
`"/api/config/"`,
);
});
it('4. /api/config/index.html', () => {
const pathname = '/api/config/index.html';
expect(pathnameToRouteService(pathname)?.path).toMatchInlineSnapshot(
`"/api/config/"`,
);
});
});
32 changes: 32 additions & 0 deletions packages/runtime/src/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Route } from '@rspress/shared';
// @ts-expect-error __VIRTUAL_ROUTES__ will be determined at build time
import { routes } from '__VIRTUAL_ROUTES__';
import { matchRoutes } from 'react-router-dom';

function normalizeRoutePath(routePath: string) {
return decodeURIComponent(routePath)
.replace(/\.html$/, '')
.replace(/\/index$/, '/');
}

const cache = new Map<string, Route>();
/**
* this is a bridge of two core features Sidebar and RouteService
* @param pathname useLocation().pathname
* @returns
*/
export function pathnameToRouteService(pathname: string): Route | undefined {
const cacheItem = cache.get(pathname);
if (cacheItem) {
return cacheItem;
}
const matched = matchRoutes(
routes as typeof import('virtual-routes')['routes'],
normalizeRoutePath(pathname),
);
const route: Route | undefined = matched?.[0]?.route;
if (route) {
cache.set(pathname, route);
}
return route;
}
6 changes: 0 additions & 6 deletions packages/runtime/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ import {
} from '@rspress/shared';
import siteData from 'virtual-site-data';

export function normalizeRoutePath(routePath: string) {
return decodeURIComponent(routePath)
.replace(/\.html$/, '')
.replace(/\/index$/, '/');
}

export function withBase(url = '/'): string {
return rawWithBase(url, siteData.base);
}
Expand Down
3 changes: 2 additions & 1 deletion packages/runtime/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
"path": "../shared"
}
],
"include": ["src"]
"include": ["src"],
"exclude": ["src/**/*.test.ts"]
}
13 changes: 4 additions & 9 deletions packages/theme-default/src/components/Link/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {
isEqualPath,
matchRoutes,
normalizeHrefInRuntime as normalizeHref,
normalizeRoutePath,
pathnameToRouteService,
useLocation,
useNavigate,
withBase,
Expand All @@ -11,7 +10,6 @@ import { isExternalUrl } from '@rspress/shared';
import nprogress from 'nprogress';
import type React from 'react';
import type { ComponentProps } from 'react';
import { routes } from 'virtual-routes';
import { scrollToTarget } from '../../logic';
import styles from './index.module.scss';

Expand Down Expand Up @@ -73,15 +71,12 @@ export function Link(props: LinkProps) {

// handle normal link
if (!process.env.__SSR__ && !inCurrentPage) {
const matchedRoutes = matchRoutes(
routes,
normalizeRoutePath(withBaseUrl),
);
if (matchedRoutes?.length) {
const matchedRoute = pathnameToRouteService(withBaseUrl);
if (matchedRoute) {
const timer = setTimeout(() => {
nprogress.start();
}, 200);
await matchedRoutes[0].route.preload();
await matchedRoute.preload();
clearTimeout(timer);
nprogress.done();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class LocalProvider implements Provider {
#cyrillicIndex?: FlexSearchDocumentWithType;

async #getPages(lang: string, version: string): Promise<PageIndexInfo[]> {
const searchIndexGroupID = `${version}###${lang}`;
const searchIndexGroupID = `${version ?? ''}###${lang ?? ''}`;
const searchIndexVersion = version ? `.${version.replace('.', '_')}` : '';
const searchIndexLang = lang ? `.${lang}` : '';
const searchIndexURL = `${removeTrailingSlash(__WEBPACK_PUBLIC_PATH__)}/static/${SEARCH_INDEX_NAME}${searchIndexVersion}${searchIndexLang}.${searchIndexHash[searchIndexGroupID]}.json`;
Expand Down
Loading
Loading