Skip to content
Open
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
*/

import {describe, expect, it, vi} from 'vitest';
import normalizeLocation from '../normalizeLocation';
import normalizeLocation, {
normalizeBrowserPathname,
} from '../normalizeLocation';
import type {Location} from 'history';

vi.mock('@generated/routes', () => import('./__mocks__/@generated/routes'));
Expand Down Expand Up @@ -111,3 +113,19 @@ describe('normalizeLocation', () => {
});
});
});

describe('normalizeBrowserPathname', () => {
it('normalizes the decoded pathname', () => {
expect(normalizeBrowserPathname('/index.html')).toBe('/');
expect(normalizeBrowserPathname('/docs/installation.html')).toBe(
'/docs/installation',
);
expect(normalizeBrowserPathname('/caf%C3%A9.html')).toBe('/caf%C3%A9.html');
});

it('returns a pathname that can be decoded', () => {
const pathname = normalizeBrowserPathname('/100%25.html');
expect(pathname).toBe('/100%25');
expect(decodeURI(pathname)).toBe('/100%');
});
});
5 changes: 4 additions & 1 deletion packages/docusaurus/src/client/clientEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import siteConfig from '@generated/docusaurus.config';
import ExecutionEnvironment from './exports/ExecutionEnvironment';
import App from './App';
import preload from './preload';
import {normalizeBrowserPathname} from './normalizeLocation';
import docusaurus from './docusaurus';

function Router({children}: {children: ReactNode}): ReactNode {
Expand Down Expand Up @@ -63,7 +64,9 @@ if (ExecutionEnvironment.canUseDOM) {
}
};

preload(window.location.pathname).then(() => {
// Preload the route App renders, or hydration of /page.html URLs can leave
// the server HTML in place: https://github.com/facebook/docusaurus/pull/10059
preload(normalizeBrowserPathname(window.location.pathname)).then(() => {
startTransition(renderApp);
});

Expand Down
38 changes: 22 additions & 16 deletions packages/docusaurus/src/client/normalizeLocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,35 @@ import type {Location} from 'history';
// Memoize previously normalized pathnames.
const pathnames = new Map<string, string>();

export default function normalizeLocation<T extends Location>(location: T): T {
if (pathnames.has(location.pathname)) {
return {
...location,
pathname: pathnames.get(location.pathname),
};
}

function computeNormalizedPathname(pathname: string): string {
// If the location was registered with an `.html` extension, we don't strip it
// away, or it will render to a 404 page.
const matchedRoutes = matchRoutes(routes, location.pathname);
const matchedRoutes = matchRoutes(routes, pathname);
if (matchedRoutes.some(({route}) => route.exact === true)) {
pathnames.set(location.pathname, location.pathname);
return location;
return pathname;
}
return pathname.trim().replace(/(?:\/index)?\.html$/, '') || '/';
}

const pathname =
location.pathname.trim().replace(/(?:\/index)?\.html$/, '') || '/';

pathnames.set(location.pathname, pathname);
function normalizePathname(pathname: string): string {
let normalizedPathname = pathnames.get(pathname);
if (normalizedPathname === undefined) {
normalizedPathname = computeNormalizedPathname(pathname);
pathnames.set(pathname, normalizedPathname);
}
return normalizedPathname;
}

export default function normalizeLocation<T extends Location>(location: T): T {
return {
...location,
pathname,
pathname: normalizePathname(location.pathname),
};
}

// The router decodes the browser pathname before App normalizes it. Encode it
// back for preload(), which decodes again and throws on /100%25 if not encoded
export function normalizeBrowserPathname(browserPathname: string): string {
const routerPathname = decodeURI(browserPathname);
return encodeURI(normalizePathname(routerPathname));
}
Loading