mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-01 07:49:43 +02:00
refactor(core): improve dev perf, fine-grained site reloads - part2 (#9968)
This commit is contained in:
parent
91f93656d8
commit
93a09ea086
10 changed files with 707 additions and 402 deletions
2
packages/docusaurus-types/src/plugin.d.ts
vendored
2
packages/docusaurus-types/src/plugin.d.ts
vendored
|
@ -183,6 +183,8 @@ export type InitializedPlugin = Plugin & {
|
|||
|
||||
export type LoadedPlugin = InitializedPlugin & {
|
||||
readonly content: unknown;
|
||||
readonly globalData: unknown;
|
||||
readonly routes: RouteConfig[];
|
||||
};
|
||||
|
||||
export type PluginModule<Content = unknown> = {
|
||||
|
|
|
@ -37,12 +37,14 @@ exports[`load loads props for site with custom i18n path 1`] = `
|
|||
{
|
||||
"content": undefined,
|
||||
"getClientModules": [Function],
|
||||
"globalData": undefined,
|
||||
"injectHtmlTags": [Function],
|
||||
"name": "docusaurus-bootstrap-plugin",
|
||||
"options": {
|
||||
"id": "default",
|
||||
},
|
||||
"path": "<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site",
|
||||
"routes": [],
|
||||
"version": {
|
||||
"type": "synthetic",
|
||||
},
|
||||
|
@ -50,11 +52,13 @@ exports[`load loads props for site with custom i18n path 1`] = `
|
|||
{
|
||||
"configureWebpack": [Function],
|
||||
"content": undefined,
|
||||
"globalData": undefined,
|
||||
"name": "docusaurus-mdx-fallback-plugin",
|
||||
"options": {
|
||||
"id": "default",
|
||||
},
|
||||
"path": ".",
|
||||
"routes": [],
|
||||
"version": {
|
||||
"type": "synthetic",
|
||||
},
|
||||
|
|
|
@ -7,15 +7,10 @@
|
|||
|
||||
import path from 'path';
|
||||
import {fromPartial} from '@total-typescript/shoehorn';
|
||||
import {loadPlugins, mergeGlobalData} from '../plugins';
|
||||
import type {
|
||||
GlobalData,
|
||||
LoadContext,
|
||||
Plugin,
|
||||
PluginConfig,
|
||||
} from '@docusaurus/types';
|
||||
import {loadPlugins, reloadPlugin} from '../plugins';
|
||||
import type {LoadContext, Plugin, PluginConfig} from '@docusaurus/types';
|
||||
|
||||
function testLoad({
|
||||
async function testLoad({
|
||||
plugins,
|
||||
themes,
|
||||
}: {
|
||||
|
@ -39,7 +34,9 @@ function testLoad({
|
|||
},
|
||||
});
|
||||
|
||||
return loadPlugins(context);
|
||||
const result = await loadPlugins(context);
|
||||
|
||||
return {context, ...result};
|
||||
}
|
||||
|
||||
const SyntheticPluginNames = [
|
||||
|
@ -50,7 +47,7 @@ const SyntheticPluginNames = [
|
|||
async function testPlugin<Content = unknown>(
|
||||
pluginConfig: PluginConfig<Content>,
|
||||
) {
|
||||
const {plugins, routes, globalData} = await testLoad({
|
||||
const {context, plugins, routes, globalData} = await testLoad({
|
||||
plugins: [pluginConfig],
|
||||
themes: [],
|
||||
});
|
||||
|
@ -62,204 +59,9 @@ async function testPlugin<Content = unknown>(
|
|||
const plugin = nonSyntheticPlugins[0]!;
|
||||
expect(plugin).toBeDefined();
|
||||
|
||||
return {plugin, routes, globalData};
|
||||
return {context, plugin, routes, globalData};
|
||||
}
|
||||
|
||||
describe('mergeGlobalData', () => {
|
||||
it('no global data', () => {
|
||||
expect(mergeGlobalData()).toEqual({});
|
||||
});
|
||||
|
||||
it('1 global data', () => {
|
||||
const globalData: GlobalData = {
|
||||
plugin: {
|
||||
default: {someData: 'val'},
|
||||
},
|
||||
};
|
||||
expect(mergeGlobalData(globalData)).toEqual(globalData);
|
||||
});
|
||||
|
||||
it('1 global data - primitive value', () => {
|
||||
// For retro-compatibility we allow primitive values to be kept as is
|
||||
// Not sure anyone is using primitive global data though...
|
||||
const globalData: GlobalData = {
|
||||
plugin: {
|
||||
default: 42,
|
||||
},
|
||||
};
|
||||
expect(mergeGlobalData(globalData)).toEqual(globalData);
|
||||
});
|
||||
|
||||
it('3 distinct plugins global data', () => {
|
||||
const globalData1: GlobalData = {
|
||||
plugin1: {
|
||||
default: {someData1: 'val1'},
|
||||
},
|
||||
};
|
||||
const globalData2: GlobalData = {
|
||||
plugin2: {
|
||||
default: {someData2: 'val2'},
|
||||
},
|
||||
};
|
||||
const globalData3: GlobalData = {
|
||||
plugin3: {
|
||||
default: {someData3: 'val3'},
|
||||
},
|
||||
};
|
||||
|
||||
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
|
||||
plugin1: {
|
||||
default: {someData1: 'val1'},
|
||||
},
|
||||
plugin2: {
|
||||
default: {someData2: 'val2'},
|
||||
},
|
||||
plugin3: {
|
||||
default: {someData3: 'val3'},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('3 plugin instances of same plugin', () => {
|
||||
const globalData1: GlobalData = {
|
||||
plugin: {
|
||||
id1: {someData1: 'val1'},
|
||||
},
|
||||
};
|
||||
const globalData2: GlobalData = {
|
||||
plugin: {
|
||||
id2: {someData2: 'val2'},
|
||||
},
|
||||
};
|
||||
const globalData3: GlobalData = {
|
||||
plugin: {
|
||||
id3: {someData3: 'val3'},
|
||||
},
|
||||
};
|
||||
|
||||
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
|
||||
plugin: {
|
||||
id1: {someData1: 'val1'},
|
||||
id2: {someData2: 'val2'},
|
||||
id3: {someData3: 'val3'},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('3 times the same plugin', () => {
|
||||
const globalData1: GlobalData = {
|
||||
plugin: {
|
||||
id: {someData1: 'val1', shared: 'shared1'},
|
||||
},
|
||||
};
|
||||
const globalData2: GlobalData = {
|
||||
plugin: {
|
||||
id: {someData2: 'val2', shared: 'shared2'},
|
||||
},
|
||||
};
|
||||
const globalData3: GlobalData = {
|
||||
plugin: {
|
||||
id: {someData3: 'val3', shared: 'shared3'},
|
||||
},
|
||||
};
|
||||
|
||||
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
|
||||
plugin: {
|
||||
id: {
|
||||
someData1: 'val1',
|
||||
someData2: 'val2',
|
||||
someData3: 'val3',
|
||||
shared: 'shared3',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('3 times same plugin - including primitive values', () => {
|
||||
// Very unlikely to happen, but we can't merge primitive values together
|
||||
// Since we use Object.assign(), the primitive values are simply ignored
|
||||
const globalData1: GlobalData = {
|
||||
plugin: {
|
||||
default: 42,
|
||||
},
|
||||
};
|
||||
const globalData2: GlobalData = {
|
||||
plugin: {
|
||||
default: {hey: 'val'},
|
||||
},
|
||||
};
|
||||
const globalData3: GlobalData = {
|
||||
plugin: {
|
||||
default: 84,
|
||||
},
|
||||
};
|
||||
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
|
||||
plugin: {
|
||||
default: {hey: 'val'},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('real world case', () => {
|
||||
const globalData1: GlobalData = {
|
||||
plugin1: {
|
||||
id1: {someData1: 'val1', shared: 'globalData1'},
|
||||
},
|
||||
};
|
||||
const globalData2: GlobalData = {
|
||||
plugin1: {
|
||||
id1: {someData2: 'val2', shared: 'globalData2'},
|
||||
},
|
||||
};
|
||||
|
||||
const globalData3: GlobalData = {
|
||||
plugin1: {
|
||||
id2: {someData3: 'val3', shared: 'globalData3'},
|
||||
},
|
||||
};
|
||||
|
||||
const globalData4: GlobalData = {
|
||||
plugin2: {
|
||||
id1: {someData1: 'val1', shared: 'globalData4'},
|
||||
},
|
||||
};
|
||||
const globalData5: GlobalData = {
|
||||
plugin2: {
|
||||
id2: {someData1: 'val1', shared: 'globalData5'},
|
||||
},
|
||||
};
|
||||
|
||||
const globalData6: GlobalData = {
|
||||
plugin3: {
|
||||
id1: {someData1: 'val1', shared: 'globalData6'},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
mergeGlobalData(
|
||||
globalData1,
|
||||
globalData2,
|
||||
globalData3,
|
||||
globalData4,
|
||||
globalData5,
|
||||
globalData6,
|
||||
),
|
||||
).toEqual({
|
||||
plugin1: {
|
||||
id1: {someData1: 'val1', someData2: 'val2', shared: 'globalData2'},
|
||||
id2: {someData3: 'val3', shared: 'globalData3'},
|
||||
},
|
||||
plugin2: {
|
||||
id1: {someData1: 'val1', shared: 'globalData4'},
|
||||
id2: {someData1: 'val1', shared: 'globalData5'},
|
||||
},
|
||||
plugin3: {
|
||||
id1: {someData1: 'val1', shared: 'globalData6'},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadPlugins', () => {
|
||||
it('registers default synthetic plugins', async () => {
|
||||
const {plugins, routes, globalData} = await testLoad({
|
||||
|
@ -526,3 +328,272 @@ describe('loadPlugins', () => {
|
|||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reloadPlugin', () => {
|
||||
it('can reload a single complex plugin with same content', async () => {
|
||||
const plugin: PluginConfig = () => ({
|
||||
name: 'plugin-name',
|
||||
contentLoaded({actions}) {
|
||||
actions.addRoute({
|
||||
path: '/contentLoadedRouteParent',
|
||||
component: 'Comp',
|
||||
routes: [
|
||||
{path: '/contentLoadedRouteParent/child', component: 'Comp'},
|
||||
],
|
||||
});
|
||||
actions.addRoute({
|
||||
path: '/contentLoadedRouteSingle',
|
||||
component: 'Comp',
|
||||
});
|
||||
actions.setGlobalData({
|
||||
globalContentLoaded: 'val1',
|
||||
globalOverridden: 'initial-value',
|
||||
});
|
||||
},
|
||||
allContentLoaded({actions}) {
|
||||
actions.addRoute({
|
||||
path: '/allContentLoadedRouteParent',
|
||||
component: 'Comp',
|
||||
routes: [
|
||||
{path: '/allContentLoadedRouteParent/child', component: 'Comp'},
|
||||
],
|
||||
});
|
||||
actions.addRoute({
|
||||
path: '/allContentLoadedRouteSingle',
|
||||
component: 'Comp',
|
||||
});
|
||||
actions.setGlobalData({
|
||||
globalAllContentLoaded: 'val2',
|
||||
globalOverridden: 'override-value',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const loadResult = await testLoad({
|
||||
plugins: [plugin],
|
||||
themes: [],
|
||||
});
|
||||
const reloadResult = await reloadPlugin({
|
||||
context: loadResult.context,
|
||||
plugins: loadResult.plugins,
|
||||
pluginIdentifier: {name: 'plugin-name', id: 'default'},
|
||||
});
|
||||
|
||||
expect(loadResult.routes).toEqual(reloadResult.routes);
|
||||
expect(loadResult.globalData).toEqual(reloadResult.globalData);
|
||||
expect(reloadResult.routes).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"component": "Comp",
|
||||
"context": {
|
||||
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name/default/plugin-route-context-module-100.json",
|
||||
},
|
||||
"path": "/allContentLoadedRouteSingle/",
|
||||
},
|
||||
{
|
||||
"component": "Comp",
|
||||
"context": {
|
||||
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name/default/plugin-route-context-module-100.json",
|
||||
},
|
||||
"path": "/contentLoadedRouteSingle/",
|
||||
},
|
||||
{
|
||||
"component": "Comp",
|
||||
"context": {
|
||||
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name/default/plugin-route-context-module-100.json",
|
||||
},
|
||||
"path": "/allContentLoadedRouteParent/",
|
||||
"routes": [
|
||||
{
|
||||
"component": "Comp",
|
||||
"path": "/allContentLoadedRouteParent/child/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"component": "Comp",
|
||||
"context": {
|
||||
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name/default/plugin-route-context-module-100.json",
|
||||
},
|
||||
"path": "/contentLoadedRouteParent/",
|
||||
"routes": [
|
||||
{
|
||||
"component": "Comp",
|
||||
"path": "/contentLoadedRouteParent/child/",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(reloadResult.globalData).toMatchInlineSnapshot(`
|
||||
{
|
||||
"plugin-name": {
|
||||
"default": {
|
||||
"globalAllContentLoaded": "val2",
|
||||
"globalContentLoaded": "val1",
|
||||
"globalOverridden": "override-value",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('can reload plugins in real-world setup', async () => {
|
||||
let isPlugin1Reload = false;
|
||||
|
||||
const plugin1: PluginConfig = () => ({
|
||||
name: 'plugin-name-1',
|
||||
contentLoaded({actions}) {
|
||||
actions.addRoute({
|
||||
path: isPlugin1Reload
|
||||
? '/contentLoaded-route-reload'
|
||||
: '/contentLoaded-route-initial',
|
||||
component: 'Comp',
|
||||
});
|
||||
actions.setGlobalData({
|
||||
contentLoadedVal: isPlugin1Reload
|
||||
? 'contentLoaded-val-reload'
|
||||
: 'contentLoaded-val-initial',
|
||||
});
|
||||
},
|
||||
allContentLoaded({actions}) {
|
||||
actions.addRoute({
|
||||
path: isPlugin1Reload
|
||||
? '/allContentLoaded-route-reload'
|
||||
: '/allContentLoaded-route-initial',
|
||||
component: 'Comp',
|
||||
});
|
||||
actions.setGlobalData({
|
||||
allContentLoadedVal: isPlugin1Reload
|
||||
? 'allContentLoaded-val-reload'
|
||||
: 'allContentLoaded-val-initial',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const plugin2: PluginConfig = () => ({
|
||||
name: 'plugin-name-2',
|
||||
contentLoaded({actions}) {
|
||||
actions.addRoute({
|
||||
path: '/plugin-2-route',
|
||||
component: 'Comp',
|
||||
});
|
||||
actions.setGlobalData({plugin2Val: 'val'});
|
||||
},
|
||||
});
|
||||
|
||||
const loadResult = await testLoad({
|
||||
plugins: [plugin1, plugin2],
|
||||
themes: [],
|
||||
});
|
||||
|
||||
isPlugin1Reload = true;
|
||||
|
||||
const reloadResult = await reloadPlugin({
|
||||
context: loadResult.context,
|
||||
plugins: loadResult.plugins,
|
||||
pluginIdentifier: {name: 'plugin-name-1', id: 'default'},
|
||||
});
|
||||
|
||||
expect(loadResult.routes).not.toEqual(reloadResult.routes);
|
||||
expect(loadResult.globalData).not.toEqual(reloadResult.globalData);
|
||||
expect(loadResult.routes).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"component": "Comp",
|
||||
"context": {
|
||||
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-1/default/plugin-route-context-module-100.json",
|
||||
},
|
||||
"path": "/allContentLoaded-route-initial/",
|
||||
},
|
||||
{
|
||||
"component": "Comp",
|
||||
"context": {
|
||||
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-1/default/plugin-route-context-module-100.json",
|
||||
},
|
||||
"path": "/contentLoaded-route-initial/",
|
||||
},
|
||||
{
|
||||
"component": "Comp",
|
||||
"context": {
|
||||
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-2/default/plugin-route-context-module-100.json",
|
||||
},
|
||||
"path": "/plugin-2-route/",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(loadResult.globalData).toMatchInlineSnapshot(`
|
||||
{
|
||||
"plugin-name-1": {
|
||||
"default": {
|
||||
"allContentLoadedVal": "allContentLoaded-val-initial",
|
||||
"contentLoadedVal": "contentLoaded-val-initial",
|
||||
},
|
||||
},
|
||||
"plugin-name-2": {
|
||||
"default": {
|
||||
"plugin2Val": "val",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(reloadResult.routes).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"component": "Comp",
|
||||
"context": {
|
||||
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-1/default/plugin-route-context-module-100.json",
|
||||
},
|
||||
"path": "/allContentLoaded-route-reload/",
|
||||
},
|
||||
{
|
||||
"component": "Comp",
|
||||
"context": {
|
||||
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-1/default/plugin-route-context-module-100.json",
|
||||
},
|
||||
"path": "/contentLoaded-route-reload/",
|
||||
},
|
||||
{
|
||||
"component": "Comp",
|
||||
"context": {
|
||||
"plugin": "<PROJECT_ROOT>/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/.docusaurus/plugin-name-2/default/plugin-route-context-module-100.json",
|
||||
},
|
||||
"path": "/plugin-2-route/",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(reloadResult.globalData).toMatchInlineSnapshot(`
|
||||
{
|
||||
"plugin-name-1": {
|
||||
"default": {
|
||||
"allContentLoadedVal": "allContentLoaded-val-reload",
|
||||
"contentLoadedVal": "contentLoaded-val-reload",
|
||||
},
|
||||
},
|
||||
"plugin-name-2": {
|
||||
"default": {
|
||||
"plugin2Val": "val",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
// Trying to reload again one plugin or the other should give
|
||||
// the same result because the plugin content doesn't change
|
||||
const reloadResult2 = await reloadPlugin({
|
||||
context: loadResult.context,
|
||||
plugins: reloadResult.plugins,
|
||||
pluginIdentifier: {name: 'plugin-name-1', id: 'default'},
|
||||
});
|
||||
expect(reloadResult2.routes).toEqual(reloadResult.routes);
|
||||
expect(reloadResult2.globalData).toEqual(reloadResult.globalData);
|
||||
|
||||
const reloadResult3 = await reloadPlugin({
|
||||
context: loadResult.context,
|
||||
plugins: reloadResult2.plugins,
|
||||
pluginIdentifier: {name: 'plugin-name-2', id: 'default'},
|
||||
});
|
||||
expect(reloadResult3.routes).toEqual(reloadResult.routes);
|
||||
expect(reloadResult3.globalData).toEqual(reloadResult.globalData);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {mergeGlobalData} from '../pluginsUtils';
|
||||
import type {GlobalData} from '@docusaurus/types';
|
||||
|
||||
describe('mergeGlobalData', () => {
|
||||
it('no global data', () => {
|
||||
expect(mergeGlobalData()).toEqual({});
|
||||
});
|
||||
|
||||
it('1 global data', () => {
|
||||
const globalData: GlobalData = {
|
||||
plugin: {
|
||||
default: {someData: 'val'},
|
||||
},
|
||||
};
|
||||
expect(mergeGlobalData(globalData)).toEqual(globalData);
|
||||
});
|
||||
|
||||
it('1 global data - primitive value', () => {
|
||||
// For retro-compatibility we allow primitive values to be kept as is
|
||||
// Not sure anyone is using primitive global data though...
|
||||
const globalData: GlobalData = {
|
||||
plugin: {
|
||||
default: 42,
|
||||
},
|
||||
};
|
||||
expect(mergeGlobalData(globalData)).toEqual(globalData);
|
||||
});
|
||||
|
||||
it('3 distinct plugins global data', () => {
|
||||
const globalData1: GlobalData = {
|
||||
plugin1: {
|
||||
default: {someData1: 'val1'},
|
||||
},
|
||||
};
|
||||
const globalData2: GlobalData = {
|
||||
plugin2: {
|
||||
default: {someData2: 'val2'},
|
||||
},
|
||||
};
|
||||
const globalData3: GlobalData = {
|
||||
plugin3: {
|
||||
default: {someData3: 'val3'},
|
||||
},
|
||||
};
|
||||
|
||||
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
|
||||
plugin1: {
|
||||
default: {someData1: 'val1'},
|
||||
},
|
||||
plugin2: {
|
||||
default: {someData2: 'val2'},
|
||||
},
|
||||
plugin3: {
|
||||
default: {someData3: 'val3'},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('3 plugin instances of same plugin', () => {
|
||||
const globalData1: GlobalData = {
|
||||
plugin: {
|
||||
id1: {someData1: 'val1'},
|
||||
},
|
||||
};
|
||||
const globalData2: GlobalData = {
|
||||
plugin: {
|
||||
id2: {someData2: 'val2'},
|
||||
},
|
||||
};
|
||||
const globalData3: GlobalData = {
|
||||
plugin: {
|
||||
id3: {someData3: 'val3'},
|
||||
},
|
||||
};
|
||||
|
||||
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
|
||||
plugin: {
|
||||
id1: {someData1: 'val1'},
|
||||
id2: {someData2: 'val2'},
|
||||
id3: {someData3: 'val3'},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('3 times the same plugin', () => {
|
||||
const globalData1: GlobalData = {
|
||||
plugin: {
|
||||
id: {someData1: 'val1', shared: 'shared1'},
|
||||
},
|
||||
};
|
||||
const globalData2: GlobalData = {
|
||||
plugin: {
|
||||
id: {someData2: 'val2', shared: 'shared2'},
|
||||
},
|
||||
};
|
||||
const globalData3: GlobalData = {
|
||||
plugin: {
|
||||
id: {someData3: 'val3', shared: 'shared3'},
|
||||
},
|
||||
};
|
||||
|
||||
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
|
||||
plugin: {
|
||||
id: {
|
||||
someData1: 'val1',
|
||||
someData2: 'val2',
|
||||
someData3: 'val3',
|
||||
shared: 'shared3',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('3 times same plugin - including primitive values', () => {
|
||||
// Very unlikely to happen, but we can't merge primitive values together
|
||||
// Since we use Object.assign(), the primitive values are simply ignored
|
||||
const globalData1: GlobalData = {
|
||||
plugin: {
|
||||
default: 42,
|
||||
},
|
||||
};
|
||||
const globalData2: GlobalData = {
|
||||
plugin: {
|
||||
default: {hey: 'val'},
|
||||
},
|
||||
};
|
||||
const globalData3: GlobalData = {
|
||||
plugin: {
|
||||
default: 84,
|
||||
},
|
||||
};
|
||||
expect(mergeGlobalData(globalData1, globalData2, globalData3)).toEqual({
|
||||
plugin: {
|
||||
default: {hey: 'val'},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('real world case', () => {
|
||||
const globalData1: GlobalData = {
|
||||
plugin1: {
|
||||
id1: {someData1: 'val1', shared: 'globalData1'},
|
||||
},
|
||||
};
|
||||
const globalData2: GlobalData = {
|
||||
plugin1: {
|
||||
id1: {someData2: 'val2', shared: 'globalData2'},
|
||||
},
|
||||
};
|
||||
|
||||
const globalData3: GlobalData = {
|
||||
plugin1: {
|
||||
id2: {someData3: 'val3', shared: 'globalData3'},
|
||||
},
|
||||
};
|
||||
|
||||
const globalData4: GlobalData = {
|
||||
plugin2: {
|
||||
id1: {someData1: 'val1', shared: 'globalData4'},
|
||||
},
|
||||
};
|
||||
const globalData5: GlobalData = {
|
||||
plugin2: {
|
||||
id2: {someData1: 'val1', shared: 'globalData5'},
|
||||
},
|
||||
};
|
||||
|
||||
const globalData6: GlobalData = {
|
||||
plugin3: {
|
||||
id1: {someData1: 'val1', shared: 'globalData6'},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
mergeGlobalData(
|
||||
globalData1,
|
||||
globalData2,
|
||||
globalData3,
|
||||
globalData4,
|
||||
globalData5,
|
||||
globalData6,
|
||||
),
|
||||
).toEqual({
|
||||
plugin1: {
|
||||
id1: {someData1: 'val1', someData2: 'val2', shared: 'globalData2'},
|
||||
id2: {someData3: 'val3', shared: 'globalData3'},
|
||||
},
|
||||
plugin2: {
|
||||
id1: {someData1: 'val1', shared: 'globalData4'},
|
||||
id2: {someData1: 'val1', shared: 'globalData5'},
|
||||
},
|
||||
plugin3: {
|
||||
id1: {someData1: 'val1', shared: 'globalData6'},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -9,7 +9,7 @@ import path from 'path';
|
|||
import {docuHash, generate} from '@docusaurus/utils';
|
||||
import {applyRouteTrailingSlash} from './routeConfig';
|
||||
import type {
|
||||
LoadedPlugin,
|
||||
InitializedPlugin,
|
||||
PluginContentLoadedActions,
|
||||
PluginRouteContext,
|
||||
RouteConfig,
|
||||
|
@ -31,7 +31,7 @@ export async function createPluginActionsUtils({
|
|||
baseUrl,
|
||||
trailingSlash,
|
||||
}: {
|
||||
plugin: LoadedPlugin;
|
||||
plugin: InitializedPlugin;
|
||||
generatedFilesDir: string;
|
||||
baseUrl: string;
|
||||
trailingSlash: boolean | undefined;
|
||||
|
|
|
@ -5,14 +5,19 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import logger from '@docusaurus/logger';
|
||||
import {initPlugins} from './init';
|
||||
import {createBootstrapPlugin, createMDXFallbackPlugin} from './synthetic';
|
||||
import {localizePluginTranslationFile} from '../translations/translations';
|
||||
import {sortRoutes} from './routeConfig';
|
||||
import {PerfLogger} from '../../utils';
|
||||
import {createPluginActionsUtils} from './actions';
|
||||
import {
|
||||
aggregateAllContent,
|
||||
aggregateGlobalData,
|
||||
aggregateRoutes,
|
||||
getPluginByIdentifier,
|
||||
mergeGlobalData,
|
||||
} from './pluginsUtils';
|
||||
import type {
|
||||
LoadContext,
|
||||
RouteConfig,
|
||||
|
@ -23,17 +28,17 @@ import type {
|
|||
InitializedPlugin,
|
||||
} from '@docusaurus/types';
|
||||
|
||||
async function translatePlugin({
|
||||
async function translatePluginContent({
|
||||
plugin,
|
||||
content,
|
||||
context,
|
||||
}: {
|
||||
plugin: LoadedPlugin;
|
||||
plugin: InitializedPlugin;
|
||||
content: unknown;
|
||||
context: LoadContext;
|
||||
}): Promise<LoadedPlugin> {
|
||||
const {content} = plugin;
|
||||
|
||||
}): Promise<unknown> {
|
||||
const rawTranslationFiles =
|
||||
(await plugin.getTranslationFiles?.({content: plugin.content})) ?? [];
|
||||
(await plugin.getTranslationFiles?.({content})) ?? [];
|
||||
|
||||
const translationFiles = await Promise.all(
|
||||
rawTranslationFiles.map((translationFile) =>
|
||||
|
@ -58,10 +63,10 @@ async function translatePlugin({
|
|||
// translate its own slice of theme config and should make no assumptions
|
||||
// about other plugins' keys, so this is safe to run in parallel.
|
||||
Object.assign(context.siteConfig.themeConfig, translatedThemeConfigSlice);
|
||||
return {...plugin, content: translatedContent};
|
||||
return translatedContent;
|
||||
}
|
||||
|
||||
async function executePluginLoadContent({
|
||||
async function executePluginContentLoading({
|
||||
plugin,
|
||||
context,
|
||||
}: {
|
||||
|
@ -69,65 +74,40 @@ async function executePluginLoadContent({
|
|||
context: LoadContext;
|
||||
}): Promise<LoadedPlugin> {
|
||||
return PerfLogger.async(
|
||||
`Plugin - loadContent - ${plugin.name}@${plugin.options.id}`,
|
||||
`Plugins - single plugin content loading - ${plugin.name}@${plugin.options.id}`,
|
||||
async () => {
|
||||
const content = await plugin.loadContent?.();
|
||||
const loadedPlugin: LoadedPlugin = {...plugin, content};
|
||||
return translatePlugin({plugin: loadedPlugin, context});
|
||||
},
|
||||
);
|
||||
}
|
||||
let content = await plugin.loadContent?.();
|
||||
|
||||
async function executePluginsLoadContent({
|
||||
plugins,
|
||||
context,
|
||||
}: {
|
||||
plugins: InitializedPlugin[];
|
||||
context: LoadContext;
|
||||
}) {
|
||||
return PerfLogger.async(`Plugins - loadContent`, () =>
|
||||
Promise.all(
|
||||
plugins.map((plugin) => executePluginLoadContent({plugin, context})),
|
||||
),
|
||||
);
|
||||
}
|
||||
content = await translatePluginContent({
|
||||
plugin,
|
||||
content,
|
||||
context,
|
||||
});
|
||||
|
||||
function aggregateAllContent(loadedPlugins: LoadedPlugin[]): AllContent {
|
||||
return _.chain(loadedPlugins)
|
||||
.groupBy((item) => item.name)
|
||||
.mapValues((nameItems) =>
|
||||
_.chain(nameItems)
|
||||
.groupBy((item) => item.options.id)
|
||||
.mapValues((idItems) => idItems[0]!.content)
|
||||
.value(),
|
||||
)
|
||||
.value();
|
||||
}
|
||||
|
||||
async function executePluginContentLoaded({
|
||||
plugin,
|
||||
context,
|
||||
}: {
|
||||
plugin: LoadedPlugin;
|
||||
context: LoadContext;
|
||||
}): Promise<{routes: RouteConfig[]; globalData: unknown}> {
|
||||
return PerfLogger.async(
|
||||
`Plugins - contentLoaded - ${plugin.name}@${plugin.options.id}`,
|
||||
async () => {
|
||||
if (!plugin.contentLoaded) {
|
||||
return {routes: [], globalData: undefined};
|
||||
return {
|
||||
...plugin,
|
||||
content,
|
||||
routes: [],
|
||||
globalData: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const pluginActionsUtils = await createPluginActionsUtils({
|
||||
plugin,
|
||||
generatedFilesDir: context.generatedFilesDir,
|
||||
baseUrl: context.siteConfig.baseUrl,
|
||||
trailingSlash: context.siteConfig.trailingSlash,
|
||||
});
|
||||
|
||||
await plugin.contentLoaded({
|
||||
content: plugin.content,
|
||||
content,
|
||||
actions: pluginActionsUtils.getActions(),
|
||||
});
|
||||
|
||||
return {
|
||||
...plugin,
|
||||
content,
|
||||
routes: pluginActionsUtils.getRoutes(),
|
||||
globalData: pluginActionsUtils.getGlobalData(),
|
||||
};
|
||||
|
@ -135,6 +115,20 @@ async function executePluginContentLoaded({
|
|||
);
|
||||
}
|
||||
|
||||
async function executeAllPluginsContentLoading({
|
||||
plugins,
|
||||
context,
|
||||
}: {
|
||||
plugins: InitializedPlugin[];
|
||||
context: LoadContext;
|
||||
}): Promise<LoadedPlugin[]> {
|
||||
return PerfLogger.async(`Plugins - all plugins content loading`, () => {
|
||||
return Promise.all(
|
||||
plugins.map((plugin) => executePluginContentLoading({plugin, context})),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function executePluginAllContentLoaded({
|
||||
plugin,
|
||||
context,
|
||||
|
@ -168,49 +162,15 @@ async function executePluginAllContentLoaded({
|
|||
);
|
||||
}
|
||||
|
||||
async function executePluginsContentLoaded({
|
||||
type AllContentLoadedResult = {routes: RouteConfig[]; globalData: GlobalData};
|
||||
|
||||
async function executeAllPluginsAllContentLoaded({
|
||||
plugins,
|
||||
context,
|
||||
}: {
|
||||
plugins: LoadedPlugin[];
|
||||
context: LoadContext;
|
||||
}): Promise<{routes: RouteConfig[]; globalData: GlobalData}> {
|
||||
return PerfLogger.async(`Plugins - contentLoaded`, async () => {
|
||||
const routes: RouteConfig[] = [];
|
||||
const globalData: GlobalData = {};
|
||||
|
||||
await Promise.all(
|
||||
plugins.map(async (plugin) => {
|
||||
const {routes: pluginRoutes, globalData: pluginGlobalData} =
|
||||
await executePluginContentLoaded({
|
||||
plugin,
|
||||
context,
|
||||
});
|
||||
|
||||
routes.push(...pluginRoutes);
|
||||
|
||||
if (pluginGlobalData !== undefined) {
|
||||
globalData[plugin.name] ??= {};
|
||||
globalData[plugin.name]![plugin.options.id] = pluginGlobalData;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// Sort the route config.
|
||||
// This ensures that route with sub routes are always placed last.
|
||||
sortRoutes(routes, context.siteConfig.baseUrl);
|
||||
|
||||
return {routes, globalData};
|
||||
});
|
||||
}
|
||||
|
||||
async function executePluginsAllContentLoaded({
|
||||
plugins,
|
||||
context,
|
||||
}: {
|
||||
plugins: LoadedPlugin[];
|
||||
context: LoadContext;
|
||||
}): Promise<{routes: RouteConfig[]; globalData: GlobalData}> {
|
||||
}): Promise<AllContentLoadedResult> {
|
||||
return PerfLogger.async(`Plugins - allContentLoaded`, async () => {
|
||||
const allContent = aggregateAllContent(plugins);
|
||||
|
||||
|
@ -235,66 +195,37 @@ async function executePluginsAllContentLoaded({
|
|||
}),
|
||||
);
|
||||
|
||||
// Sort the route config.
|
||||
// This ensures that route with sub routes are always placed last.
|
||||
sortRoutes(routes, context.siteConfig.baseUrl);
|
||||
|
||||
return {routes, globalData};
|
||||
});
|
||||
}
|
||||
|
||||
function mergeResults({
|
||||
plugins,
|
||||
allContentLoadedResult,
|
||||
}: {
|
||||
plugins: LoadedPlugin[];
|
||||
allContentLoadedResult: AllContentLoadedResult;
|
||||
}) {
|
||||
const routes: RouteConfig[] = [
|
||||
...aggregateRoutes(plugins),
|
||||
...allContentLoadedResult.routes,
|
||||
];
|
||||
sortRoutes(routes);
|
||||
|
||||
const globalData: GlobalData = mergeGlobalData(
|
||||
aggregateGlobalData(plugins),
|
||||
allContentLoadedResult.globalData,
|
||||
);
|
||||
|
||||
return {routes, globalData};
|
||||
}
|
||||
|
||||
export type LoadPluginsResult = {
|
||||
plugins: LoadedPlugin[];
|
||||
routes: RouteConfig[];
|
||||
globalData: GlobalData;
|
||||
};
|
||||
|
||||
type ContentLoadedResult = {routes: RouteConfig[]; globalData: GlobalData};
|
||||
|
||||
export function mergeGlobalData(...globalDataList: GlobalData[]): GlobalData {
|
||||
const result: GlobalData = {};
|
||||
|
||||
const allPluginIdentifiers: PluginIdentifier[] = globalDataList.flatMap(
|
||||
(gd) =>
|
||||
Object.keys(gd).flatMap((name) =>
|
||||
Object.keys(gd[name]!).map((id) => ({name, id})),
|
||||
),
|
||||
);
|
||||
|
||||
allPluginIdentifiers.forEach(({name, id}) => {
|
||||
const allData = globalDataList
|
||||
.map((gd) => gd?.[name]?.[id])
|
||||
.filter((d) => typeof d !== 'undefined');
|
||||
const mergedData =
|
||||
allData.length === 1 ? allData[0] : Object.assign({}, ...allData);
|
||||
result[name] ??= {};
|
||||
result[name]![id] = mergedData;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function mergeResults({
|
||||
contentLoadedResult,
|
||||
allContentLoadedResult,
|
||||
}: {
|
||||
contentLoadedResult: ContentLoadedResult;
|
||||
allContentLoadedResult: ContentLoadedResult;
|
||||
}): ContentLoadedResult {
|
||||
const routes = [
|
||||
...contentLoadedResult.routes,
|
||||
...allContentLoadedResult.routes,
|
||||
];
|
||||
sortRoutes(routes);
|
||||
|
||||
const globalData = mergeGlobalData(
|
||||
contentLoadedResult.globalData,
|
||||
allContentLoadedResult.globalData,
|
||||
);
|
||||
|
||||
return {routes, globalData};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the plugins and run their lifecycle functions.
|
||||
*/
|
||||
|
@ -307,28 +238,24 @@ export async function loadPlugins(
|
|||
() => initPlugins(context),
|
||||
);
|
||||
|
||||
// TODO probably not the ideal place to hardcode those plugins
|
||||
initializedPlugins.push(
|
||||
createBootstrapPlugin(context),
|
||||
createMDXFallbackPlugin(context),
|
||||
);
|
||||
|
||||
const plugins = await executePluginsLoadContent({
|
||||
const plugins = await executeAllPluginsContentLoading({
|
||||
plugins: initializedPlugins,
|
||||
context,
|
||||
});
|
||||
|
||||
const contentLoadedResult = await executePluginsContentLoaded({
|
||||
plugins,
|
||||
context,
|
||||
});
|
||||
|
||||
const allContentLoadedResult = await executePluginsAllContentLoaded({
|
||||
const allContentLoadedResult = await executeAllPluginsAllContentLoaded({
|
||||
plugins,
|
||||
context,
|
||||
});
|
||||
|
||||
const {routes, globalData} = mergeResults({
|
||||
contentLoadedResult,
|
||||
plugins,
|
||||
allContentLoadedResult,
|
||||
});
|
||||
|
||||
|
@ -336,25 +263,6 @@ export async function loadPlugins(
|
|||
});
|
||||
}
|
||||
|
||||
export function getPluginByIdentifier({
|
||||
plugins,
|
||||
pluginIdentifier,
|
||||
}: {
|
||||
pluginIdentifier: PluginIdentifier;
|
||||
plugins: LoadedPlugin[];
|
||||
}): LoadedPlugin {
|
||||
const plugin = plugins.find(
|
||||
(p) =>
|
||||
p.name === pluginIdentifier.name && p.options.id === pluginIdentifier.id,
|
||||
);
|
||||
if (!plugin) {
|
||||
throw new Error(
|
||||
logger.interpolate`Plugin not found for identifier ${pluginIdentifier.name}@${pluginIdentifier.id}`,
|
||||
);
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
|
||||
export async function reloadPlugin({
|
||||
pluginIdentifier,
|
||||
plugins: previousPlugins,
|
||||
|
@ -365,30 +273,32 @@ export async function reloadPlugin({
|
|||
context: LoadContext;
|
||||
}): Promise<LoadPluginsResult> {
|
||||
return PerfLogger.async('Plugins - reloadPlugin', async () => {
|
||||
const plugin = getPluginByIdentifier({
|
||||
const previousPlugin = getPluginByIdentifier({
|
||||
plugins: previousPlugins,
|
||||
pluginIdentifier,
|
||||
});
|
||||
|
||||
const reloadedPlugin = await executePluginLoadContent({plugin, context});
|
||||
const plugins = previousPlugins.with(
|
||||
previousPlugins.indexOf(plugin),
|
||||
reloadedPlugin,
|
||||
);
|
||||
|
||||
// TODO optimize this, we shouldn't need to re-run this lifecycle
|
||||
const contentLoadedResult = await executePluginsContentLoaded({
|
||||
plugins,
|
||||
const plugin = await executePluginContentLoading({
|
||||
plugin: previousPlugin,
|
||||
context,
|
||||
});
|
||||
|
||||
const allContentLoadedResult = await executePluginsAllContentLoaded({
|
||||
/*
|
||||
// TODO Docusaurus v4 - upgrade to Node 20, use array.with()
|
||||
const plugins = previousPlugins.with(
|
||||
previousPlugins.indexOf(previousPlugin),
|
||||
plugin,
|
||||
);
|
||||
*/
|
||||
const plugins = [...previousPlugins];
|
||||
plugins[previousPlugins.indexOf(previousPlugin)] = plugin;
|
||||
|
||||
const allContentLoadedResult = await executeAllPluginsAllContentLoaded({
|
||||
plugins,
|
||||
context,
|
||||
});
|
||||
|
||||
const {routes, globalData} = mergeResults({
|
||||
contentLoadedResult,
|
||||
plugins,
|
||||
allContentLoadedResult,
|
||||
});
|
||||
|
||||
|
|
87
packages/docusaurus/src/server/plugins/pluginsUtils.ts
Normal file
87
packages/docusaurus/src/server/plugins/pluginsUtils.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import logger from '@docusaurus/logger';
|
||||
import type {
|
||||
AllContent,
|
||||
GlobalData,
|
||||
InitializedPlugin,
|
||||
LoadedPlugin,
|
||||
PluginIdentifier,
|
||||
RouteConfig,
|
||||
} from '@docusaurus/types';
|
||||
|
||||
export function getPluginByIdentifier<P extends InitializedPlugin>({
|
||||
plugins,
|
||||
pluginIdentifier,
|
||||
}: {
|
||||
pluginIdentifier: PluginIdentifier;
|
||||
plugins: P[];
|
||||
}): P {
|
||||
const plugin = plugins.find(
|
||||
(p) =>
|
||||
p.name === pluginIdentifier.name && p.options.id === pluginIdentifier.id,
|
||||
);
|
||||
if (!plugin) {
|
||||
throw new Error(
|
||||
logger.interpolate`Plugin not found for identifier ${pluginIdentifier.name}@${pluginIdentifier.id}`,
|
||||
);
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
|
||||
export function aggregateAllContent(loadedPlugins: LoadedPlugin[]): AllContent {
|
||||
return _.chain(loadedPlugins)
|
||||
.groupBy((item) => item.name)
|
||||
.mapValues((nameItems) =>
|
||||
_.chain(nameItems)
|
||||
.groupBy((item) => item.options.id)
|
||||
.mapValues((idItems) => idItems[0]!.content)
|
||||
.value(),
|
||||
)
|
||||
.value();
|
||||
}
|
||||
|
||||
export function aggregateRoutes(loadedPlugins: LoadedPlugin[]): RouteConfig[] {
|
||||
return loadedPlugins.flatMap((p) => p.routes);
|
||||
}
|
||||
|
||||
export function aggregateGlobalData(loadedPlugins: LoadedPlugin[]): GlobalData {
|
||||
const globalData: GlobalData = {};
|
||||
loadedPlugins.forEach((plugin) => {
|
||||
if (plugin.globalData !== undefined) {
|
||||
globalData[plugin.name] ??= {};
|
||||
globalData[plugin.name]![plugin.options.id] = plugin.globalData;
|
||||
}
|
||||
});
|
||||
|
||||
return globalData;
|
||||
}
|
||||
|
||||
export function mergeGlobalData(...globalDataList: GlobalData[]): GlobalData {
|
||||
const result: GlobalData = {};
|
||||
|
||||
const allPluginIdentifiers: PluginIdentifier[] = globalDataList.flatMap(
|
||||
(gd) =>
|
||||
Object.keys(gd).flatMap((name) =>
|
||||
Object.keys(gd[name]!).map((id) => ({name, id})),
|
||||
),
|
||||
);
|
||||
|
||||
allPluginIdentifiers.forEach(({name, id}) => {
|
||||
const allData = globalDataList
|
||||
.map((gd) => gd?.[name]?.[id])
|
||||
.filter((d) => typeof d !== 'undefined');
|
||||
const mergedData =
|
||||
allData.length === 1 ? allData[0] : Object.assign({}, ...allData);
|
||||
result[name] ??= {};
|
||||
result[name]![id] = mergedData;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
|
@ -7,7 +7,11 @@
|
|||
|
||||
import path from 'path';
|
||||
import type {RuleSetRule} from 'webpack';
|
||||
import type {HtmlTagObject, LoadedPlugin, LoadContext} from '@docusaurus/types';
|
||||
import type {
|
||||
HtmlTagObject,
|
||||
LoadContext,
|
||||
InitializedPlugin,
|
||||
} from '@docusaurus/types';
|
||||
import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader';
|
||||
|
||||
/**
|
||||
|
@ -18,7 +22,7 @@ import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader';
|
|||
export function createBootstrapPlugin({
|
||||
siteDir,
|
||||
siteConfig,
|
||||
}: LoadContext): LoadedPlugin {
|
||||
}: LoadContext): InitializedPlugin {
|
||||
const {
|
||||
stylesheets,
|
||||
scripts,
|
||||
|
@ -27,7 +31,6 @@ export function createBootstrapPlugin({
|
|||
} = siteConfig;
|
||||
return {
|
||||
name: 'docusaurus-bootstrap-plugin',
|
||||
content: null,
|
||||
options: {
|
||||
id: 'default',
|
||||
},
|
||||
|
@ -75,10 +78,9 @@ export function createBootstrapPlugin({
|
|||
export function createMDXFallbackPlugin({
|
||||
siteDir,
|
||||
siteConfig,
|
||||
}: LoadContext): LoadedPlugin {
|
||||
}: LoadContext): InitializedPlugin {
|
||||
return {
|
||||
name: 'docusaurus-mdx-fallback-plugin',
|
||||
content: null,
|
||||
options: {
|
||||
id: 'default',
|
||||
},
|
||||
|
|
|
@ -247,10 +247,6 @@ export async function reloadSitePlugin(
|
|||
site: Site,
|
||||
pluginIdentifier: PluginIdentifier,
|
||||
): Promise<Site> {
|
||||
console.log(
|
||||
`reloadSitePlugin ${pluginIdentifier.name}@${pluginIdentifier.id}`,
|
||||
);
|
||||
|
||||
const {plugins, routes, globalData} = await reloadPlugin({
|
||||
pluginIdentifier,
|
||||
plugins: site.props.plugins,
|
||||
|
|
|
@ -11,6 +11,12 @@ import logger from '@docusaurus/logger';
|
|||
export const PerfDebuggingEnabled: boolean =
|
||||
!!process.env.DOCUSAURUS_PERF_LOGGER;
|
||||
|
||||
const Thresholds = {
|
||||
min: 5,
|
||||
yellow: 100,
|
||||
red: 1000,
|
||||
};
|
||||
|
||||
type PerfLoggerAPI = {
|
||||
start: (label: string) => void;
|
||||
end: (label: string) => void;
|
||||
|
@ -34,17 +40,40 @@ function createPerfLogger(): PerfLoggerAPI {
|
|||
|
||||
const prefix = logger.yellow(`[PERF] `);
|
||||
|
||||
const start: PerfLoggerAPI['start'] = (label) => console.time(prefix + label);
|
||||
const formatDuration = (duration: number): string => {
|
||||
if (duration > Thresholds.red) {
|
||||
return logger.red(`${(duration / 1000).toFixed(2)} seconds!`);
|
||||
} else if (duration > Thresholds.yellow) {
|
||||
return logger.yellow(`${duration.toFixed(2)} ms`);
|
||||
} else {
|
||||
return logger.green(`${duration.toFixed(2)} ms`);
|
||||
}
|
||||
};
|
||||
|
||||
const end: PerfLoggerAPI['end'] = (label) => console.timeEnd(prefix + label);
|
||||
const logDuration = (label: string, duration: number) => {
|
||||
if (duration < Thresholds.min) {
|
||||
return;
|
||||
}
|
||||
console.log(`${prefix + label} - ${formatDuration(duration)}`);
|
||||
};
|
||||
|
||||
const start: PerfLoggerAPI['start'] = (label) => performance.mark(label);
|
||||
|
||||
const end: PerfLoggerAPI['end'] = (label) => {
|
||||
const {duration} = performance.measure(label);
|
||||
performance.clearMarks(label);
|
||||
logDuration(label, duration);
|
||||
};
|
||||
|
||||
const log: PerfLoggerAPI['log'] = (label: string) =>
|
||||
console.log(prefix + label);
|
||||
|
||||
const async: PerfLoggerAPI['async'] = async (label, asyncFn) => {
|
||||
start(label);
|
||||
const before = performance.now();
|
||||
const result = await asyncFn();
|
||||
end(label);
|
||||
const duration = performance.now() - before;
|
||||
logDuration(label, duration);
|
||||
return result;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue