mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 15:47:23 +02:00
refactor(core): improve dev perf, fine-grained site reloads - part 3 (#9975)
This commit is contained in:
parent
06e70a4f9a
commit
efbe474e9c
22 changed files with 359 additions and 286 deletions
1
packages/docusaurus-types/src/context.d.ts
vendored
1
packages/docusaurus-types/src/context.d.ts
vendored
|
@ -31,6 +31,7 @@ export type GlobalData = {[pluginName: string]: {[pluginId: string]: unknown}};
|
|||
|
||||
export type LoadContext = {
|
||||
siteDir: string;
|
||||
siteVersion: string | undefined;
|
||||
generatedFilesDir: string;
|
||||
siteConfig: DocusaurusConfig;
|
||||
siteConfigPath: string;
|
||||
|
|
3
packages/docusaurus-types/src/plugin.d.ts
vendored
3
packages/docusaurus-types/src/plugin.d.ts
vendored
|
@ -5,7 +5,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {TranslationFile} from './i18n';
|
||||
import type {CodeTranslations, TranslationFile} from './i18n';
|
||||
import type {RuleSetRule, Configuration as WebpackConfiguration} from 'webpack';
|
||||
import type {CustomizeRuleString} from 'webpack-merge/dist/types';
|
||||
import type {CommanderStatic} from 'commander';
|
||||
|
@ -185,6 +185,7 @@ export type LoadedPlugin = InitializedPlugin & {
|
|||
readonly content: unknown;
|
||||
readonly globalData: unknown;
|
||||
readonly routes: RouteConfig[];
|
||||
readonly defaultCodeTranslations: CodeTranslations;
|
||||
};
|
||||
|
||||
export type PluginModule<Content = unknown> = {
|
||||
|
|
|
@ -12,6 +12,10 @@ import {findAsyncSequential} from './jsUtils';
|
|||
|
||||
const fileHash = new Map<string, string>();
|
||||
|
||||
const hashContent = (content: string): string => {
|
||||
return createHash('md5').update(content).digest('hex');
|
||||
};
|
||||
|
||||
/**
|
||||
* Outputs a file to the generated files directory. Only writes files if content
|
||||
* differs from cache (for hot reload performance).
|
||||
|
@ -38,7 +42,7 @@ export async function generate(
|
|||
// first "A" remains in cache. But if the file never existed in cache, no
|
||||
// need to register it.
|
||||
if (fileHash.get(filepath)) {
|
||||
fileHash.set(filepath, createHash('md5').update(content).digest('hex'));
|
||||
fileHash.set(filepath, hashContent(content));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -50,11 +54,11 @@ export async function generate(
|
|||
// overwriting and we can reuse old file.
|
||||
if (!lastHash && (await fs.pathExists(filepath))) {
|
||||
const lastContent = await fs.readFile(filepath, 'utf8');
|
||||
lastHash = createHash('md5').update(lastContent).digest('hex');
|
||||
lastHash = hashContent(lastContent);
|
||||
fileHash.set(filepath, lastHash);
|
||||
}
|
||||
|
||||
const currentHash = createHash('md5').update(content).digest('hex');
|
||||
const currentHash = hashContent(content);
|
||||
|
||||
if (lastHash !== currentHash) {
|
||||
await fs.outputFile(filepath, content);
|
||||
|
|
|
@ -6,7 +6,16 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import shell from 'shelljs';
|
||||
import fs from 'fs-extra';
|
||||
import _ from 'lodash';
|
||||
import shell from 'shelljs'; // TODO replace with async-first version
|
||||
|
||||
const realHasGitFn = () => !!shell.which('git');
|
||||
|
||||
// The hasGit call is synchronous IO so we memoize it
|
||||
// The user won't install Git in the middle of a build anyway...
|
||||
const hasGit =
|
||||
process.env.NODE_ENV === 'test' ? realHasGitFn : _.memoize(realHasGitFn);
|
||||
|
||||
/** Custom error thrown when git is not found in `PATH`. */
|
||||
export class GitNotFoundError extends Error {}
|
||||
|
@ -86,13 +95,13 @@ export async function getFileCommitDate(
|
|||
timestamp: number;
|
||||
author?: string;
|
||||
}> {
|
||||
if (!shell.which('git')) {
|
||||
if (!hasGit()) {
|
||||
throw new GitNotFoundError(
|
||||
`Failed to retrieve git history for "${file}" because git is not installed.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!shell.test('-f', file)) {
|
||||
if (!(await fs.pathExists(file))) {
|
||||
throw new Error(
|
||||
`Failed to retrieve git history for "${file}" because the file does not exist.`,
|
||||
);
|
||||
|
|
|
@ -64,23 +64,15 @@ export async function build(
|
|||
process.on(sig, () => process.exit());
|
||||
});
|
||||
|
||||
async function tryToBuildLocale({
|
||||
locale,
|
||||
isLastLocale,
|
||||
}: {
|
||||
locale: string;
|
||||
isLastLocale: boolean;
|
||||
}) {
|
||||
async function tryToBuildLocale({locale}: {locale: string}) {
|
||||
try {
|
||||
PerfLogger.start(`Building site for locale ${locale}`);
|
||||
await buildLocale({
|
||||
await PerfLogger.async(`${logger.name(locale)}`, () =>
|
||||
buildLocale({
|
||||
siteDir,
|
||||
locale,
|
||||
cliOptions,
|
||||
forceTerminate,
|
||||
isLastLocale,
|
||||
});
|
||||
PerfLogger.end(`Building site for locale ${locale}`);
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
logger.interpolate`Unable to build website for locale name=${locale}.`,
|
||||
|
@ -91,20 +83,28 @@ export async function build(
|
|||
}
|
||||
}
|
||||
|
||||
PerfLogger.start(`Get locales to build`);
|
||||
const locales = await getLocalesToBuild({siteDir, cliOptions});
|
||||
PerfLogger.end(`Get locales to build`);
|
||||
const locales = await PerfLogger.async('Get locales to build', () =>
|
||||
getLocalesToBuild({siteDir, cliOptions}),
|
||||
);
|
||||
|
||||
if (locales.length > 1) {
|
||||
logger.info`Website will be built for all these locales: ${locales}`;
|
||||
}
|
||||
|
||||
PerfLogger.start(`Building ${locales.length} locales`);
|
||||
await mapAsyncSequential(locales, (locale) => {
|
||||
await PerfLogger.async(`Build`, () =>
|
||||
mapAsyncSequential(locales, async (locale) => {
|
||||
const isLastLocale = locales.indexOf(locale) === locales.length - 1;
|
||||
return tryToBuildLocale({locale, isLastLocale});
|
||||
});
|
||||
PerfLogger.end(`Building ${locales.length} locales`);
|
||||
await tryToBuildLocale({locale});
|
||||
if (isLastLocale) {
|
||||
logger.info`Use code=${'npm run serve'} command to test your build locally.`;
|
||||
}
|
||||
|
||||
// TODO do we really need this historical forceTerminate exit???
|
||||
if (forceTerminate && isLastLocale && !cliOptions.bundleAnalyzer) {
|
||||
process.exit(0);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function getLocalesToBuild({
|
||||
|
@ -144,14 +144,10 @@ async function buildLocale({
|
|||
siteDir,
|
||||
locale,
|
||||
cliOptions,
|
||||
forceTerminate,
|
||||
isLastLocale,
|
||||
}: {
|
||||
siteDir: string;
|
||||
locale: string;
|
||||
cliOptions: Partial<BuildCLIOptions>;
|
||||
forceTerminate: boolean;
|
||||
isLastLocale: boolean;
|
||||
}): Promise<string> {
|
||||
// Temporary workaround to unlock the ability to translate the site config
|
||||
// We'll remove it if a better official API can be designed
|
||||
|
@ -160,23 +156,23 @@ async function buildLocale({
|
|||
|
||||
logger.info`name=${`[${locale}]`} Creating an optimized production build...`;
|
||||
|
||||
PerfLogger.start('Loading site');
|
||||
const site = await loadSite({
|
||||
const site = await PerfLogger.async('Load site', () =>
|
||||
loadSite({
|
||||
siteDir,
|
||||
outDir: cliOptions.outDir,
|
||||
config: cliOptions.config,
|
||||
locale,
|
||||
localizePath: cliOptions.locale ? false : undefined,
|
||||
});
|
||||
PerfLogger.end('Loading site');
|
||||
}),
|
||||
);
|
||||
|
||||
const {props} = site;
|
||||
const {outDir, plugins} = props;
|
||||
|
||||
// We can build the 2 configs in parallel
|
||||
PerfLogger.start('Creating webpack configs');
|
||||
const [{clientConfig, clientManifestPath}, {serverConfig, serverBundlePath}] =
|
||||
await Promise.all([
|
||||
await PerfLogger.async('Creating webpack configs', () =>
|
||||
Promise.all([
|
||||
getBuildClientConfig({
|
||||
props,
|
||||
cliOptions,
|
||||
|
@ -184,57 +180,42 @@ async function buildLocale({
|
|||
getBuildServerConfig({
|
||||
props,
|
||||
}),
|
||||
]);
|
||||
PerfLogger.end('Creating webpack configs');
|
||||
|
||||
// Make sure generated client-manifest is cleaned first, so we don't reuse
|
||||
// the one from previous builds.
|
||||
// TODO do we really need this? .docusaurus folder is cleaned between builds
|
||||
PerfLogger.start('Deleting previous client manifest');
|
||||
await ensureUnlink(clientManifestPath);
|
||||
PerfLogger.end('Deleting previous client manifest');
|
||||
]),
|
||||
);
|
||||
|
||||
// Run webpack to build JS bundle (client) and static html files (server).
|
||||
PerfLogger.start('Bundling');
|
||||
await compile([clientConfig, serverConfig]);
|
||||
PerfLogger.end('Bundling');
|
||||
await PerfLogger.async('Bundling with Webpack', () =>
|
||||
compile([clientConfig, serverConfig]),
|
||||
);
|
||||
|
||||
PerfLogger.start('Executing static site generation');
|
||||
const {collectedData} = await executeSSG({
|
||||
const {collectedData} = await PerfLogger.async('SSG', () =>
|
||||
executeSSG({
|
||||
props,
|
||||
serverBundlePath,
|
||||
clientManifestPath,
|
||||
});
|
||||
PerfLogger.end('Executing static site generation');
|
||||
}),
|
||||
);
|
||||
|
||||
// Remove server.bundle.js because it is not needed.
|
||||
PerfLogger.start('Deleting server bundle');
|
||||
await ensureUnlink(serverBundlePath);
|
||||
PerfLogger.end('Deleting server bundle');
|
||||
await PerfLogger.async('Deleting server bundle', () =>
|
||||
ensureUnlink(serverBundlePath),
|
||||
);
|
||||
|
||||
// Plugin Lifecycle - postBuild.
|
||||
PerfLogger.start('Executing postBuild()');
|
||||
await executePluginsPostBuild({plugins, props, collectedData});
|
||||
PerfLogger.end('Executing postBuild()');
|
||||
await PerfLogger.async('postBuild()', () =>
|
||||
executePluginsPostBuild({plugins, props, collectedData}),
|
||||
);
|
||||
|
||||
// TODO execute this in parallel to postBuild?
|
||||
PerfLogger.start('Executing broken links checker');
|
||||
await executeBrokenLinksCheck({props, collectedData});
|
||||
PerfLogger.end('Executing broken links checker');
|
||||
await PerfLogger.async('Broken links checker', () =>
|
||||
executeBrokenLinksCheck({props, collectedData}),
|
||||
);
|
||||
|
||||
logger.success`Generated static files in path=${path.relative(
|
||||
process.cwd(),
|
||||
outDir,
|
||||
)}.`;
|
||||
|
||||
if (isLastLocale) {
|
||||
logger.info`Use code=${'npm run serve'} command to test your build locally.`;
|
||||
}
|
||||
|
||||
if (forceTerminate && isLastLocale && !cliOptions.bundleAnalyzer) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
return outDir;
|
||||
}
|
||||
|
||||
|
@ -247,24 +228,23 @@ async function executeSSG({
|
|||
serverBundlePath: string;
|
||||
clientManifestPath: string;
|
||||
}) {
|
||||
PerfLogger.start('Reading client manifest');
|
||||
const manifest: Manifest = await fs.readJSON(clientManifestPath, 'utf-8');
|
||||
PerfLogger.end('Reading client manifest');
|
||||
|
||||
PerfLogger.start('Compiling SSR template');
|
||||
const ssrTemplate = await compileSSRTemplate(
|
||||
props.siteConfig.ssrTemplate ?? defaultSSRTemplate,
|
||||
const manifest: Manifest = await PerfLogger.async(
|
||||
'Read client manifest',
|
||||
() => fs.readJSON(clientManifestPath, 'utf-8'),
|
||||
);
|
||||
PerfLogger.end('Compiling SSR template');
|
||||
|
||||
PerfLogger.start('Loading App renderer');
|
||||
const renderer = await loadAppRenderer({
|
||||
const ssrTemplate = await PerfLogger.async('Compile SSR template', () =>
|
||||
compileSSRTemplate(props.siteConfig.ssrTemplate ?? defaultSSRTemplate),
|
||||
);
|
||||
|
||||
const renderer = await PerfLogger.async('Load App renderer', () =>
|
||||
loadAppRenderer({
|
||||
serverBundlePath,
|
||||
});
|
||||
PerfLogger.end('Loading App renderer');
|
||||
}),
|
||||
);
|
||||
|
||||
PerfLogger.start('Generate static files');
|
||||
const ssgResult = await generateStaticFiles({
|
||||
const ssgResult = await PerfLogger.async('Generate static files', () =>
|
||||
generateStaticFiles({
|
||||
pathnames: props.routesPaths,
|
||||
renderer,
|
||||
params: {
|
||||
|
@ -279,8 +259,8 @@ async function executeSSG({
|
|||
noIndex: props.siteConfig.noIndex,
|
||||
DOCUSAURUS_VERSION,
|
||||
},
|
||||
});
|
||||
PerfLogger.end('Generate static files');
|
||||
}),
|
||||
);
|
||||
|
||||
return ssgResult;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
reloadSite,
|
||||
reloadSitePlugin,
|
||||
} from '../../server/site';
|
||||
import {formatPluginName} from '../../server/plugins/pluginsUtils';
|
||||
import type {StartCLIOptions} from './start';
|
||||
import type {LoadedPlugin} from '@docusaurus/types';
|
||||
|
||||
|
@ -69,10 +70,13 @@ async function createLoadSiteParams({
|
|||
export async function createReloadableSite(startParams: StartParams) {
|
||||
const openUrlContext = await createOpenUrlContext(startParams);
|
||||
|
||||
let site = await PerfLogger.async('Loading site', async () => {
|
||||
const params = await createLoadSiteParams(startParams);
|
||||
return loadSite(params);
|
||||
});
|
||||
const loadSiteParams = await PerfLogger.async('createLoadSiteParams', () =>
|
||||
createLoadSiteParams(startParams),
|
||||
);
|
||||
|
||||
let site = await PerfLogger.async('Load site', () =>
|
||||
loadSite(loadSiteParams),
|
||||
);
|
||||
|
||||
const get = () => site;
|
||||
|
||||
|
@ -89,7 +93,7 @@ export async function createReloadableSite(startParams: StartParams) {
|
|||
const reloadBase = async () => {
|
||||
try {
|
||||
const oldSite = site;
|
||||
site = await PerfLogger.async('Reloading site', () => reloadSite(site));
|
||||
site = await PerfLogger.async('Reload site', () => reloadSite(site));
|
||||
if (oldSite.props.baseUrl !== site.props.baseUrl) {
|
||||
printOpenUrlMessage();
|
||||
}
|
||||
|
@ -108,7 +112,7 @@ export async function createReloadableSite(startParams: StartParams) {
|
|||
const reloadPlugin = async (plugin: LoadedPlugin) => {
|
||||
try {
|
||||
site = await PerfLogger.async(
|
||||
`Reloading site plugin ${plugin.name}@${plugin.options.id}`,
|
||||
`Reload site plugin ${formatPluginName(plugin)}`,
|
||||
() => {
|
||||
const pluginIdentifier = {name: plugin.name, id: plugin.options.id};
|
||||
return reloadSitePlugin(site, pluginIdentifier);
|
||||
|
@ -116,7 +120,7 @@ export async function createReloadableSite(startParams: StartParams) {
|
|||
);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Site plugin reload failure - Plugin ${plugin.name}@${plugin.options.id}`,
|
||||
`Site plugin reload failure - Plugin ${formatPluginName(plugin)}`,
|
||||
);
|
||||
console.error(e);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
writePluginTranslations,
|
||||
writeCodeTranslations,
|
||||
type WriteTranslationsOptions,
|
||||
getPluginsDefaultCodeTranslationMessages,
|
||||
loadPluginsDefaultCodeTranslationMessages,
|
||||
applyDefaultCodeTranslations,
|
||||
} from '../server/translations/translations';
|
||||
import {
|
||||
|
@ -114,7 +114,7 @@ Available locales are: ${context.i18n.locales.join(',')}.`,
|
|||
await getExtraSourceCodeFilePaths(),
|
||||
);
|
||||
|
||||
const defaultCodeMessages = await getPluginsDefaultCodeTranslationMessages(
|
||||
const defaultCodeMessages = await loadPluginsDefaultCodeTranslationMessages(
|
||||
plugins,
|
||||
);
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
|
|||
"plugins": [
|
||||
{
|
||||
"content": undefined,
|
||||
"defaultCodeTranslations": {},
|
||||
"getClientModules": [Function],
|
||||
"globalData": undefined,
|
||||
"injectHtmlTags": [Function],
|
||||
|
@ -52,6 +53,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
|
|||
{
|
||||
"configureWebpack": [Function],
|
||||
"content": undefined,
|
||||
"defaultCodeTranslations": {},
|
||||
"globalData": undefined,
|
||||
"name": "docusaurus-mdx-fallback-plugin",
|
||||
"options": {
|
||||
|
@ -132,5 +134,6 @@ exports[`load loads props for site with custom i18n path 1`] = `
|
|||
"pluginVersions": {},
|
||||
"siteVersion": undefined,
|
||||
},
|
||||
"siteVersion": undefined,
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {loadClientModules} from '../clientModules';
|
||||
import {getAllClientModules} from '../clientModules';
|
||||
import type {LoadedPlugin} from '@docusaurus/types';
|
||||
|
||||
const pluginEmpty = {
|
||||
|
@ -33,14 +33,14 @@ const pluginHelloWorld = {
|
|||
},
|
||||
} as unknown as LoadedPlugin;
|
||||
|
||||
describe('loadClientModules', () => {
|
||||
describe('getAllClientModules', () => {
|
||||
it('loads an empty plugin', () => {
|
||||
const clientModules = loadClientModules([pluginEmpty]);
|
||||
const clientModules = getAllClientModules([pluginEmpty]);
|
||||
expect(clientModules).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
|
||||
it('loads a non-empty plugin', () => {
|
||||
const clientModules = loadClientModules([pluginFooBar]);
|
||||
const clientModules = getAllClientModules([pluginFooBar]);
|
||||
expect(clientModules).toMatchInlineSnapshot(`
|
||||
[
|
||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/foo",
|
||||
|
@ -50,7 +50,7 @@ describe('loadClientModules', () => {
|
|||
});
|
||||
|
||||
it('loads multiple non-empty plugins', () => {
|
||||
const clientModules = loadClientModules([pluginFooBar, pluginHelloWorld]);
|
||||
const clientModules = getAllClientModules([pluginFooBar, pluginHelloWorld]);
|
||||
expect(clientModules).toMatchInlineSnapshot(`
|
||||
[
|
||||
"<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/foo",
|
||||
|
@ -62,7 +62,7 @@ describe('loadClientModules', () => {
|
|||
});
|
||||
|
||||
it('loads multiple non-empty plugins in different order', () => {
|
||||
const clientModules = loadClientModules([pluginHelloWorld, pluginFooBar]);
|
||||
const clientModules = getAllClientModules([pluginHelloWorld, pluginFooBar]);
|
||||
expect(clientModules).toMatchInlineSnapshot(`
|
||||
[
|
||||
"/hello",
|
||||
|
@ -74,7 +74,7 @@ describe('loadClientModules', () => {
|
|||
});
|
||||
|
||||
it('loads both empty and non-empty plugins', () => {
|
||||
const clientModules = loadClientModules([
|
||||
const clientModules = getAllClientModules([
|
||||
pluginHelloWorld,
|
||||
pluginEmpty,
|
||||
pluginFooBar,
|
||||
|
@ -90,7 +90,7 @@ describe('loadClientModules', () => {
|
|||
});
|
||||
|
||||
it('loads empty and non-empty in a different order', () => {
|
||||
const clientModules = loadClientModules([
|
||||
const clientModules = getAllClientModules([
|
||||
pluginHelloWorld,
|
||||
pluginFooBar,
|
||||
pluginEmpty,
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
import path from 'path';
|
||||
import {DOCUSAURUS_VERSION} from '@docusaurus/utils';
|
||||
import {getPluginVersion, loadSiteMetadata} from '../siteMetadata';
|
||||
import {loadPluginVersion, createSiteMetadata} from '../siteMetadata';
|
||||
import type {LoadedPlugin} from '@docusaurus/types';
|
||||
|
||||
describe('getPluginVersion', () => {
|
||||
describe('loadPluginVersion', () => {
|
||||
it('detects external packages plugins versions', async () => {
|
||||
await expect(
|
||||
getPluginVersion(
|
||||
loadPluginVersion(
|
||||
path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'),
|
||||
// Make the plugin appear external.
|
||||
path.join(__dirname, '..', '..', '..', '..', '..', '..', 'website'),
|
||||
|
@ -23,7 +23,7 @@ describe('getPluginVersion', () => {
|
|||
|
||||
it('detects project plugins versions', async () => {
|
||||
await expect(
|
||||
getPluginVersion(
|
||||
loadPluginVersion(
|
||||
path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'),
|
||||
// Make the plugin appear project local.
|
||||
path.join(__dirname, '__fixtures__/siteMetadata'),
|
||||
|
@ -32,14 +32,14 @@ describe('getPluginVersion', () => {
|
|||
});
|
||||
|
||||
it('detects local packages versions', async () => {
|
||||
await expect(getPluginVersion('/', '/')).resolves.toEqual({type: 'local'});
|
||||
await expect(loadPluginVersion('/', '/')).resolves.toEqual({type: 'local'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadSiteMetadata', () => {
|
||||
it('throws if plugin versions mismatch', async () => {
|
||||
await expect(
|
||||
loadSiteMetadata({
|
||||
describe('createSiteMetadata', () => {
|
||||
it('throws if plugin versions mismatch', () => {
|
||||
expect(() =>
|
||||
createSiteMetadata({
|
||||
plugins: [
|
||||
{
|
||||
name: 'docusaurus-plugin-content-docs',
|
||||
|
@ -50,10 +50,9 @@ describe('loadSiteMetadata', () => {
|
|||
},
|
||||
},
|
||||
] as LoadedPlugin[],
|
||||
siteDir: path.join(__dirname, '__fixtures__/siteMetadata'),
|
||||
siteVersion: 'some-random-version',
|
||||
}),
|
||||
).rejects
|
||||
.toThrow(`Invalid name=docusaurus-plugin-content-docs version number=1.0.0.
|
||||
).toThrow(`Invalid name=docusaurus-plugin-content-docs version number=1.0.0.
|
||||
All official @docusaurus/* packages should have the exact same version as @docusaurus/core (number=${DOCUSAURUS_VERSION}).
|
||||
Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?`);
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ import type {LoadedPlugin} from '@docusaurus/types';
|
|||
* Runs the `getClientModules` lifecycle. The returned file paths are all
|
||||
* absolute.
|
||||
*/
|
||||
export function loadClientModules(plugins: LoadedPlugin[]): string[] {
|
||||
export function getAllClientModules(plugins: LoadedPlugin[]): string[] {
|
||||
return plugins.flatMap(
|
||||
(plugin) =>
|
||||
plugin.getClientModules?.().map((p) => path.resolve(plugin.path, p)) ??
|
||||
|
|
|
@ -48,6 +48,7 @@ export async function createPluginActionsUtils({
|
|||
dataDir,
|
||||
`${docuHash('pluginRouteContextModule')}.json`,
|
||||
);
|
||||
// TODO not ideal place to generate that file
|
||||
await generate(
|
||||
'/',
|
||||
pluginRouteContextModulePath,
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
normalizePluginOptions,
|
||||
normalizeThemeConfig,
|
||||
} from '@docusaurus/utils-validation';
|
||||
import {getPluginVersion} from '../siteMetadata';
|
||||
import {loadPluginVersion} from '../siteMetadata';
|
||||
import {ensureUniquePluginInstanceIds} from './pluginIds';
|
||||
import {loadPluginConfigs, type NormalizedPluginConfig} from './configs';
|
||||
import type {
|
||||
|
@ -61,14 +61,14 @@ export async function initPlugins(
|
|||
const pluginRequire = createRequire(context.siteConfigPath);
|
||||
const pluginConfigs = await loadPluginConfigs(context);
|
||||
|
||||
async function doGetPluginVersion(
|
||||
async function doLoadPluginVersion(
|
||||
normalizedPluginConfig: NormalizedPluginConfig,
|
||||
): Promise<PluginVersionInformation> {
|
||||
if (normalizedPluginConfig.pluginModule?.path) {
|
||||
const pluginPath = pluginRequire.resolve(
|
||||
normalizedPluginConfig.pluginModule.path,
|
||||
);
|
||||
return getPluginVersion(pluginPath, context.siteDir);
|
||||
return loadPluginVersion(pluginPath, context.siteDir);
|
||||
}
|
||||
return {type: 'local'};
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ export async function initPlugins(
|
|||
async function initializePlugin(
|
||||
normalizedPluginConfig: NormalizedPluginConfig,
|
||||
): Promise<InitializedPlugin> {
|
||||
const pluginVersion: PluginVersionInformation = await doGetPluginVersion(
|
||||
const pluginVersion: PluginVersionInformation = await doLoadPluginVersion(
|
||||
normalizedPluginConfig,
|
||||
);
|
||||
const pluginOptions = doValidatePluginOptions(normalizedPluginConfig);
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
aggregateAllContent,
|
||||
aggregateGlobalData,
|
||||
aggregateRoutes,
|
||||
formatPluginName,
|
||||
getPluginByIdentifier,
|
||||
mergeGlobalData,
|
||||
} from './pluginsUtils';
|
||||
|
@ -73,21 +74,29 @@ async function executePluginContentLoading({
|
|||
plugin: InitializedPlugin;
|
||||
context: LoadContext;
|
||||
}): Promise<LoadedPlugin> {
|
||||
return PerfLogger.async(
|
||||
`Plugins - single plugin content loading - ${plugin.name}@${plugin.options.id}`,
|
||||
async () => {
|
||||
let content = await plugin.loadContent?.();
|
||||
return PerfLogger.async(`Load ${formatPluginName(plugin)}`, async () => {
|
||||
let content = await PerfLogger.async('loadContent()', () =>
|
||||
plugin.loadContent?.(),
|
||||
);
|
||||
|
||||
content = await translatePluginContent({
|
||||
content = await PerfLogger.async('translatePluginContent()', () =>
|
||||
translatePluginContent({
|
||||
plugin,
|
||||
content,
|
||||
context,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
const defaultCodeTranslations =
|
||||
(await PerfLogger.async('getDefaultCodeTranslationMessages()', () =>
|
||||
plugin.getDefaultCodeTranslationMessages?.(),
|
||||
)) ?? {};
|
||||
|
||||
if (!plugin.contentLoaded) {
|
||||
return {
|
||||
...plugin,
|
||||
content,
|
||||
defaultCodeTranslations,
|
||||
routes: [],
|
||||
globalData: undefined,
|
||||
};
|
||||
|
@ -100,19 +109,22 @@ async function executePluginContentLoading({
|
|||
trailingSlash: context.siteConfig.trailingSlash,
|
||||
});
|
||||
|
||||
await plugin.contentLoaded({
|
||||
await PerfLogger.async('contentLoaded()', () =>
|
||||
// @ts-expect-error: should autofix with TS 5.4
|
||||
plugin.contentLoaded({
|
||||
content,
|
||||
actions: pluginActionsUtils.getActions(),
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
...plugin,
|
||||
content,
|
||||
defaultCodeTranslations,
|
||||
routes: pluginActionsUtils.getRoutes(),
|
||||
globalData: pluginActionsUtils.getGlobalData(),
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function executeAllPluginsContentLoading({
|
||||
|
@ -122,7 +134,7 @@ async function executeAllPluginsContentLoading({
|
|||
plugins: InitializedPlugin[];
|
||||
context: LoadContext;
|
||||
}): Promise<LoadedPlugin[]> {
|
||||
return PerfLogger.async(`Plugins - all plugins content loading`, () => {
|
||||
return PerfLogger.async(`Load plugins content`, () => {
|
||||
return Promise.all(
|
||||
plugins.map((plugin) => executePluginContentLoading({plugin, context})),
|
||||
);
|
||||
|
@ -139,7 +151,7 @@ async function executePluginAllContentLoaded({
|
|||
allContent: AllContent;
|
||||
}): Promise<{routes: RouteConfig[]; globalData: unknown}> {
|
||||
return PerfLogger.async(
|
||||
`Plugins - allContentLoaded - ${plugin.name}@${plugin.options.id}`,
|
||||
`allContentLoaded() - ${formatPluginName(plugin)}`,
|
||||
async () => {
|
||||
if (!plugin.allContentLoaded) {
|
||||
return {routes: [], globalData: undefined};
|
||||
|
@ -171,7 +183,7 @@ async function executeAllPluginsAllContentLoaded({
|
|||
plugins: LoadedPlugin[];
|
||||
context: LoadContext;
|
||||
}): Promise<AllContentLoadedResult> {
|
||||
return PerfLogger.async(`Plugins - allContentLoaded`, async () => {
|
||||
return PerfLogger.async(`allContentLoaded()`, async () => {
|
||||
const allContent = aggregateAllContent(plugins);
|
||||
|
||||
const routes: RouteConfig[] = [];
|
||||
|
@ -199,6 +211,9 @@ async function executeAllPluginsAllContentLoaded({
|
|||
});
|
||||
}
|
||||
|
||||
// This merges plugins routes and global data created from both lifecycles:
|
||||
// - contentLoaded()
|
||||
// - allContentLoaded()
|
||||
function mergeResults({
|
||||
plugins,
|
||||
allContentLoadedResult,
|
||||
|
@ -232,9 +247,9 @@ export type LoadPluginsResult = {
|
|||
export async function loadPlugins(
|
||||
context: LoadContext,
|
||||
): Promise<LoadPluginsResult> {
|
||||
return PerfLogger.async('Plugins - loadPlugins', async () => {
|
||||
return PerfLogger.async('Load plugins', async () => {
|
||||
const initializedPlugins: InitializedPlugin[] = await PerfLogger.async(
|
||||
'Plugins - initPlugins',
|
||||
'Init plugins',
|
||||
() => initPlugins(context),
|
||||
);
|
||||
|
||||
|
@ -272,7 +287,9 @@ export async function reloadPlugin({
|
|||
plugins: LoadedPlugin[];
|
||||
context: LoadContext;
|
||||
}): Promise<LoadPluginsResult> {
|
||||
return PerfLogger.async('Plugins - reloadPlugin', async () => {
|
||||
return PerfLogger.async(
|
||||
`Reload plugin ${formatPluginName(pluginIdentifier)}`,
|
||||
async () => {
|
||||
const previousPlugin = getPluginByIdentifier({
|
||||
plugins: previousPlugins,
|
||||
pluginIdentifier,
|
||||
|
@ -303,5 +320,6 @@ export async function reloadPlugin({
|
|||
});
|
||||
|
||||
return {plugins, routes, globalData};
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,9 @@ export function getPluginByIdentifier<P extends InitializedPlugin>({
|
|||
);
|
||||
if (!plugin) {
|
||||
throw new Error(
|
||||
logger.interpolate`Plugin not found for identifier ${pluginIdentifier.name}@${pluginIdentifier.id}`,
|
||||
logger.interpolate`Plugin not found for identifier ${formatPluginName(
|
||||
pluginIdentifier,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
return plugin;
|
||||
|
@ -85,3 +87,22 @@ export function mergeGlobalData(...globalDataList: GlobalData[]): GlobalData {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
// This is primarily useful for colored logging purpose
|
||||
// Do not rely on this for logic
|
||||
export function formatPluginName(
|
||||
plugin: InitializedPlugin | PluginIdentifier,
|
||||
): string {
|
||||
let formattedName = plugin.name;
|
||||
// Hacky way to reduce string size for logging purpose
|
||||
formattedName = formattedName.replace('docusaurus-plugin-content-', '');
|
||||
formattedName = formattedName.replace('docusaurus-plugin-', '');
|
||||
formattedName = formattedName.replace('docusaurus-theme-', '');
|
||||
formattedName = formattedName.replace('-plugin', '');
|
||||
formattedName = logger.name(formattedName);
|
||||
|
||||
const id = 'id' in plugin ? plugin.id : plugin.options.id;
|
||||
const formattedId = logger.subdue(id);
|
||||
|
||||
return `${formattedName}@${formattedId}`;
|
||||
}
|
||||
|
|
|
@ -13,14 +13,14 @@ import {
|
|||
} from '@docusaurus/utils';
|
||||
import combinePromises from 'combine-promises';
|
||||
import {loadSiteConfig} from './config';
|
||||
import {loadClientModules} from './clientModules';
|
||||
import {getAllClientModules} from './clientModules';
|
||||
import {loadPlugins, reloadPlugin} from './plugins/plugins';
|
||||
import {loadHtmlTags} from './htmlTags';
|
||||
import {loadSiteMetadata} from './siteMetadata';
|
||||
import {createSiteMetadata, loadSiteVersion} from './siteMetadata';
|
||||
import {loadI18n} from './i18n';
|
||||
import {
|
||||
loadSiteCodeTranslations,
|
||||
getPluginsDefaultCodeTranslationMessages,
|
||||
getPluginsDefaultCodeTranslations,
|
||||
} from './translations/translations';
|
||||
import {PerfLogger} from '../utils';
|
||||
import {generateSiteFiles} from './codegen/codegen';
|
||||
|
@ -76,9 +76,15 @@ export async function loadContext(
|
|||
} = params;
|
||||
const generatedFilesDir = path.resolve(siteDir, GENERATED_FILES_DIR_NAME);
|
||||
|
||||
const {siteConfig: initialSiteConfig, siteConfigPath} = await loadSiteConfig({
|
||||
const {
|
||||
siteVersion,
|
||||
loadSiteConfig: {siteConfig: initialSiteConfig, siteConfigPath},
|
||||
} = await combinePromises({
|
||||
siteVersion: loadSiteVersion(siteDir),
|
||||
loadSiteConfig: loadSiteConfig({
|
||||
siteDir,
|
||||
customConfigFilePath,
|
||||
}),
|
||||
});
|
||||
|
||||
const i18n = await loadI18n(initialSiteConfig, {locale});
|
||||
|
@ -107,6 +113,7 @@ export async function loadContext(
|
|||
|
||||
return {
|
||||
siteDir,
|
||||
siteVersion,
|
||||
generatedFilesDir,
|
||||
localizationDir,
|
||||
siteConfig,
|
||||
|
@ -118,13 +125,14 @@ export async function loadContext(
|
|||
};
|
||||
}
|
||||
|
||||
async function createSiteProps(
|
||||
function createSiteProps(
|
||||
params: LoadPluginsResult & {context: LoadContext},
|
||||
): Promise<Props> {
|
||||
): Props {
|
||||
const {plugins, routes, context} = params;
|
||||
const {
|
||||
generatedFilesDir,
|
||||
siteDir,
|
||||
siteVersion,
|
||||
siteConfig,
|
||||
siteConfigPath,
|
||||
outDir,
|
||||
|
@ -136,19 +144,12 @@ async function createSiteProps(
|
|||
|
||||
const {headTags, preBodyTags, postBodyTags} = loadHtmlTags(plugins);
|
||||
|
||||
const {codeTranslations, siteMetadata} = await combinePromises({
|
||||
// TODO code translations should be loaded as part of LoadedPlugin?
|
||||
codeTranslations: PerfLogger.async(
|
||||
'Load - loadCodeTranslations',
|
||||
async () => ({
|
||||
...(await getPluginsDefaultCodeTranslationMessages(plugins)),
|
||||
const siteMetadata = createSiteMetadata({plugins, siteVersion});
|
||||
|
||||
const codeTranslations = {
|
||||
...getPluginsDefaultCodeTranslations({plugins}),
|
||||
...siteCodeTranslations,
|
||||
}),
|
||||
),
|
||||
siteMetadata: PerfLogger.async('Load - loadSiteMetadata', () =>
|
||||
loadSiteMetadata({plugins, siteDir}),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
handleDuplicateRoutes(routes, siteConfig.onDuplicateRoutes);
|
||||
const routesPaths = getRoutesPaths(routes, baseUrl);
|
||||
|
@ -157,6 +158,7 @@ async function createSiteProps(
|
|||
siteConfig,
|
||||
siteConfigPath,
|
||||
siteMetadata,
|
||||
siteVersion,
|
||||
siteDir,
|
||||
outDir,
|
||||
baseUrl,
|
||||
|
@ -181,7 +183,7 @@ async function createSiteFiles({
|
|||
site: Site;
|
||||
globalData: GlobalData;
|
||||
}) {
|
||||
return PerfLogger.async('Load - createSiteFiles', async () => {
|
||||
return PerfLogger.async('Create site files', async () => {
|
||||
const {
|
||||
props: {
|
||||
plugins,
|
||||
|
@ -194,7 +196,7 @@ async function createSiteFiles({
|
|||
baseUrl,
|
||||
},
|
||||
} = site;
|
||||
const clientModules = loadClientModules(plugins);
|
||||
const clientModules = getAllClientModules(plugins);
|
||||
await generateSiteFiles({
|
||||
generatedFilesDir,
|
||||
clientModules,
|
||||
|
@ -216,13 +218,11 @@ async function createSiteFiles({
|
|||
* it generates temp files in the `.docusaurus` folder for the bundler.
|
||||
*/
|
||||
export async function loadSite(params: LoadContextParams): Promise<Site> {
|
||||
PerfLogger.start('Load - loadContext');
|
||||
const context = await loadContext(params);
|
||||
PerfLogger.end('Load - loadContext');
|
||||
const context = await PerfLogger.async('Load context', () =>
|
||||
loadContext(params),
|
||||
);
|
||||
|
||||
PerfLogger.start('Load - loadPlugins');
|
||||
const {plugins, routes, globalData} = await loadPlugins(context);
|
||||
PerfLogger.end('Load - loadPlugins');
|
||||
|
||||
const props = await createSiteProps({plugins, routes, globalData, context});
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import type {
|
|||
SiteMetadata,
|
||||
} from '@docusaurus/types';
|
||||
|
||||
async function getPackageJsonVersion(
|
||||
async function loadPackageJsonVersion(
|
||||
packageJsonPath: string,
|
||||
): Promise<string | undefined> {
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
|
@ -24,14 +24,20 @@ async function getPackageJsonVersion(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
async function getPackageJsonName(
|
||||
async function loadPackageJsonName(
|
||||
packageJsonPath: string,
|
||||
): Promise<string | undefined> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
|
||||
return (require(packageJsonPath) as {name?: string}).name;
|
||||
}
|
||||
|
||||
export async function getPluginVersion(
|
||||
export async function loadSiteVersion(
|
||||
siteDir: string,
|
||||
): Promise<string | undefined> {
|
||||
return loadPackageJsonVersion(path.join(siteDir, 'package.json'));
|
||||
}
|
||||
|
||||
export async function loadPluginVersion(
|
||||
pluginPath: string,
|
||||
siteDir: string,
|
||||
): Promise<PluginVersionInformation> {
|
||||
|
@ -52,8 +58,8 @@ export async function getPluginVersion(
|
|||
}
|
||||
return {
|
||||
type: 'package',
|
||||
name: await getPackageJsonName(packageJsonPath),
|
||||
version: await getPackageJsonVersion(packageJsonPath),
|
||||
name: await loadPackageJsonName(packageJsonPath),
|
||||
version: await loadPackageJsonVersion(packageJsonPath),
|
||||
};
|
||||
}
|
||||
potentialPluginPackageJsonDirectory = path.dirname(
|
||||
|
@ -89,18 +95,16 @@ Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?
|
|||
);
|
||||
}
|
||||
|
||||
export async function loadSiteMetadata({
|
||||
export function createSiteMetadata({
|
||||
siteVersion,
|
||||
plugins,
|
||||
siteDir,
|
||||
}: {
|
||||
siteVersion: string | undefined;
|
||||
plugins: LoadedPlugin[];
|
||||
siteDir: string;
|
||||
}): Promise<SiteMetadata> {
|
||||
}): SiteMetadata {
|
||||
const siteMetadata: SiteMetadata = {
|
||||
docusaurusVersion: DOCUSAURUS_VERSION,
|
||||
siteVersion: await getPackageJsonVersion(
|
||||
path.join(siteDir, 'package.json'),
|
||||
),
|
||||
siteVersion,
|
||||
pluginVersions: Object.fromEntries(
|
||||
plugins
|
||||
.filter(({version: {type}}) => type !== 'synthetic')
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
readCodeTranslationFileContent,
|
||||
type WriteTranslationsOptions,
|
||||
localizePluginTranslationFile,
|
||||
getPluginsDefaultCodeTranslationMessages,
|
||||
loadPluginsDefaultCodeTranslationMessages,
|
||||
applyDefaultCodeTranslations,
|
||||
} from '../translations';
|
||||
import type {
|
||||
|
@ -537,7 +537,7 @@ describe('readCodeTranslationFileContent', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getPluginsDefaultCodeTranslationMessages', () => {
|
||||
describe('loadPluginsDefaultCodeTranslationMessages', () => {
|
||||
function createTestPlugin(
|
||||
fn: InitializedPlugin['getDefaultCodeTranslationMessages'],
|
||||
): InitializedPlugin {
|
||||
|
@ -547,14 +547,14 @@ describe('getPluginsDefaultCodeTranslationMessages', () => {
|
|||
it('works for empty plugins', async () => {
|
||||
const plugins: InitializedPlugin[] = [];
|
||||
await expect(
|
||||
getPluginsDefaultCodeTranslationMessages(plugins),
|
||||
loadPluginsDefaultCodeTranslationMessages(plugins),
|
||||
).resolves.toEqual({});
|
||||
});
|
||||
|
||||
it('works for 1 plugin without lifecycle', async () => {
|
||||
const plugins: InitializedPlugin[] = [createTestPlugin(undefined)];
|
||||
await expect(
|
||||
getPluginsDefaultCodeTranslationMessages(plugins),
|
||||
loadPluginsDefaultCodeTranslationMessages(plugins),
|
||||
).resolves.toEqual({});
|
||||
});
|
||||
|
||||
|
@ -566,7 +566,7 @@ describe('getPluginsDefaultCodeTranslationMessages', () => {
|
|||
})),
|
||||
];
|
||||
await expect(
|
||||
getPluginsDefaultCodeTranslationMessages(plugins),
|
||||
loadPluginsDefaultCodeTranslationMessages(plugins),
|
||||
).resolves.toEqual({
|
||||
a: '1',
|
||||
b: '2',
|
||||
|
@ -585,7 +585,7 @@ describe('getPluginsDefaultCodeTranslationMessages', () => {
|
|||
})),
|
||||
];
|
||||
await expect(
|
||||
getPluginsDefaultCodeTranslationMessages(plugins),
|
||||
loadPluginsDefaultCodeTranslationMessages(plugins),
|
||||
).resolves.toEqual({
|
||||
a: '1',
|
||||
b: '2',
|
||||
|
@ -613,7 +613,7 @@ describe('getPluginsDefaultCodeTranslationMessages', () => {
|
|||
createTestPlugin(undefined),
|
||||
];
|
||||
await expect(
|
||||
getPluginsDefaultCodeTranslationMessages(plugins),
|
||||
loadPluginsDefaultCodeTranslationMessages(plugins),
|
||||
).resolves.toEqual({
|
||||
// merge, last plugin wins
|
||||
b: '2',
|
||||
|
|
|
@ -20,6 +20,7 @@ import type {
|
|||
TranslationFile,
|
||||
CodeTranslations,
|
||||
InitializedPlugin,
|
||||
LoadedPlugin,
|
||||
} from '@docusaurus/types';
|
||||
|
||||
export type WriteTranslationsOptions = {
|
||||
|
@ -242,17 +243,33 @@ export async function localizePluginTranslationFile({
|
|||
return translationFile;
|
||||
}
|
||||
|
||||
export async function getPluginsDefaultCodeTranslationMessages(
|
||||
export function mergeCodeTranslations(
|
||||
codeTranslations: CodeTranslations[],
|
||||
): CodeTranslations {
|
||||
return codeTranslations.reduce(
|
||||
(allCodeTranslations, current) => ({
|
||||
...allCodeTranslations,
|
||||
...current,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
export async function loadPluginsDefaultCodeTranslationMessages(
|
||||
plugins: InitializedPlugin[],
|
||||
): Promise<CodeTranslations> {
|
||||
const pluginsMessages = await Promise.all(
|
||||
plugins.map((plugin) => plugin.getDefaultCodeTranslationMessages?.() ?? {}),
|
||||
);
|
||||
return mergeCodeTranslations(pluginsMessages);
|
||||
}
|
||||
|
||||
return pluginsMessages.reduce(
|
||||
(allMessages, pluginMessages) => ({...allMessages, ...pluginMessages}),
|
||||
{},
|
||||
);
|
||||
export function getPluginsDefaultCodeTranslations({
|
||||
plugins,
|
||||
}: {
|
||||
plugins: LoadedPlugin[];
|
||||
}): CodeTranslations {
|
||||
return mergeCodeTranslations(plugins.map((p) => p.defaultCodeTranslations));
|
||||
}
|
||||
|
||||
export function applyDefaultCodeTranslations({
|
||||
|
|
|
@ -47,12 +47,11 @@ export async function loadAppRenderer({
|
|||
}: {
|
||||
serverBundlePath: string;
|
||||
}): Promise<AppRenderer> {
|
||||
console.log(`SSG - Load server bundle`);
|
||||
PerfLogger.start(`SSG - Load server bundle`);
|
||||
const source = await fs.readFile(serverBundlePath);
|
||||
PerfLogger.end(`SSG - Load server bundle`);
|
||||
const source = await PerfLogger.async(`Load server bundle`, () =>
|
||||
fs.readFile(serverBundlePath),
|
||||
);
|
||||
PerfLogger.log(
|
||||
`SSG - Server bundle size = ${(source.length / 1024000).toFixed(3)} MB`,
|
||||
`Server bundle size = ${(source.length / 1024000).toFixed(3)} MB`,
|
||||
);
|
||||
|
||||
const filename = path.basename(serverBundlePath);
|
||||
|
@ -69,14 +68,16 @@ export async function loadAppRenderer({
|
|||
require: createRequire(serverBundlePath),
|
||||
};
|
||||
|
||||
PerfLogger.start(`SSG - Evaluate server bundle`);
|
||||
const serverEntry = evaluate(
|
||||
const serverEntry = await PerfLogger.async(
|
||||
`Evaluate server bundle`,
|
||||
() =>
|
||||
evaluate(
|
||||
source,
|
||||
/* filename: */ filename,
|
||||
/* scope: */ globals,
|
||||
/* includeGlobals: */ true,
|
||||
) as {default?: AppRenderer};
|
||||
PerfLogger.end(`SSG - Evaluate server bundle`);
|
||||
) as {default?: AppRenderer},
|
||||
);
|
||||
|
||||
if (!serverEntry?.default || typeof serverEntry.default !== 'function') {
|
||||
throw new Error(
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
import {AsyncLocalStorage} from 'async_hooks';
|
||||
import logger from '@docusaurus/logger';
|
||||
|
||||
// For now this is a private env variable we use internally
|
||||
|
@ -17,6 +18,16 @@ const Thresholds = {
|
|||
red: 1000,
|
||||
};
|
||||
|
||||
const PerfPrefix = logger.yellow(`[PERF] `);
|
||||
|
||||
// This is what enables to "see the parent stack" for each log
|
||||
// Parent1 > Parent2 > Parent3 > child trace
|
||||
const ParentPrefix = new AsyncLocalStorage<string>();
|
||||
function applyParentPrefix(label: string) {
|
||||
const parentPrefix = ParentPrefix.getStore();
|
||||
return parentPrefix ? `${parentPrefix} > ${label}` : label;
|
||||
}
|
||||
|
||||
type PerfLoggerAPI = {
|
||||
start: (label: string) => void;
|
||||
end: (label: string) => void;
|
||||
|
@ -38,8 +49,6 @@ function createPerfLogger(): PerfLoggerAPI {
|
|||
};
|
||||
}
|
||||
|
||||
const prefix = logger.yellow(`[PERF] `);
|
||||
|
||||
const formatDuration = (duration: number): string => {
|
||||
if (duration > Thresholds.red) {
|
||||
return logger.red(`${(duration / 1000).toFixed(2)} seconds!`);
|
||||
|
@ -54,7 +63,7 @@ function createPerfLogger(): PerfLoggerAPI {
|
|||
if (duration < Thresholds.min) {
|
||||
return;
|
||||
}
|
||||
console.log(`${prefix + label} - ${formatDuration(duration)}`);
|
||||
console.log(`${PerfPrefix + label} - ${formatDuration(duration)}`);
|
||||
};
|
||||
|
||||
const start: PerfLoggerAPI['start'] = (label) => performance.mark(label);
|
||||
|
@ -62,18 +71,18 @@ function createPerfLogger(): PerfLoggerAPI {
|
|||
const end: PerfLoggerAPI['end'] = (label) => {
|
||||
const {duration} = performance.measure(label);
|
||||
performance.clearMarks(label);
|
||||
logDuration(label, duration);
|
||||
logDuration(applyParentPrefix(label), duration);
|
||||
};
|
||||
|
||||
const log: PerfLoggerAPI['log'] = (label: string) =>
|
||||
console.log(prefix + label);
|
||||
console.log(PerfPrefix + applyParentPrefix(label));
|
||||
|
||||
const async: PerfLoggerAPI['async'] = async (label, asyncFn) => {
|
||||
start(label);
|
||||
const finalLabel = applyParentPrefix(label);
|
||||
const before = performance.now();
|
||||
const result = await asyncFn();
|
||||
const result = await ParentPrefix.run(finalLabel, () => asyncFn());
|
||||
const duration = performance.now() - before;
|
||||
logDuration(label, duration);
|
||||
logDuration(finalLabel, duration);
|
||||
return result;
|
||||
};
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ architecting
|
|||
Astro
|
||||
atrule
|
||||
Autoconverted
|
||||
autofix
|
||||
Autogen
|
||||
autogen
|
||||
autogenerating
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue