mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-18 18:52:23 +02:00
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>
This commit is contained in:
parent
68aa3c876b
commit
264774a550
10 changed files with 160 additions and 21 deletions
|
@ -521,9 +521,9 @@ declare module '@docusaurus/plugin-content-blog' {
|
||||||
readingTime: ReadingTimeFunctionOption;
|
readingTime: ReadingTimeFunctionOption;
|
||||||
/** Governs the direction of blog post sorting. */
|
/** Governs the direction of blog post sorting. */
|
||||||
sortPosts: 'ascending' | 'descending';
|
sortPosts: 'ascending' | 'descending';
|
||||||
/** Whether to display the last date the doc was updated. */
|
/** Whether to display the last date the blog post was updated. */
|
||||||
showLastUpdateTime: boolean;
|
showLastUpdateTime: boolean;
|
||||||
/** Whether to display the author who last updated the doc. */
|
/** Whether to display the author who last updated the blog post. */
|
||||||
showLastUpdateAuthor: boolean;
|
showLastUpdateAuthor: boolean;
|
||||||
/** An optional function which can be used to transform blog posts
|
/** An optional function which can be used to transform blog posts
|
||||||
* (filter, modify, delete, etc...).
|
* (filter, modify, delete, etc...).
|
||||||
|
|
|
@ -225,5 +225,66 @@ describe('createSitemapItem', () => {
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('read from both - route metadata lastUpdatedAt null', () => {
|
||||||
|
const route = {
|
||||||
|
path: '/routePath',
|
||||||
|
metadata: {
|
||||||
|
sourceFilePath: 'route/file.md',
|
||||||
|
lastUpdatedAt: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('lastmod default option', async () => {
|
||||||
|
await expect(
|
||||||
|
test({
|
||||||
|
route,
|
||||||
|
}),
|
||||||
|
).resolves.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"changefreq": "weekly",
|
||||||
|
"lastmod": null,
|
||||||
|
"priority": 0.5,
|
||||||
|
"url": "https://example.com/routePath",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lastmod date option', async () => {
|
||||||
|
await expect(
|
||||||
|
test({
|
||||||
|
route,
|
||||||
|
options: {
|
||||||
|
lastmod: 'date',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"changefreq": "weekly",
|
||||||
|
"lastmod": null,
|
||||||
|
"priority": 0.5,
|
||||||
|
"url": "https://example.com/routePath",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lastmod datetime option', async () => {
|
||||||
|
await expect(
|
||||||
|
test({
|
||||||
|
route,
|
||||||
|
options: {
|
||||||
|
lastmod: 'datetime',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"changefreq": "weekly",
|
||||||
|
"lastmod": null,
|
||||||
|
"priority": 0.5,
|
||||||
|
"url": "https://example.com/routePath",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,13 +13,19 @@ import type {PluginOptions} from './options';
|
||||||
|
|
||||||
async function getRouteLastUpdatedAt(
|
async function getRouteLastUpdatedAt(
|
||||||
route: RouteConfig,
|
route: RouteConfig,
|
||||||
): Promise<number | undefined> {
|
): Promise<number | null | undefined> {
|
||||||
|
// Important to bail-out early here
|
||||||
|
// This can lead to duplicated getLastUpdate() calls and performance problems
|
||||||
|
// See https://github.com/facebook/docusaurus/pull/11211
|
||||||
|
if (route.metadata?.lastUpdatedAt === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (route.metadata?.lastUpdatedAt) {
|
if (route.metadata?.lastUpdatedAt) {
|
||||||
return route.metadata?.lastUpdatedAt;
|
return route.metadata?.lastUpdatedAt;
|
||||||
}
|
}
|
||||||
if (route.metadata?.sourceFilePath) {
|
if (route.metadata?.sourceFilePath) {
|
||||||
const lastUpdate = await getLastUpdate(route.metadata?.sourceFilePath);
|
const lastUpdate = await getLastUpdate(route.metadata?.sourceFilePath);
|
||||||
return lastUpdate?.lastUpdatedAt;
|
return lastUpdate?.lastUpdatedAt ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
|
@ -852,8 +852,8 @@ declare module '@theme/EditMetaRow' {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly className: string;
|
readonly className: string;
|
||||||
readonly editUrl: string | null | undefined;
|
readonly editUrl: string | null | undefined;
|
||||||
readonly lastUpdatedAt: number | undefined;
|
readonly lastUpdatedAt: number | null | undefined;
|
||||||
readonly lastUpdatedBy: string | undefined;
|
readonly lastUpdatedBy: string | null | undefined;
|
||||||
}
|
}
|
||||||
export default function EditMetaRow(props: Props): ReactNode;
|
export default function EditMetaRow(props: Props): ReactNode;
|
||||||
}
|
}
|
||||||
|
@ -1024,8 +1024,8 @@ declare module '@theme/LastUpdated' {
|
||||||
import type {ReactNode} from 'react';
|
import type {ReactNode} from 'react';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly lastUpdatedAt?: number;
|
readonly lastUpdatedAt?: number | null;
|
||||||
readonly lastUpdatedBy?: string;
|
readonly lastUpdatedBy?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LastUpdated(props: Props): ReactNode;
|
export default function LastUpdated(props: Props): ReactNode;
|
||||||
|
|
11
packages/docusaurus-types/src/routing.d.ts
vendored
11
packages/docusaurus-types/src/routing.d.ts
vendored
|
@ -56,12 +56,19 @@ export type RouteMetadata = {
|
||||||
/**
|
/**
|
||||||
* The last updated date of this route
|
* The last updated date of this route
|
||||||
* This is generally read from the Git history of the sourceFilePath
|
* This is generally read from the Git history of the sourceFilePath
|
||||||
* but can also be provided through other means (usually front matter)
|
* but can also be provided through other means (usually front matter).
|
||||||
*
|
*
|
||||||
* This has notably been introduced for adding "lastmod" support to the
|
* This has notably been introduced for adding "lastmod" support to the
|
||||||
* sitemap plugin, see https://github.com/facebook/docusaurus/pull/9954
|
* sitemap plugin, see https://github.com/facebook/docusaurus/pull/9954
|
||||||
|
*
|
||||||
|
* `undefined` means we haven't tried to compute the value for this route.
|
||||||
|
* This is usually the case for routes created by third-party plugins that do
|
||||||
|
* not need this metadata.
|
||||||
|
*
|
||||||
|
* `null` means we already tried to compute a lastUpdatedAt, but we know for
|
||||||
|
* sure there isn't any. This usually happens for untracked Git files.
|
||||||
*/
|
*/
|
||||||
lastUpdatedAt?: number;
|
lastUpdatedAt?: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,8 +14,10 @@ import execa from 'execa';
|
||||||
import {
|
import {
|
||||||
getGitLastUpdate,
|
getGitLastUpdate,
|
||||||
LAST_UPDATE_FALLBACK,
|
LAST_UPDATE_FALLBACK,
|
||||||
|
LAST_UPDATE_UNTRACKED_GIT_FILEPATH,
|
||||||
readLastUpdateData,
|
readLastUpdateData,
|
||||||
} from '../lastUpdateUtils';
|
} from '../lastUpdateUtils';
|
||||||
|
import type {FrontMatterLastUpdate} from '../lastUpdateUtils';
|
||||||
|
|
||||||
describe('getGitLastUpdate', () => {
|
describe('getGitLastUpdate', () => {
|
||||||
const {repoDir} = createTempRepo();
|
const {repoDir} = createTempRepo();
|
||||||
|
@ -109,6 +111,34 @@ describe('readLastUpdateData', () => {
|
||||||
const testTimestamp = new Date(testDate).getTime();
|
const testTimestamp = new Date(testDate).getTime();
|
||||||
const testAuthor = 'ozaki';
|
const testAuthor = 'ozaki';
|
||||||
|
|
||||||
|
describe('on untracked Git file', () => {
|
||||||
|
function test(lastUpdateFrontMatter: FrontMatterLastUpdate | undefined) {
|
||||||
|
return readLastUpdateData(
|
||||||
|
LAST_UPDATE_UNTRACKED_GIT_FILEPATH,
|
||||||
|
{showLastUpdateAuthor: true, showLastUpdateTime: true},
|
||||||
|
lastUpdateFrontMatter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('reads null at/by from Git', async () => {
|
||||||
|
const {lastUpdatedAt, lastUpdatedBy} = await test({});
|
||||||
|
expect(lastUpdatedAt).toBeNull();
|
||||||
|
expect(lastUpdatedBy).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reads null at from Git and author from front matter', async () => {
|
||||||
|
const {lastUpdatedAt, lastUpdatedBy} = await test({author: testAuthor});
|
||||||
|
expect(lastUpdatedAt).toBeNull();
|
||||||
|
expect(lastUpdatedBy).toEqual(testAuthor);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reads null by from Git and date from front matter', async () => {
|
||||||
|
const {lastUpdatedAt, lastUpdatedBy} = await test({date: testDate});
|
||||||
|
expect(lastUpdatedBy).toBeNull();
|
||||||
|
expect(lastUpdatedAt).toEqual(testTimestamp);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('read last time show author time', async () => {
|
it('read last time show author time', async () => {
|
||||||
const {lastUpdatedAt, lastUpdatedBy} = await readLastUpdateData(
|
const {lastUpdatedAt, lastUpdatedBy} = await readLastUpdateData(
|
||||||
'',
|
'',
|
||||||
|
|
|
@ -154,12 +154,12 @@ export async function getFileCommitDate(
|
||||||
file,
|
file,
|
||||||
)}"`;
|
)}"`;
|
||||||
|
|
||||||
const result = (await GitCommandQueue.add(() =>
|
const result = (await GitCommandQueue.add(() => {
|
||||||
execa(command, {
|
return execa(command, {
|
||||||
cwd: path.dirname(file),
|
cwd: path.dirname(file),
|
||||||
shell: true,
|
shell: true,
|
||||||
}),
|
});
|
||||||
))!;
|
}))!;
|
||||||
|
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
|
@ -15,10 +15,18 @@ import {
|
||||||
import type {PluginOptions} from '@docusaurus/types';
|
import type {PluginOptions} from '@docusaurus/types';
|
||||||
|
|
||||||
export type LastUpdateData = {
|
export type LastUpdateData = {
|
||||||
/** A timestamp in **milliseconds**, usually read from `git log` */
|
/**
|
||||||
lastUpdatedAt?: number;
|
* A timestamp in **milliseconds**, usually read from `git log`
|
||||||
/** The author's name, usually coming from `git log` */
|
* `undefined`: not read
|
||||||
lastUpdatedBy?: string;
|
* `null`: no value to read (usual for untracked files)
|
||||||
|
*/
|
||||||
|
lastUpdatedAt: number | undefined | null;
|
||||||
|
/**
|
||||||
|
* The author's name, usually coming from `git log`
|
||||||
|
* `undefined`: not read
|
||||||
|
* `null`: no value to read (usual for untracked files)
|
||||||
|
*/
|
||||||
|
lastUpdatedBy: string | undefined | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
let showedGitRequirementError = false;
|
let showedGitRequirementError = false;
|
||||||
|
@ -68,9 +76,15 @@ export const LAST_UPDATE_FALLBACK: LastUpdateData = {
|
||||||
lastUpdatedBy: 'Author',
|
lastUpdatedBy: 'Author',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Not proud of this, but convenient for tests :/
|
||||||
|
export const LAST_UPDATE_UNTRACKED_GIT_FILEPATH = `file/path/${Math.random()}.mdx`;
|
||||||
|
|
||||||
export async function getLastUpdate(
|
export async function getLastUpdate(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
): Promise<LastUpdateData | null> {
|
): Promise<LastUpdateData | null> {
|
||||||
|
if (filePath === LAST_UPDATE_UNTRACKED_GIT_FILEPATH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
process.env.NODE_ENV !== 'production' ||
|
process.env.NODE_ENV !== 'production' ||
|
||||||
process.env.DOCUSAURUS_DISABLE_LAST_UPDATE === 'true'
|
process.env.DOCUSAURUS_DISABLE_LAST_UPDATE === 'true'
|
||||||
|
@ -103,7 +117,7 @@ export async function readLastUpdateData(
|
||||||
const {showLastUpdateAuthor, showLastUpdateTime} = options;
|
const {showLastUpdateAuthor, showLastUpdateTime} = options;
|
||||||
|
|
||||||
if (!showLastUpdateAuthor && !showLastUpdateTime) {
|
if (!showLastUpdateAuthor && !showLastUpdateTime) {
|
||||||
return {};
|
return {lastUpdatedBy: undefined, lastUpdatedAt: undefined};
|
||||||
}
|
}
|
||||||
|
|
||||||
const frontMatterAuthor = lastUpdateFrontMatter?.author;
|
const frontMatterAuthor = lastUpdateFrontMatter?.author;
|
||||||
|
@ -116,9 +130,21 @@ export async function readLastUpdateData(
|
||||||
// If all the data is provided as front matter, we do not call it
|
// If all the data is provided as front matter, we do not call it
|
||||||
const getLastUpdateMemoized = _.memoize(() => getLastUpdate(filePath));
|
const getLastUpdateMemoized = _.memoize(() => getLastUpdate(filePath));
|
||||||
const getLastUpdateBy = () =>
|
const getLastUpdateBy = () =>
|
||||||
getLastUpdateMemoized().then((update) => update?.lastUpdatedBy);
|
getLastUpdateMemoized().then((update) => {
|
||||||
|
// Important, see https://github.com/facebook/docusaurus/pull/11211
|
||||||
|
if (update === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return update?.lastUpdatedBy;
|
||||||
|
});
|
||||||
const getLastUpdateAt = () =>
|
const getLastUpdateAt = () =>
|
||||||
getLastUpdateMemoized().then((update) => update?.lastUpdatedAt);
|
getLastUpdateMemoized().then((update) => {
|
||||||
|
// Important, see https://github.com/facebook/docusaurus/pull/11211
|
||||||
|
if (update === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return update?.lastUpdatedAt;
|
||||||
|
});
|
||||||
|
|
||||||
const lastUpdatedBy = showLastUpdateAuthor
|
const lastUpdatedBy = showLastUpdateAuthor
|
||||||
? frontMatterAuthor ?? (await getLastUpdateBy())
|
? frontMatterAuthor ?? (await getLastUpdateBy())
|
||||||
|
|
|
@ -35,6 +35,7 @@ export type BuildLocaleParams = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const SkipBundling = process.env.DOCUSAURUS_SKIP_BUNDLING === 'true';
|
const SkipBundling = process.env.DOCUSAURUS_SKIP_BUNDLING === 'true';
|
||||||
|
const ExitAfterLoading = process.env.DOCUSAURUS_EXIT_AFTER_LOADING === 'true';
|
||||||
const ExitAfterBundling = process.env.DOCUSAURUS_EXIT_AFTER_BUNDLING === 'true';
|
const ExitAfterBundling = process.env.DOCUSAURUS_EXIT_AFTER_BUNDLING === 'true';
|
||||||
|
|
||||||
export async function buildLocale({
|
export async function buildLocale({
|
||||||
|
@ -59,6 +60,10 @@ export async function buildLocale({
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (ExitAfterLoading) {
|
||||||
|
return process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
const {props} = site;
|
const {props} = site;
|
||||||
const {outDir, plugins, siteConfig} = props;
|
const {outDir, plugins, siteConfig} = props;
|
||||||
|
|
||||||
|
|
|
@ -316,6 +316,10 @@ export default async function createConfigAsync() {
|
||||||
'./src/plugins/changelog/index.ts',
|
'./src/plugins/changelog/index.ts',
|
||||||
{
|
{
|
||||||
blogTitle: 'Docusaurus changelog',
|
blogTitle: 'Docusaurus changelog',
|
||||||
|
// Not useful, but permits to run git commands earlier
|
||||||
|
// Otherwise the sitemap plugin will run them in postBuild()
|
||||||
|
showLastUpdateAuthor: true,
|
||||||
|
showLastUpdateTime: true,
|
||||||
blogDescription:
|
blogDescription:
|
||||||
'Keep yourself up-to-date about new features in every release',
|
'Keep yourself up-to-date about new features in every release',
|
||||||
blogSidebarCount: 'ALL',
|
blogSidebarCount: 'ALL',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue