mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-29 10:17:55 +02:00
fix(docs, blog): Markdown link resolution does not support hot reload (#10185)
This commit is contained in:
parent
0eb7b64aac
commit
a7afd9cc87
7 changed files with 93 additions and 44 deletions
|
@ -45,14 +45,6 @@ export function truncate(fileString: string, truncateMarker: RegExp): string {
|
||||||
return fileString.split(truncateMarker, 1).shift()!;
|
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({
|
export function paginateBlogPosts({
|
||||||
blogPosts,
|
blogPosts,
|
||||||
basePageUrl,
|
basePageUrl,
|
||||||
|
|
|
@ -19,10 +19,10 @@ import {
|
||||||
getDataFilePath,
|
getDataFilePath,
|
||||||
DEFAULT_PLUGIN_ID,
|
DEFAULT_PLUGIN_ID,
|
||||||
resolveMarkdownLinkPathname,
|
resolveMarkdownLinkPathname,
|
||||||
|
type SourceToPermalink,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation';
|
import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation';
|
||||||
import {
|
import {
|
||||||
getSourceToPermalink,
|
|
||||||
getBlogTags,
|
getBlogTags,
|
||||||
paginateBlogPosts,
|
paginateBlogPosts,
|
||||||
shouldBeListed,
|
shouldBeListed,
|
||||||
|
@ -50,6 +50,33 @@ import type {RuleSetUseItem} from 'webpack';
|
||||||
|
|
||||||
const PluginName = 'docusaurus-plugin-content-blog';
|
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(
|
export default async function pluginContentBlog(
|
||||||
context: LoadContext,
|
context: LoadContext,
|
||||||
options: PluginOptions,
|
options: PluginOptions,
|
||||||
|
@ -96,6 +123,8 @@ export default async function pluginContentBlog(
|
||||||
contentPaths,
|
contentPaths,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sourceToPermalinkHelper = createSourceToPermalinkHelper();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: PluginName,
|
name: PluginName,
|
||||||
|
|
||||||
|
@ -201,6 +230,8 @@ export default async function pluginContentBlog(
|
||||||
},
|
},
|
||||||
|
|
||||||
async contentLoaded({content, actions}) {
|
async contentLoaded({content, actions}) {
|
||||||
|
sourceToPermalinkHelper.update(content);
|
||||||
|
|
||||||
await createAllRoutes({
|
await createAllRoutes({
|
||||||
baseUrl,
|
baseUrl,
|
||||||
content,
|
content,
|
||||||
|
@ -214,7 +245,7 @@ export default async function pluginContentBlog(
|
||||||
return translateContent(content, translationFiles);
|
return translateContent(content, translationFiles);
|
||||||
},
|
},
|
||||||
|
|
||||||
configureWebpack(_config, isServer, utils, content) {
|
configureWebpack() {
|
||||||
const {
|
const {
|
||||||
admonitions,
|
admonitions,
|
||||||
rehypePlugins,
|
rehypePlugins,
|
||||||
|
@ -224,7 +255,6 @@ export default async function pluginContentBlog(
|
||||||
beforeDefaultRehypePlugins,
|
beforeDefaultRehypePlugins,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const sourceToPermalink = getSourceToPermalink(content.blogPosts);
|
|
||||||
const contentDirs = getContentPathList(contentPaths);
|
const contentDirs = getContentPathList(contentPaths);
|
||||||
|
|
||||||
function createMDXLoader(): RuleSetUseItem {
|
function createMDXLoader(): RuleSetUseItem {
|
||||||
|
@ -271,7 +301,7 @@ export default async function pluginContentBlog(
|
||||||
resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
|
resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
|
||||||
const permalink = resolveMarkdownLinkPathname(linkPathname, {
|
const permalink = resolveMarkdownLinkPathname(linkPathname, {
|
||||||
sourceFilePath,
|
sourceFilePath,
|
||||||
sourceToPermalink,
|
sourceToPermalink: sourceToPermalinkHelper.get(),
|
||||||
siteDir,
|
siteDir,
|
||||||
contentPaths,
|
contentPaths,
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,6 +19,8 @@ import {
|
||||||
createSlugger,
|
createSlugger,
|
||||||
resolveMarkdownLinkPathname,
|
resolveMarkdownLinkPathname,
|
||||||
DEFAULT_PLUGIN_ID,
|
DEFAULT_PLUGIN_ID,
|
||||||
|
type SourceToPermalink,
|
||||||
|
type TagsFile,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {
|
import {
|
||||||
getTagsFile,
|
getTagsFile,
|
||||||
|
@ -47,7 +49,6 @@ import {
|
||||||
} from './translations';
|
} from './translations';
|
||||||
import {createAllRoutes} from './routes';
|
import {createAllRoutes} from './routes';
|
||||||
import {createSidebarsUtils} from './sidebars/utils';
|
import {createSidebarsUtils} from './sidebars/utils';
|
||||||
import type {TagsFile} from '@docusaurus/utils';
|
|
||||||
import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader';
|
import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
@ -59,9 +60,32 @@ import type {
|
||||||
LoadedVersion,
|
LoadedVersion,
|
||||||
} from '@docusaurus/plugin-content-docs';
|
} from '@docusaurus/plugin-content-docs';
|
||||||
import type {LoadContext, Plugin} from '@docusaurus/types';
|
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';
|
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(
|
export default async function pluginContentDocs(
|
||||||
context: LoadContext,
|
context: LoadContext,
|
||||||
options: PluginOptions,
|
options: PluginOptions,
|
||||||
|
@ -88,6 +112,8 @@ export default async function pluginContentDocs(
|
||||||
// TODO env should be injected into all plugins
|
// TODO env should be injected into all plugins
|
||||||
const env = process.env.NODE_ENV as DocEnv;
|
const env = process.env.NODE_ENV as DocEnv;
|
||||||
|
|
||||||
|
const sourceToPermalinkHelper = createSourceToPermalinkHelper();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'docusaurus-plugin-content-docs',
|
name: 'docusaurus-plugin-content-docs',
|
||||||
|
|
||||||
|
@ -244,6 +270,8 @@ export default async function pluginContentDocs(
|
||||||
},
|
},
|
||||||
|
|
||||||
async contentLoaded({content, actions}) {
|
async contentLoaded({content, actions}) {
|
||||||
|
sourceToPermalinkHelper.update(content);
|
||||||
|
|
||||||
const versions: FullVersion[] = content.loadedVersions.map(toFullVersion);
|
const versions: FullVersion[] = content.loadedVersions.map(toFullVersion);
|
||||||
|
|
||||||
await createAllRoutes({
|
await createAllRoutes({
|
||||||
|
@ -274,16 +302,6 @@ export default async function pluginContentDocs(
|
||||||
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
||||||
.map(addTrailingPathSeparator);
|
.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 {
|
function createMDXLoader(): RuleSetUseItem {
|
||||||
const loaderOptions: MDXLoaderOptions = {
|
const loaderOptions: MDXLoaderOptions = {
|
||||||
admonitions: options.admonitions,
|
admonitions: options.admonitions,
|
||||||
|
@ -318,7 +336,7 @@ export default async function pluginContentDocs(
|
||||||
);
|
);
|
||||||
const permalink = resolveMarkdownLinkPathname(linkPathname, {
|
const permalink = resolveMarkdownLinkPathname(linkPathname, {
|
||||||
sourceFilePath,
|
sourceFilePath,
|
||||||
sourceToPermalink,
|
sourceToPermalink: sourceToPermalinkHelper.get(),
|
||||||
siteDir,
|
siteDir,
|
||||||
contentPaths: version,
|
contentPaths: version,
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,10 +19,6 @@ export type DocFile = {
|
||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SourceToPermalink = {
|
|
||||||
[source: string]: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type VersionTag = TagMetadata & {
|
export type VersionTag = TagMetadata & {
|
||||||
/** All doc ids having this tag. */
|
/** All doc ids having this tag. */
|
||||||
docIds: string[];
|
docIds: string[];
|
||||||
|
|
|
@ -18,12 +18,14 @@ describe('resolveMarkdownLinkPathname', () => {
|
||||||
contentPath: 'docs',
|
contentPath: 'docs',
|
||||||
contentPathLocalized: 'i18n/docs-localized',
|
contentPathLocalized: 'i18n/docs-localized',
|
||||||
},
|
},
|
||||||
sourceToPermalink: {
|
sourceToPermalink: new Map(
|
||||||
'@site/docs/intro.md': '/docs/intro',
|
Object.entries({
|
||||||
'@site/docs/foo.md': '/doc/foo',
|
'@site/docs/intro.md': '/docs/intro',
|
||||||
'@site/docs/bar/baz.md': '/doc/baz',
|
'@site/docs/foo.md': '/doc/foo',
|
||||||
'@site/docs/http.foo.md': '/doc/http',
|
'@site/docs/bar/baz.md': '/doc/baz',
|
||||||
},
|
'@site/docs/http.foo.md': '/doc/http',
|
||||||
|
}),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
function test(linkPathname: string, expectedOutput: string) {
|
function test(linkPathname: string, expectedOutput: string) {
|
||||||
|
@ -50,11 +52,13 @@ describe('resolveMarkdownLinkPathname', () => {
|
||||||
contentPathLocalized: 'i18n/docs-localized',
|
contentPathLocalized: 'i18n/docs-localized',
|
||||||
},
|
},
|
||||||
|
|
||||||
sourceToPermalink: {
|
sourceToPermalink: new Map(
|
||||||
'@site/docs/intro/intro.md': '/docs/intro',
|
Object.entries({
|
||||||
'@site/docs/intro/another.md': '/docs/another',
|
'@site/docs/intro/intro.md': '/docs/intro',
|
||||||
'@site/docs/api/classes/divine_uri.URI.md': '/docs/api/classes/uri',
|
'@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) {
|
function test(linkPathname: string, expectedOutput: string) {
|
||||||
|
|
|
@ -77,7 +77,11 @@ export {
|
||||||
writeMarkdownHeadingId,
|
writeMarkdownHeadingId,
|
||||||
type WriteHeadingIDOptions,
|
type WriteHeadingIDOptions,
|
||||||
} from './markdownUtils';
|
} 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 {type SluggerOptions, type Slugger, createSlugger} from './slugger';
|
||||||
export {
|
export {
|
||||||
isNameTooLong,
|
isNameTooLong,
|
||||||
|
|
|
@ -40,6 +40,11 @@ export type BrokenMarkdownLink<T extends ContentPaths> = {
|
||||||
link: string;
|
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
|
// Note this is historical logic extracted during a 2024 refactor
|
||||||
// The algo has been kept exactly as before for retro compatibility
|
// The algo has been kept exactly as before for retro compatibility
|
||||||
// See also https://github.com/facebook/docusaurus/pull/10168
|
// See also https://github.com/facebook/docusaurus/pull/10168
|
||||||
|
@ -47,7 +52,7 @@ export function resolveMarkdownLinkPathname(
|
||||||
linkPathname: string,
|
linkPathname: string,
|
||||||
context: {
|
context: {
|
||||||
sourceFilePath: string;
|
sourceFilePath: string;
|
||||||
sourceToPermalink: {[aliasedFilePath: string]: string};
|
sourceToPermalink: SourceToPermalink;
|
||||||
contentPaths: ContentPaths;
|
contentPaths: ContentPaths;
|
||||||
siteDir: string;
|
siteDir: string;
|
||||||
},
|
},
|
||||||
|
@ -66,9 +71,9 @@ export function resolveMarkdownLinkPathname(
|
||||||
const aliasedSourceMatch = sourceDirsToTry
|
const aliasedSourceMatch = sourceDirsToTry
|
||||||
.map((sourceDir) => path.join(sourceDir, decodeURIComponent(linkPathname)))
|
.map((sourceDir) => path.join(sourceDir, decodeURIComponent(linkPathname)))
|
||||||
.map((source) => aliasedSitePath(source, siteDir))
|
.map((source) => aliasedSitePath(source, siteDir))
|
||||||
.find((source) => sourceToPermalink[source]);
|
.find((source) => sourceToPermalink.has(source));
|
||||||
|
|
||||||
return aliasedSourceMatch
|
return aliasedSourceMatch
|
||||||
? sourceToPermalink[aliasedSourceMatch] ?? null
|
? sourceToPermalink.get(aliasedSourceMatch) ?? null
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue