Skip to content

Commit e38e42a

Browse files
authored
perf: Add caching to expensive file computations (#13106)
1 parent 173f730 commit e38e42a

File tree

4 files changed

+49
-29
lines changed

4 files changed

+49
-29
lines changed

app/[[...path]]/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {isDeveloperDocs} from 'sentry-docs/isDeveloperDocs';
2222
import {
2323
getDevDocsFrontMatter,
2424
getDocsFrontMatter,
25-
getFileBySlug,
25+
getFileBySlugWithCache,
2626
getVersionsFromDoc,
2727
} from 'sentry-docs/mdx';
2828
import {mdxComponents} from 'sentry-docs/mdxComponents';
@@ -106,9 +106,9 @@ export default async function Page(props: {params: Promise<{path?: string[]}>})
106106

107107
if (isDeveloperDocs) {
108108
// get the MDX for the current doc and render it
109-
let doc: Awaited<ReturnType<typeof getFileBySlug>> | null = null;
109+
let doc: Awaited<ReturnType<typeof getFileBySlugWithCache>>;
110110
try {
111-
doc = await getFileBySlug(`develop-docs/${params.path?.join('/') ?? ''}`);
111+
doc = await getFileBySlugWithCache(`develop-docs/${params.path?.join('/') ?? ''}`);
112112
} catch (e) {
113113
if (e.code === 'ENOENT') {
114114
// eslint-disable-next-line no-console
@@ -144,9 +144,9 @@ export default async function Page(props: {params: Promise<{path?: string[]}>})
144144
}
145145

146146
// get the MDX for the current doc and render it
147-
let doc: Awaited<ReturnType<typeof getFileBySlug>> | null = null;
147+
let doc: Awaited<ReturnType<typeof getFileBySlugWithCache>>;
148148
try {
149-
doc = await getFileBySlug(`docs/${pageNode.path}`);
149+
doc = await getFileBySlugWithCache(`docs/${pageNode.path}`);
150150
} catch (e) {
151151
if (e.code === 'ENOENT') {
152152
// eslint-disable-next-line no-console

src/components/include.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {useMemo} from 'react';
22
import {getMDXComponent} from 'mdx-bundler/client';
33

4-
import {getFileBySlug} from 'sentry-docs/mdx';
4+
import {getFileBySlugWithCache} from 'sentry-docs/mdx';
55
import {mdxComponents} from 'sentry-docs/mdxComponents';
66

77
import {PlatformContent} from './platformContent';
@@ -10,23 +10,24 @@ type Props = {
1010
name: string;
1111
};
1212

13+
function MDXLayoutRenderer({mdxSource: source, ...rest}) {
14+
const MDXLayout = useMemo(() => getMDXComponent(source), [source]);
15+
return <MDXLayout components={mdxComponents({Include, PlatformContent})} {...rest} />;
16+
}
17+
1318
export async function Include({name}: Props) {
14-
let doc: Awaited<ReturnType<typeof getFileBySlug>> | null = null;
19+
let doc: Awaited<ReturnType<typeof getFileBySlugWithCache>>;
1520
if (name.endsWith('.mdx')) {
1621
name = name.slice(0, name.length - '.mdx'.length);
1722
}
1823
try {
19-
doc = await getFileBySlug(`includes/${name}`);
24+
doc = await getFileBySlugWithCache(`includes/${name}`);
2025
} catch (e) {
2126
if (e.code === 'ENOENT') {
2227
return null;
2328
}
2429
throw e;
2530
}
2631
const {mdxSource} = doc;
27-
function MDXLayoutRenderer({mdxSource: source, ...rest}) {
28-
const MDXLayout = useMemo(() => getMDXComponent(source), [source]);
29-
return <MDXLayout components={mdxComponents({Include, PlatformContent})} {...rest} />;
30-
}
3132
return <MDXLayoutRenderer mdxSource={mdxSource} />;
3233
}

src/components/platformContent.tsx

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import fs from 'fs';
22

3-
import {useMemo} from 'react';
3+
import {cache, useMemo} from 'react';
44
import {getMDXComponent} from 'mdx-bundler/client';
55

66
import {getCurrentGuide, getDocsRootNode, getPlatform} from 'sentry-docs/docTree';
7-
import {getFileBySlug} from 'sentry-docs/mdx';
7+
import {getFileBySlugWithCache} from 'sentry-docs/mdx';
88
import {mdxComponents} from 'sentry-docs/mdxComponents';
99
import {serverContext} from 'sentry-docs/serverContext';
1010
import {
@@ -24,7 +24,7 @@ type Props = {
2424
platform?: string;
2525
};
2626

27-
const udpatePathIfVersionedFileDoesNotExist = (path: string): string => {
27+
const updatePathIfVersionedFileDoesNotExist = (path: string): string => {
2828
if (!isVersioned(path)) {
2929
return path;
3030
}
@@ -39,6 +39,21 @@ const udpatePathIfVersionedFileDoesNotExist = (path: string): string => {
3939
return path;
4040
};
4141

42+
/**
43+
* Cache the result of updatePathIfVersionedFileDoesNotExist
44+
* to avoid calling it multiple times for the same path.
45+
*
46+
* This is important because we want to skip the `fs.existsSync` call if possible.
47+
*/
48+
const updatePathIfVersionedFileDoesNotExistWithCache = cache(
49+
updatePathIfVersionedFileDoesNotExist
50+
);
51+
52+
function MDXLayoutRenderer({mdxSource: source, ...rest}) {
53+
const MDXLayout = useMemo(() => getMDXComponent(source), [source]);
54+
return <MDXLayout components={mdxComponentsWithWrapper} {...rest} />;
55+
}
56+
4257
export async function PlatformContent({includePath, platform, noGuides}: Props) {
4358
const {path} = serverContext();
4459

@@ -54,15 +69,15 @@ export async function PlatformContent({includePath, platform, noGuides}: Props)
5469
guide = `${platform}.${path[3]}`;
5570
}
5671

57-
let doc: Awaited<ReturnType<typeof getFileBySlug>> | null = null;
72+
let doc: Awaited<ReturnType<typeof getFileBySlugWithCache>> | undefined;
5873

5974
if (guide) {
60-
const guidePath = udpatePathIfVersionedFileDoesNotExist(
75+
const guidePath = updatePathIfVersionedFileDoesNotExistWithCache(
6176
`platform-includes/${includePath}/${guide}`
6277
);
6378

6479
try {
65-
doc = await getFileBySlug(guidePath);
80+
doc = await getFileBySlugWithCache(guidePath);
6681
} catch (e) {
6782
// It's fine - keep looking.
6883
}
@@ -72,13 +87,13 @@ export async function PlatformContent({includePath, platform, noGuides}: Props)
7287
const rootNode = await getDocsRootNode();
7388
const guideObject = getCurrentGuide(rootNode, path);
7489

75-
const fallbackGuidePath = udpatePathIfVersionedFileDoesNotExist(
90+
const fallbackGuidePath = updatePathIfVersionedFileDoesNotExistWithCache(
7691
`platform-includes/${includePath}/${guideObject?.fallbackGuide}${VERSION_INDICATOR}${getVersion(guide || '')}`
7792
);
7893

7994
if (guideObject?.fallbackGuide) {
8095
try {
81-
doc = await getFileBySlug(fallbackGuidePath);
96+
doc = await getFileBySlugWithCache(fallbackGuidePath);
8297
} catch (e) {
8398
// It's fine - keep looking.
8499
}
@@ -87,11 +102,11 @@ export async function PlatformContent({includePath, platform, noGuides}: Props)
87102

88103
if (!doc) {
89104
try {
90-
const platformPath = udpatePathIfVersionedFileDoesNotExist(
105+
const platformPath = updatePathIfVersionedFileDoesNotExistWithCache(
91106
`platform-includes/${includePath}/${platform}`
92107
);
93108

94-
doc = await getFileBySlug(platformPath);
109+
doc = await getFileBySlugWithCache(platformPath);
95110
} catch (e) {
96111
// It's fine - keep looking.
97112
}
@@ -101,13 +116,13 @@ export async function PlatformContent({includePath, platform, noGuides}: Props)
101116
const rootNode = await getDocsRootNode();
102117
const platformObject = getPlatform(rootNode, platform);
103118

104-
const fallbackPlatformPath = udpatePathIfVersionedFileDoesNotExist(
119+
const fallbackPlatformPath = updatePathIfVersionedFileDoesNotExistWithCache(
105120
`platform-includes/${includePath}/${platformObject?.fallbackPlatform}`
106121
);
107122

108123
if (platformObject?.fallbackPlatform) {
109124
try {
110-
doc = await getFileBySlug(fallbackPlatformPath);
125+
doc = await getFileBySlugWithCache(fallbackPlatformPath);
111126
} catch (e) {
112127
// It's fine - keep looking.
113128
}
@@ -116,18 +131,14 @@ export async function PlatformContent({includePath, platform, noGuides}: Props)
116131

117132
if (!doc) {
118133
try {
119-
doc = await getFileBySlug(`platform-includes/${includePath}/_default`);
134+
doc = await getFileBySlugWithCache(`platform-includes/${includePath}/_default`);
120135
} catch (e) {
121136
// Couldn't find anything.
122137
return null;
123138
}
124139
}
125140

126141
const {mdxSource} = doc;
127-
function MDXLayoutRenderer({mdxSource: source, ...rest}) {
128-
const MDXLayout = useMemo(() => getMDXComponent(source), [source]);
129-
return <MDXLayout components={mdxComponentsWithWrapper} {...rest} />;
130-
}
131142
return <MDXLayoutRenderer mdxSource={mdxSource} />;
132143
}
133144

src/mdx.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from 'fs';
22
import path from 'path';
33

4+
import {cache} from 'react';
45
import matter from 'gray-matter';
56
import {s} from 'hastscript';
67
import yaml from 'js-yaml';
@@ -474,3 +475,10 @@ export async function getFileBySlug(slug: string) {
474475
},
475476
};
476477
}
478+
479+
/**
480+
* Cache the result of {@link getFileBySlug}.
481+
*
482+
* This is useful for performance when rendering the same file multiple times.
483+
*/
484+
export const getFileBySlugWithCache = cache(getFileBySlug);

0 commit comments

Comments
 (0)