Skip to content

Commit 264774a

Browse files
authored
perf: avoid duplicated git log calls in loadContent() and postBuild() for untracked Git files (#11211)
Co-authored-by: slorber <749374+slorber@users.noreply.github.com>
1 parent 68aa3c8 commit 264774a

File tree

10 files changed

+160
-21
lines changed

10 files changed

+160
-21
lines changed

packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,9 +521,9 @@ declare module '@docusaurus/plugin-content-blog' {
521521
readingTime: ReadingTimeFunctionOption;
522522
/** Governs the direction of blog post sorting. */
523523
sortPosts: 'ascending' | 'descending';
524-
/** Whether to display the last date the doc was updated. */
524+
/** Whether to display the last date the blog post was updated. */
525525
showLastUpdateTime: boolean;
526-
/** Whether to display the author who last updated the doc. */
526+
/** Whether to display the author who last updated the blog post. */
527527
showLastUpdateAuthor: boolean;
528528
/** An optional function which can be used to transform blog posts
529529
* (filter, modify, delete, etc...).

packages/docusaurus-plugin-sitemap/src/__tests__/createSitemapItem.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,5 +225,66 @@ describe('createSitemapItem', () => {
225225
`);
226226
});
227227
});
228+
229+
describe('read from both - route metadata lastUpdatedAt null', () => {
230+
const route = {
231+
path: '/routePath',
232+
metadata: {
233+
sourceFilePath: 'route/file.md',
234+
lastUpdatedAt: null,
235+
},
236+
};
237+
238+
it('lastmod default option', async () => {
239+
await expect(
240+
test({
241+
route,
242+
}),
243+
).resolves.toMatchInlineSnapshot(`
244+
{
245+
"changefreq": "weekly",
246+
"lastmod": null,
247+
"priority": 0.5,
248+
"url": "https://example.com/routePath",
249+
}
250+
`);
251+
});
252+
253+
it('lastmod date option', async () => {
254+
await expect(
255+
test({
256+
route,
257+
options: {
258+
lastmod: 'date',
259+
},
260+
}),
261+
).resolves.toMatchInlineSnapshot(`
262+
{
263+
"changefreq": "weekly",
264+
"lastmod": null,
265+
"priority": 0.5,
266+
"url": "https://example.com/routePath",
267+
}
268+
`);
269+
});
270+
271+
it('lastmod datetime option', async () => {
272+
await expect(
273+
test({
274+
route,
275+
options: {
276+
lastmod: 'datetime',
277+
},
278+
}),
279+
).resolves.toMatchInlineSnapshot(`
280+
{
281+
"changefreq": "weekly",
282+
"lastmod": null,
283+
"priority": 0.5,
284+
"url": "https://example.com/routePath",
285+
}
286+
`);
287+
});
288+
});
228289
});
229290
});

packages/docusaurus-plugin-sitemap/src/createSitemapItem.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@ import type {PluginOptions} from './options';
1313

1414
async function getRouteLastUpdatedAt(
1515
route: RouteConfig,
16-
): Promise<number | undefined> {
16+
): Promise<number | null | undefined> {
17+
// Important to bail-out early here
18+
// This can lead to duplicated getLastUpdate() calls and performance problems
19+
// See https://github.com/facebook/docusaurus/pull/11211
20+
if (route.metadata?.lastUpdatedAt === null) {
21+
return null;
22+
}
1723
if (route.metadata?.lastUpdatedAt) {
1824
return route.metadata?.lastUpdatedAt;
1925
}
2026
if (route.metadata?.sourceFilePath) {
2127
const lastUpdate = await getLastUpdate(route.metadata?.sourceFilePath);
22-
return lastUpdate?.lastUpdatedAt;
28+
return lastUpdate?.lastUpdatedAt ?? null;
2329
}
2430

2531
return undefined;

packages/docusaurus-theme-classic/src/theme-classic.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -852,8 +852,8 @@ declare module '@theme/EditMetaRow' {
852852
export interface Props {
853853
readonly className: string;
854854
readonly editUrl: string | null | undefined;
855-
readonly lastUpdatedAt: number | undefined;
856-
readonly lastUpdatedBy: string | undefined;
855+
readonly lastUpdatedAt: number | null | undefined;
856+
readonly lastUpdatedBy: string | null | undefined;
857857
}
858858
export default function EditMetaRow(props: Props): ReactNode;
859859
}
@@ -1024,8 +1024,8 @@ declare module '@theme/LastUpdated' {
10241024
import type {ReactNode} from 'react';
10251025

10261026
export interface Props {
1027-
readonly lastUpdatedAt?: number;
1028-
readonly lastUpdatedBy?: string;
1027+
readonly lastUpdatedAt?: number | null;
1028+
readonly lastUpdatedBy?: string | null;
10291029
}
10301030

10311031
export default function LastUpdated(props: Props): ReactNode;

packages/docusaurus-types/src/routing.d.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,19 @@ export type RouteMetadata = {
5656
/**
5757
* The last updated date of this route
5858
* This is generally read from the Git history of the sourceFilePath
59-
* but can also be provided through other means (usually front matter)
59+
* but can also be provided through other means (usually front matter).
6060
*
6161
* This has notably been introduced for adding "lastmod" support to the
6262
* sitemap plugin, see https://github.com/facebook/docusaurus/pull/9954
63+
*
64+
* `undefined` means we haven't tried to compute the value for this route.
65+
* This is usually the case for routes created by third-party plugins that do
66+
* not need this metadata.
67+
*
68+
* `null` means we already tried to compute a lastUpdatedAt, but we know for
69+
* sure there isn't any. This usually happens for untracked Git files.
6370
*/
64-
lastUpdatedAt?: number;
71+
lastUpdatedAt?: number | null;
6572
};
6673

6774
/**

packages/docusaurus-utils/src/__tests__/lastUpdateUtils.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import execa from 'execa';
1414
import {
1515
getGitLastUpdate,
1616
LAST_UPDATE_FALLBACK,
17+
LAST_UPDATE_UNTRACKED_GIT_FILEPATH,
1718
readLastUpdateData,
1819
} from '../lastUpdateUtils';
20+
import type {FrontMatterLastUpdate} from '../lastUpdateUtils';
1921

2022
describe('getGitLastUpdate', () => {
2123
const {repoDir} = createTempRepo();
@@ -109,6 +111,34 @@ describe('readLastUpdateData', () => {
109111
const testTimestamp = new Date(testDate).getTime();
110112
const testAuthor = 'ozaki';
111113

114+
describe('on untracked Git file', () => {
115+
function test(lastUpdateFrontMatter: FrontMatterLastUpdate | undefined) {
116+
return readLastUpdateData(
117+
LAST_UPDATE_UNTRACKED_GIT_FILEPATH,
118+
{showLastUpdateAuthor: true, showLastUpdateTime: true},
119+
lastUpdateFrontMatter,
120+
);
121+
}
122+
123+
it('reads null at/by from Git', async () => {
124+
const {lastUpdatedAt, lastUpdatedBy} = await test({});
125+
expect(lastUpdatedAt).toBeNull();
126+
expect(lastUpdatedBy).toBeNull();
127+
});
128+
129+
it('reads null at from Git and author from front matter', async () => {
130+
const {lastUpdatedAt, lastUpdatedBy} = await test({author: testAuthor});
131+
expect(lastUpdatedAt).toBeNull();
132+
expect(lastUpdatedBy).toEqual(testAuthor);
133+
});
134+
135+
it('reads null by from Git and date from front matter', async () => {
136+
const {lastUpdatedAt, lastUpdatedBy} = await test({date: testDate});
137+
expect(lastUpdatedBy).toBeNull();
138+
expect(lastUpdatedAt).toEqual(testTimestamp);
139+
});
140+
});
141+
112142
it('read last time show author time', async () => {
113143
const {lastUpdatedAt, lastUpdatedBy} = await readLastUpdateData(
114144
'',

packages/docusaurus-utils/src/gitUtils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,12 @@ export async function getFileCommitDate(
154154
file,
155155
)}"`;
156156

157-
const result = (await GitCommandQueue.add(() =>
158-
execa(command, {
157+
const result = (await GitCommandQueue.add(() => {
158+
return execa(command, {
159159
cwd: path.dirname(file),
160160
shell: true,
161-
}),
162-
))!;
161+
});
162+
}))!;
163163

164164
if (result.exitCode !== 0) {
165165
throw new Error(

packages/docusaurus-utils/src/lastUpdateUtils.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ import {
1515
import type {PluginOptions} from '@docusaurus/types';
1616

1717
export type LastUpdateData = {
18-
/** A timestamp in **milliseconds**, usually read from `git log` */
19-
lastUpdatedAt?: number;
20-
/** The author's name, usually coming from `git log` */
21-
lastUpdatedBy?: string;
18+
/**
19+
* A timestamp in **milliseconds**, usually read from `git log`
20+
* `undefined`: not read
21+
* `null`: no value to read (usual for untracked files)
22+
*/
23+
lastUpdatedAt: number | undefined | null;
24+
/**
25+
* The author's name, usually coming from `git log`
26+
* `undefined`: not read
27+
* `null`: no value to read (usual for untracked files)
28+
*/
29+
lastUpdatedBy: string | undefined | null;
2230
};
2331

2432
let showedGitRequirementError = false;
@@ -68,9 +76,15 @@ export const LAST_UPDATE_FALLBACK: LastUpdateData = {
6876
lastUpdatedBy: 'Author',
6977
};
7078

79+
// Not proud of this, but convenient for tests :/
80+
export const LAST_UPDATE_UNTRACKED_GIT_FILEPATH = `file/path/${Math.random()}.mdx`;
81+
7182
export async function getLastUpdate(
7283
filePath: string,
7384
): Promise<LastUpdateData | null> {
85+
if (filePath === LAST_UPDATE_UNTRACKED_GIT_FILEPATH) {
86+
return null;
87+
}
7488
if (
7589
process.env.NODE_ENV !== 'production' ||
7690
process.env.DOCUSAURUS_DISABLE_LAST_UPDATE === 'true'
@@ -103,7 +117,7 @@ export async function readLastUpdateData(
103117
const {showLastUpdateAuthor, showLastUpdateTime} = options;
104118

105119
if (!showLastUpdateAuthor && !showLastUpdateTime) {
106-
return {};
120+
return {lastUpdatedBy: undefined, lastUpdatedAt: undefined};
107121
}
108122

109123
const frontMatterAuthor = lastUpdateFrontMatter?.author;
@@ -116,9 +130,21 @@ export async function readLastUpdateData(
116130
// If all the data is provided as front matter, we do not call it
117131
const getLastUpdateMemoized = _.memoize(() => getLastUpdate(filePath));
118132
const getLastUpdateBy = () =>
119-
getLastUpdateMemoized().then((update) => update?.lastUpdatedBy);
133+
getLastUpdateMemoized().then((update) => {
134+
// Important, see https://github.com/facebook/docusaurus/pull/11211
135+
if (update === null) {
136+
return null;
137+
}
138+
return update?.lastUpdatedBy;
139+
});
120140
const getLastUpdateAt = () =>
121-
getLastUpdateMemoized().then((update) => update?.lastUpdatedAt);
141+
getLastUpdateMemoized().then((update) => {
142+
// Important, see https://github.com/facebook/docusaurus/pull/11211
143+
if (update === null) {
144+
return null;
145+
}
146+
return update?.lastUpdatedAt;
147+
});
122148

123149
const lastUpdatedBy = showLastUpdateAuthor
124150
? frontMatterAuthor ?? (await getLastUpdateBy())

packages/docusaurus/src/commands/build/buildLocale.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type BuildLocaleParams = {
3535
};
3636

3737
const SkipBundling = process.env.DOCUSAURUS_SKIP_BUNDLING === 'true';
38+
const ExitAfterLoading = process.env.DOCUSAURUS_EXIT_AFTER_LOADING === 'true';
3839
const ExitAfterBundling = process.env.DOCUSAURUS_EXIT_AFTER_BUNDLING === 'true';
3940

4041
export async function buildLocale({
@@ -59,6 +60,10 @@ export async function buildLocale({
5960
}),
6061
);
6162

63+
if (ExitAfterLoading) {
64+
return process.exit(0);
65+
}
66+
6267
const {props} = site;
6368
const {outDir, plugins, siteConfig} = props;
6469

website/docusaurus.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,10 @@ export default async function createConfigAsync() {
316316
'./src/plugins/changelog/index.ts',
317317
{
318318
blogTitle: 'Docusaurus changelog',
319+
// Not useful, but permits to run git commands earlier
320+
// Otherwise the sitemap plugin will run them in postBuild()
321+
showLastUpdateAuthor: true,
322+
showLastUpdateTime: true,
319323
blogDescription:
320324
'Keep yourself up-to-date about new features in every release',
321325
blogSidebarCount: 'ALL',

0 commit comments

Comments
 (0)