Skip to content
Draft
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
7 changes: 3 additions & 4 deletions packages/docusaurus-module-type-aliases/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
},
"dependencies": {
"@docusaurus/types": "3.10.1",
"@types/history": "^4.7.11",
"@types/react": "19.2.14",
"@types/react-router-config": "*",
"@types/react-router-dom": "*",
"history": "^5.3.0",
"react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
"react-loadable": "npm:@docusaurus/react-loadable@6.0.0"
"react-loadable": "npm:@docusaurus/react-loadable@6.0.0",
"react-router": "^8.0.1"
},
"peerDependencies": {
"react": "*",
Expand Down
69 changes: 61 additions & 8 deletions packages/docusaurus-module-type-aliases/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,17 @@ declare module '@generated/registry' {
}

declare module '@generated/routes' {
import type {RouteConfig as RRRouteConfig} from 'react-router-config';
import type Loadable from 'react-loadable';

type RouteConfig = RRRouteConfig & {
// At runtime the route `component` is a react-loadable component (with a
// `.preload()` method), not the string module path used in the route config.
type RouteConfig = {
path: string;
component: ReturnType<typeof Loadable>;
exact?: boolean;
strict?: boolean;
routes?: RouteConfig[];
[attribute: string]: unknown;
};
const routes: RouteConfig[];
export default routes;
Expand Down Expand Up @@ -169,9 +174,17 @@ declare module '@docusaurus/Head' {

declare module '@docusaurus/Link' {
import type {CSSProperties, ComponentProps, ReactNode} from 'react';
import type {NavLinkProps as RRNavLinkProps} from 'react-router-dom';

type NavLinkProps = Partial<RRNavLinkProps>;
import type {Location} from 'history';

// React Router v6+ removed NavLink's `isActive`/`activeClassName`/`exact`/
// `strict` props; Docusaurus reimplements them, so we declare them ourselves.
type NavLinkProps = {
readonly exact?: boolean;
readonly strict?: boolean;
readonly activeClassName?: string;
readonly activeStyle?: CSSProperties;
readonly isActive?: (match: unknown, location: Location) => boolean;
};
export type Props = NavLinkProps &
ComponentProps<'a'> & {
readonly className?: string;
Expand Down Expand Up @@ -260,7 +273,34 @@ declare module '@docusaurus/Translate' {
}

declare module '@docusaurus/router' {
export {useHistory, useLocation, Redirect, matchPath} from 'react-router-dom';
import type {ComponentType, ReactNode} from 'react';
import type {History, Location, To} from 'history';

export function useHistory(): History;
export function useLocation(): Location;
export function matchPath(
pathname: string,
options?:
| string
| {
path?: string | string[];
exact?: boolean;
strict?: boolean;
sensitive?: boolean;
},
): {
path: string | undefined;
url: string;
isExact: boolean;
params: {[paramName: string]: string | undefined};
} | null;

export type RedirectProps = {
readonly to: To;
readonly push?: boolean;
readonly children?: ReactNode;
};
export const Redirect: ComponentType<RedirectProps>;
}

declare module '@docusaurus/useIsomorphicLayoutEffect' {
Expand Down Expand Up @@ -351,9 +391,22 @@ declare module '@docusaurus/Noop' {
}

declare module '@docusaurus/renderRoutes' {
import {renderRoutes} from 'react-router-config';
import type {ReactElement, ReactNode} from 'react';

type RouteConfig = {
path?: string;
component?: unknown;
exact?: boolean;
strict?: boolean;
render?: (props: {[key: string]: unknown}) => ReactNode;
routes?: RouteConfig[];
[attribute: string]: unknown;
};

export default renderRoutes;
export default function renderRoutes(
routes: RouteConfig[],
extraProps?: {[propName: string]: unknown},
): ReactElement;
}

declare module '@docusaurus/useGlobalData' {
Expand Down
1 change: 0 additions & 1 deletion packages/docusaurus-plugin-content-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"@docusaurus/utils": "3.10.1",
"@docusaurus/utils-common": "3.10.1",
"@docusaurus/utils-validation": "3.10.1",
"@types/react-router-config": "^5.0.7",
"combine-promises": "^1.1.0",
"fs-extra": "^11.2.0",
"js-yaml": "^4.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -635,21 +635,22 @@ declare module '@theme/DocBreadcrumbs' {

declare module '@theme/DocsRoot' {
import type {ReactNode} from 'react';
import type {RouteConfigComponentProps} from 'react-router-config';
import type {Required} from 'utility-types';
import type {RouteConfig} from '@docusaurus/types';

export interface Props extends Required<RouteConfigComponentProps, 'route'> {}
export interface Props {
readonly route: RouteConfig;
}

export default function DocsRoot(props: Props): ReactNode;
}

declare module '@theme/DocVersionRoot' {
import type {ReactNode} from 'react';
import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs';
import type {RouteConfigComponentProps} from 'react-router-config';
import type {Required} from 'utility-types';
import type {RouteConfig} from '@docusaurus/types';

export interface Props extends Required<RouteConfigComponentProps, 'route'> {
export interface Props {
readonly route: RouteConfig;
readonly version: PropVersionMetadata;
}

Expand All @@ -658,10 +659,11 @@ declare module '@theme/DocVersionRoot' {

declare module '@theme/DocRoot' {
import type {ReactNode} from 'react';
import type {RouteConfigComponentProps} from 'react-router-config';
import type {Required} from 'utility-types';
import type {RouteConfig} from '@docusaurus/types';

export interface Props extends Required<RouteConfigComponentProps, 'route'> {}
export interface Props {
readonly route: RouteConfig;
}

export default function DocRoot(props: Props): ReactNode;
}
2 changes: 1 addition & 1 deletion packages/docusaurus-theme-classic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"postcss": "^8.5.12",
"prism-react-renderer": "^2.4.1",
"prismjs": "^1.29.0",
"react-router-dom": "^5.3.4",
"react-router": "^8.0.1",
"rtlcss": "^4.1.0",
"tslib": "^2.6.0",
"utility-types": "^3.10.0"
Expand Down
3 changes: 1 addition & 2 deletions packages/docusaurus-theme-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@
"@docusaurus/module-type-aliases": "3.10.1",
"@docusaurus/utils": "3.10.1",
"@docusaurus/utils-common": "3.10.1",
"@types/history": "^4.7.11",
"@types/react": "19.2.14",
"@types/react-router-config": "*",
"clsx": "^2.0.0",
"history": "^5.3.0",
"parse-numeric-range": "^1.3.0",
"prism-react-renderer": "^2.4.1",
"tslib": "^2.6.0",
Expand Down
29 changes: 24 additions & 5 deletions packages/docusaurus-theme-common/src/utils/historyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,30 @@ type HistoryBlockHandler = (location: Location, action: Action) => void | false;
function useHistoryActionHandler(handler: HistoryBlockHandler): void {
const history = useHistory();
const stableHandler = useEvent(handler);
useEffect(
useEffect(() => {
// history v5 changed the block() API: the blocker now receives a
// "transition" object and the navigation stays blocked until `retry()` is
// called. We adapt it to the previous `(location, action) => false` API
// (returning `false` cancels the navigation), re-registering the blocker
// after each allowed navigation.
// See https://github.com/remix-run/history/blob/main/docs/blocking-transitions.md
() => history.block((location, action) => stableHandler(location, action)),
[history, stableHandler],
);
let unblock: () => void = () => {};
const block = () => {
unblock = history.block((transition) => {
const result = stableHandler(transition.location, transition.action);
if (result === false) {
// Cancel the navigation: keep blocking, don't retry.
return;
}
// Allow the navigation, then re-arm the blocker for next time.
unblock();
transition.retry();
block();
});
};
block();
return () => unblock();
}, [history, stableHandler]);
}

/**
Expand All @@ -51,7 +70,7 @@ export function useHistoryPopHandler(handler: HistoryBlockHandler): void {
* @param selector
*/
export function useHistorySelector<Value>(
selector: (history: History<unknown>) => Value,
selector: (history: History) => Value,
): Value {
const history = useHistory();
return useSyncExternalStore(
Expand Down
8 changes: 6 additions & 2 deletions packages/docusaurus-theme-common/src/utils/routesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import {useMemo} from 'react';
import generatedRoutes from '@generated/routes';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import type {RouteConfig} from 'react-router-config';
import type {RouteConfig} from '@docusaurus/types';

/**
* Compare the 2 paths, case insensitive and ignoring trailing slash
Expand Down Expand Up @@ -69,7 +69,11 @@ export function findHomePageRoute({
export function useHomePageRoute(): RouteConfig | undefined {
const {baseUrl} = useDocusaurusContext().siteConfig;
return useMemo(
() => findHomePageRoute({routes: generatedRoutes, baseUrl}),
() =>
findHomePageRoute({
routes: generatedRoutes as unknown as RouteConfig[],
baseUrl,
}),
[baseUrl],
);
}
2 changes: 1 addition & 1 deletion packages/docusaurus-types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
"license": "MIT",
"dependencies": {
"@mdx-js/mdx": "^3.1.1",
"@types/history": "^4.7.11",
"@types/mdast": "^4.0.2",
"@types/react": "19.2.14",
"commander": "^5.1.0",
"history": "^5.3.0",
"joi": "^18.1.2",
"react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
"utility-types": "^3.10.0",
Expand Down
8 changes: 2 additions & 6 deletions packages/docusaurus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"eval": "^0.1.8",
"execa": "^5.1.1",
"fs-extra": "^11.2.0",
"history": "^5.3.0",
"html-tags": "^3.3.1",
"html-webpack-plugin": "^5.6.7",
"leven": "^3.1.0",
Expand All @@ -63,9 +64,7 @@
"react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
"react-loadable": "npm:@docusaurus/react-loadable@6.0.0",
"react-loadable-ssr-addon-v5-slorber": "^1.0.3",
"react-router": "^5.3.4",
"react-router-config": "^5.1.1",
"react-router-dom": "^5.3.4",
"react-router": "^8.0.1",
"semver": "^7.7.4",
"serve-handler": "^6.1.7",
"tinypool": "^2.1.0",
Expand All @@ -81,10 +80,7 @@
"@docusaurus/types": "3.10.1",
"@total-typescript/shoehorn": "^0.1.2",
"@types/escape-html": "^1.0.4",
"@types/history": "^4.7.11",
"@types/react-dom": "^19.2.3",
"@types/react-router-dom": "^5.3.3",
"@types/react-router-config": "^5.0.7",
"@types/serve-handler": "^6.1.4",
"@types/update-notifier": "^6.0.4",
"@types/webpack-bundle-analyzer": "^4.7.0",
Expand Down
11 changes: 7 additions & 4 deletions packages/docusaurus/src/client/PendingNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
*/

import React, {type ReactNode} from 'react';
import {Route} from 'react-router-dom';
import ClientLifecyclesDispatcher, {
dispatchLifecycleAction,
} from './ClientLifecyclesDispatcher';
import ExecutionEnvironment from './exports/ExecutionEnvironment';
import {LocationOverrideProvider} from './exports/router';
import preload from './preload';
import type {Location} from 'history';

Expand Down Expand Up @@ -82,13 +82,16 @@ class PendingNavigation extends React.Component<Props, State> {

override render(): ReactNode {
const {children, location} = this.props;
// Use a controlled <Route> to trick all descendants into rendering the old
// location.
// Use a location override context to trick all descendants into rendering
// the old location until the next route has loaded (React Router v6+ removed
// the controlled `<Route location={...}>` API used previously).
return (
<ClientLifecyclesDispatcher
previousLocation={this.previousLocation}
location={location}>
<Route location={location} render={() => children} />
<LocationOverrideProvider location={location}>
{children}
</LocationOverrideProvider>
</ClientLifecyclesDispatcher>
);
}
Expand Down
26 changes: 14 additions & 12 deletions packages/docusaurus/src/client/clientEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,17 @@
* LICENSE file in the root directory of this source tree.
*/

import React, {startTransition, type ReactNode} from 'react';
import React, {startTransition} from 'react';
import ReactDOM, {type ErrorInfo} from 'react-dom/client';
import {HelmetProvider} from 'react-helmet-async';
import {BrowserRouter, HashRouter} from 'react-router-dom';
import {createBrowserHistory, createHashHistory} from 'history';
import siteConfig from '@generated/docusaurus.config';
import ExecutionEnvironment from './exports/ExecutionEnvironment';
import {DocusaurusRouter} from './exports/router';
import App from './App';
import preload from './preload';
import docusaurus from './docusaurus';

function Router({children}: {children: ReactNode}): ReactNode {
return siteConfig.future.experimental_router === 'hash' ? (
<HashRouter>{children}</HashRouter>
) : (
<BrowserRouter>{children}</BrowserRouter>
);
}

const hydrate = Boolean(process.env.HYDRATE_CLIENT_ENTRY);

// Client-side render (e.g: running in browser) to become single-page
Expand All @@ -31,11 +24,20 @@ if (ExecutionEnvironment.canUseDOM) {
window.docusaurus = docusaurus;
const container = document.getElementById('__docusaurus')!;

// React Router v6+ no longer exposes a mutable history object, so Docusaurus
// creates its own (from the `history` package) and drives React Router with it
// through <DocusaurusRouter>. This powers useHistory() (blocking, listening,
// querystring updates).
const history =
siteConfig.future.experimental_router === 'hash'
? createHashHistory()
: createBrowserHistory();

const app = (
<HelmetProvider>
<Router>
<DocusaurusRouter history={history}>
<App />
</Router>
</DocusaurusRouter>
</HelmetProvider>
);

Expand Down
2 changes: 1 addition & 1 deletion packages/docusaurus/src/client/docusaurus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/

import {matchRoutes} from 'react-router-config';
import routesChunkNames from '@generated/routesChunkNames';
import routes from '@generated/routes';
import {matchRoutes} from './matchRoutes';
import prefetchHelper from './prefetch';
import preloadHelper from './preload';
import flat from './flat';
Expand Down
Loading