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(docs, blog): Markdown link resolution does not support hot reload #10185

Merged
merged 5 commits into from
May 31, 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
8 changes: 0 additions & 8 deletions packages/docusaurus-plugin-content-blog/src/blogUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,6 @@ export function truncate(fileString: string, truncateMarker: RegExp): string {
return fileString.split(truncateMarker, 1).shift()!;
}

export function getSourceToPermalink(blogPosts: BlogPost[]): {
[aliasedPath: string]: string;
} {
return Object.fromEntries(
blogPosts.map(({metadata: {source, permalink}}) => [source, permalink]),
);
}

export function paginateBlogPosts({
blogPosts,
basePageUrl,
Expand Down
38 changes: 34 additions & 4 deletions packages/docusaurus-plugin-content-blog/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import {
getDataFilePath,
DEFAULT_PLUGIN_ID,
resolveMarkdownLinkPathname,
type SourceToPermalink,
} from '@docusaurus/utils';
import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation';
import {
getSourceToPermalink,
getBlogTags,
paginateBlogPosts,
shouldBeListed,
Expand Down Expand Up @@ -50,6 +50,33 @@ import type {RuleSetUseItem} from 'webpack';

const PluginName = 'docusaurus-plugin-content-blog';

// TODO this is bad, we should have a better way to do this (new lifecycle?)
// The source to permalink is currently a mutable map passed to the mdx loader
// for link resolution
// see https://github.com/facebook/docusaurus/pull/10185
function createSourceToPermalinkHelper() {
const sourceToPermalink: SourceToPermalink = new Map();

function computeSourceToPermalink(content: BlogContent): SourceToPermalink {
return new Map(
content.blogPosts.map(({metadata: {source, permalink}}) => [
source,
permalink,
]),
);
}

// Mutable map update :/
function update(content: BlogContent): void {
sourceToPermalink.clear();
computeSourceToPermalink(content).forEach((value, key) => {
sourceToPermalink.set(key, value);
});
}

return {get: () => sourceToPermalink, update};
}

export default async function pluginContentBlog(
context: LoadContext,
options: PluginOptions,
Expand Down Expand Up @@ -96,6 +123,8 @@ export default async function pluginContentBlog(
contentPaths,
});

const sourceToPermalinkHelper = createSourceToPermalinkHelper();

return {
name: PluginName,

Expand Down Expand Up @@ -201,6 +230,8 @@ export default async function pluginContentBlog(
},

async contentLoaded({content, actions}) {
sourceToPermalinkHelper.update(content);

await createAllRoutes({
baseUrl,
content,
Expand All @@ -214,7 +245,7 @@ export default async function pluginContentBlog(
return translateContent(content, translationFiles);
},

configureWebpack(_config, isServer, utils, content) {
configureWebpack() {
const {
admonitions,
rehypePlugins,
Expand All @@ -224,7 +255,6 @@ export default async function pluginContentBlog(
beforeDefaultRehypePlugins,
} = options;

const sourceToPermalink = getSourceToPermalink(content.blogPosts);
const contentDirs = getContentPathList(contentPaths);

function createMDXLoader(): RuleSetUseItem {
Expand Down Expand Up @@ -271,7 +301,7 @@ export default async function pluginContentBlog(
resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
const permalink = resolveMarkdownLinkPathname(linkPathname, {
sourceFilePath,
sourceToPermalink,
sourceToPermalink: sourceToPermalinkHelper.get(),
siteDir,
contentPaths,
});
Expand Down
44 changes: 31 additions & 13 deletions packages/docusaurus-plugin-content-docs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
createSlugger,
resolveMarkdownLinkPathname,
DEFAULT_PLUGIN_ID,
type SourceToPermalink,
type TagsFile,
} from '@docusaurus/utils';
import {
getTagsFile,
Expand Down Expand Up @@ -47,7 +49,6 @@ import {
} from './translations';
import {createAllRoutes} from './routes';
import {createSidebarsUtils} from './sidebars/utils';
import type {TagsFile} from '@docusaurus/utils';
import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader';

import type {
Expand All @@ -59,9 +60,32 @@ import type {
LoadedVersion,
} from '@docusaurus/plugin-content-docs';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {SourceToPermalink, DocFile, FullVersion} from './types';
import type {DocFile, FullVersion} from './types';
import type {RuleSetUseItem} from 'webpack';

// TODO this is bad, we should have a better way to do this (new lifecycle?)
// The source to permalink is currently a mutable map passed to the mdx loader
// for link resolution
// see https://github.com/facebook/docusaurus/pull/10185
function createSourceToPermalinkHelper() {
const sourceToPermalink: SourceToPermalink = new Map();

function computeSourceToPermalink(content: LoadedContent): SourceToPermalink {
const allDocs = content.loadedVersions.flatMap((v) => v.docs);
return new Map(allDocs.map(({source, permalink}) => [source, permalink]));
}

// Mutable map update :/
function update(content: LoadedContent): void {
sourceToPermalink.clear();
computeSourceToPermalink(content).forEach((value, key) => {
sourceToPermalink.set(key, value);
});
}

return {get: () => sourceToPermalink, update};
}

export default async function pluginContentDocs(
context: LoadContext,
options: PluginOptions,
Expand All @@ -88,6 +112,8 @@ export default async function pluginContentDocs(
// TODO env should be injected into all plugins
const env = process.env.NODE_ENV as DocEnv;

const sourceToPermalinkHelper = createSourceToPermalinkHelper();

return {
name: 'docusaurus-plugin-content-docs',

Expand Down Expand Up @@ -244,6 +270,8 @@ export default async function pluginContentDocs(
},

async contentLoaded({content, actions}) {
sourceToPermalinkHelper.update(content);

const versions: FullVersion[] = content.loadedVersions.map(toFullVersion);

await createAllRoutes({
Expand Down Expand Up @@ -274,16 +302,6 @@ export default async function pluginContentDocs(
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
.map(addTrailingPathSeparator);

// TODO this does not re-run when content gets updated in dev!
// it's probably better to restore a mutable cache in the plugin
function getSourceToPermalink(): SourceToPermalink {
const allDocs = content.loadedVersions.flatMap((v) => v.docs);
return Object.fromEntries(
allDocs.map(({source, permalink}) => [source, permalink]),
);
}
const sourceToPermalink = getSourceToPermalink();

function createMDXLoader(): RuleSetUseItem {
const loaderOptions: MDXLoaderOptions = {
admonitions: options.admonitions,
Expand Down Expand Up @@ -318,7 +336,7 @@ export default async function pluginContentDocs(
);
const permalink = resolveMarkdownLinkPathname(linkPathname, {
sourceFilePath,
sourceToPermalink,
sourceToPermalink: sourceToPermalinkHelper.get(),
siteDir,
contentPaths: version,
});
Expand Down
4 changes: 0 additions & 4 deletions packages/docusaurus-plugin-content-docs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ export type DocFile = {
content: string;
};

export type SourceToPermalink = {
[source: string]: string;
};

export type VersionTag = TagMetadata & {
/** All doc ids having this tag. */
docIds: string[];
Expand Down
26 changes: 15 additions & 11 deletions packages/docusaurus-utils/src/__tests__/markdownLinks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ describe('resolveMarkdownLinkPathname', () => {
contentPath: 'docs',
contentPathLocalized: 'i18n/docs-localized',
},
sourceToPermalink: {
'@site/docs/intro.md': '/docs/intro',
'@site/docs/foo.md': '/doc/foo',
'@site/docs/bar/baz.md': '/doc/baz',
'@site/docs/http.foo.md': '/doc/http',
},
sourceToPermalink: new Map(
Object.entries({
'@site/docs/intro.md': '/docs/intro',
'@site/docs/foo.md': '/doc/foo',
'@site/docs/bar/baz.md': '/doc/baz',
'@site/docs/http.foo.md': '/doc/http',
}),
),
};

function test(linkPathname: string, expectedOutput: string) {
Expand All @@ -50,11 +52,13 @@ describe('resolveMarkdownLinkPathname', () => {
contentPathLocalized: 'i18n/docs-localized',
},

sourceToPermalink: {
'@site/docs/intro/intro.md': '/docs/intro',
'@site/docs/intro/another.md': '/docs/another',
'@site/docs/api/classes/divine_uri.URI.md': '/docs/api/classes/uri',
},
sourceToPermalink: new Map(
Object.entries({
'@site/docs/intro/intro.md': '/docs/intro',
'@site/docs/intro/another.md': '/docs/another',
'@site/docs/api/classes/divine_uri.URI.md': '/docs/api/classes/uri',
}),
),
};

function test(linkPathname: string, expectedOutput: string) {
Expand Down
6 changes: 5 additions & 1 deletion packages/docusaurus-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ export {
writeMarkdownHeadingId,
type WriteHeadingIDOptions,
} from './markdownUtils';
export {type ContentPaths, resolveMarkdownLinkPathname} from './markdownLinks';
export {
type ContentPaths,
type SourceToPermalink,
resolveMarkdownLinkPathname,
} from './markdownLinks';
export {type SluggerOptions, type Slugger, createSlugger} from './slugger';
export {
isNameTooLong,
Expand Down
11 changes: 8 additions & 3 deletions packages/docusaurus-utils/src/markdownLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,19 @@ export type BrokenMarkdownLink<T extends ContentPaths> = {
link: string;
};

export type SourceToPermalink = Map<
string, // Aliased source path: "@site/docs/content.mdx"
string // Permalink: "/docs/content"
>;

// Note this is historical logic extracted during a 2024 refactor
// The algo has been kept exactly as before for retro compatibility
// See also https://github.com/facebook/docusaurus/pull/10168
export function resolveMarkdownLinkPathname(
linkPathname: string,
context: {
sourceFilePath: string;
sourceToPermalink: {[aliasedFilePath: string]: string};
sourceToPermalink: SourceToPermalink;
contentPaths: ContentPaths;
siteDir: string;
},
Expand All @@ -66,9 +71,9 @@ export function resolveMarkdownLinkPathname(
const aliasedSourceMatch = sourceDirsToTry
.map((sourceDir) => path.join(sourceDir, decodeURIComponent(linkPathname)))
.map((source) => aliasedSitePath(source, siteDir))
.find((source) => sourceToPermalink[source]);
.find((source) => sourceToPermalink.has(source));

return aliasedSourceMatch
? sourceToPermalink[aliasedSourceMatch] ?? null
? sourceToPermalink.get(aliasedSourceMatch) ?? null
: null;
}