8000 fix(core): all plugin lifecycles should receive translated content by Josh-Cena · Pull Request #7066 · facebook/docusaurus · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix(core): all plugin lifecycles should receive translated content #7066

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions packages/docusaurus/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,7 @@ export async function load(options: LoadContextOptions): Promise<Props> {
ssrTemplate,
codeTranslations: siteCodeTranslations,
} = context;
const {plugins, pluginsRouteConfigs, globalData, themeConfigTranslated} =
await loadPlugins(context);
// Side-effect to replace the untranslated themeConfig by the translated one
context.siteConfig.themeConfig = themeConfigTranslated;
const {plugins, pluginsRouteConfigs, globalData} = await loadPlugins(context);
const clientModules = loadClientModules(plugins);
const {headTags, preBodyTags, postBodyTags} = loadHtmlTags(plugins);
const {registry, routesChunkNames, routesConfig, routesPaths} =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,5 @@ exports[`loadPlugins loads plugins 1`] = `
},
],
"pluginsRouteConfigs": [],
"themeConfigTranslated": {},
}
`;
183 changes: 76 additions & 107 deletions packages/docusaurus/src/server/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type {
RouteConfig,
AllContent,
GlobalData,
ThemeConfig,
LoadedPlugin,
InitializedPlugin,
PluginRouteContext,
Expand All @@ -28,13 +27,13 @@ import {applyRouteTrailingSlash, sortConfig} from './routeConfig';
/**
* Initializes the plugins, runs `loadContent`, `translateContent`,
* `contentLoaded`, and `translateThemeConfig`. Because `contentLoaded` is
* side-effect-ful (it generates temp files), so is this function.
* side-effect-ful (it generates temp files), so is this function. This function
* would also mutate `context.siteConfig.themeConfig` to translate it.
*/
export async function loadPlugins(context: LoadContext): Promise<{
plugins: LoadedPlugin[];
pluginsRouteConfigs: RouteConfig[];
globalData: GlobalData;
themeConfigTranslated: ThemeConfig;
}> {
// 1. Plugin Lifecycle - Initialization/Constructor.
const plugins: InitializedPlugin[] = await initPlugins(context);
Expand All @@ -48,21 +47,15 @@ export async function loadPlugins(context: LoadContext): Promise<{
// Currently plugins run lifecycle methods in parallel and are not
// order-dependent. We could change this in future if there are plugins which
// need to run in certain order or depend on others for data.
// This would also translate theme config and content upfront, given the
// translation files that the plugin declares.
const loadedPlugins: LoadedPlugin[] = await Promise.all(
plugins.map(async (plugin) => {
const content = await plugin.loadContent?.();
return {...plugin, content};
}),
);

const contentLoadedTranslatedPlugins = await Promise.all(
loadedPlugins.map(async (plugin) => {
const translationFiles =
(await plugin?.getTranslationFiles?.({
content: plugin.content,
})) ?? [];
const localizedTranslationFiles = await Promise.all(
translationFiles.map((translationFile) =>
const rawTranslationFiles =
(await plugin?.getTranslationFiles?.({content})) ?? [];
const translationFiles = await Promise.all(
rawTranslationFiles.map((translationFile) =>
localizePluginTranslationFile({
locale: context.i18n.currentLocale,
siteDir: context.siteDir,
Expand All @@ -71,10 +64,17 @@ export async function loadPlugins(context: LoadContext): Promise<{
}),
),
);
return {
...plugin,
translationFiles: localizedTranslationFiles,
};
const translatedContent =
plugin.translateContent?.({content, translationFiles}) ?? content;
const translatedThemeConfigSlice = plugin.translateThemeConfig?.({
themeConfig: context.siteConfig.themeConfig,
translationFiles,
});
// Side-effect to merge theme config translations. A plugin should only
// 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};
}),
);

Expand All @@ -90,86 +90,75 @@ export async function loadPlugins(context: LoadContext): Promise<{

// 3. Plugin Lifecycle - contentLoaded.
const pluginsRouteConfigs: RouteConfig[] = [];

const globalData: GlobalData = {};

await Promise.all(
contentLoadedTranslatedPlugins.map(
async ({content, translationFiles, ...plugin}) => {
if (!plugin.contentLoaded) {
return;
}
const pluginId = plugin.options.id;
// plugins data files are namespaced by pluginName/pluginId
const dataDir = path.join(
context.generatedFilesDir,
plugin.name,
pluginId,
);
// TODO this would be better to do all that in the codegen phase
// TODO handle context for nested routes
const pluginRouteContext: PluginRouteContext = {
plugin: {name: plugin.name, id: pluginId},
data: undefined, // TODO allow plugins to provide context data
};
const pluginRouteContextModulePath = path.join(
dataDir,
`${docuHash('pluginRouteContextModule')}.json`,
);
await generate(
'/',
pluginRouteContextModulePath,
JSON.stringify(pluginRouteContext, null, 2),
);

const actions: PluginContentLoadedActions = {
addRoute(initialRouteConfig) {
// Trailing slash behavior is handled generically for all plugins
const finalRouteConfig = applyRouteTrailingSlash(
initialRouteConfig,
context.siteConfig,
);
pluginsRouteConfigs.push({
...finalRouteConfig,
modules: {
...finalRouteConfig.modules,
__routeContextModule: pluginRouteContextModulePath,
},
});
},
async createData(name, data) {
const modulePath = path.join(dataDir, name);
await generate(dataDir, name, data);
return modulePath;
},
setGlobalData(data) {
globalData[plugin.name] ??= {};
globalData[plugin.name]![pluginId] = data;
},
};
loadedPlugins.map(async ({content, ...plugin}) => {
if (!plugin.contentLoaded) {
return;
}
const pluginId = plugin.options.id;
// plugins data files are namespaced by pluginName/pluginId
const dataDir = path.join(
context.generatedFilesDir,
plugin.name,
pluginId,
);
// TODO this would be better to do all that in the codegen phase
// TODO handle context for nested routes
const pluginRouteContext: PluginRouteContext = {
plugin: {name: plugin.name, id: pluginId},
data: undefined, // TODO allow plugins to provide context data
};
const pluginRouteContextModulePath = path.join(
dataDir,
`${docuHash('pluginRouteContextModule')}.json`,
);
await generate(
'/',
pluginRouteContextModulePath,
JSON.stringify(pluginRouteContext, null, 2),
);

const translatedContent =
plugin.translateContent?.({content, translationFiles}) ?? content;
const actions: PluginContentLoadedActions = {
addRoute(initialRouteConfig) {
// Trailing slash behavior is handled generically for all plugins
const finalRouteConfig = applyRouteTrailingSlash(
initialRouteConfig,
context.siteConfig,
);
pluginsRouteConfigs.push({
...finalRouteConfig,
modules: {
...finalRouteConfig.modules,
__routeContextModule: pluginRouteContextModulePath,
},
});
},
async createData(name, data) {
const modulePath = path.join(dataDir, name);
await generate(dataDir, name, data);
return modulePath;
},
setGlobalData(data) {
globalData[plugin.name] ??= {};
globalData[plugin.name]![pluginId] = data;
},
};

await plugin.contentLoaded({
content: translatedContent,
actions,
allContent,
});
},
),
await plugin.contentLoaded({content, actions, allContent});
}),
);

// 4. Plugin Lifecycle - routesLoaded.
await Promise.all(
contentLoadedTranslatedPlugins.map(async (plugin) => {
loadedPlugins.map(async (plugin) => {
if (!plugin.routesLoaded) {
return;
}

// TODO remove this deprecated lifecycle soon
// deprecated since alpha-60
// TODO, 1 user reported usage of this lifecycle! https://github.com/facebook/docusaurus/issues/3918
// TODO alpha-60: remove this deprecated lifecycle soon
// 1 user reported usage of this lifecycle: https://github.com/facebook/docusaurus/issues/3918
logger.error`Plugin code=${'routesLoaded'} lifecycle is deprecated. If you think we should keep this lifecycle, please report here: url=${'https://github.com/facebook/docusaurus/issues/3918'}`;

await plugin.routesLoaded(pluginsRouteConfigs);
Expand All @@ -180,25 +169,5 @@ export async function loadPlugins(context: LoadContext): Promise<{
// routes are always placed last.
sortConfig(pluginsRouteConfigs, context.siteConfig.baseUrl);

// Apply each plugin one after the other to translate the theme config
const themeConfigTranslated = contentLoadedTranslatedPlugins.reduce(
(currentThemeConfig, plugin) => {
const translatedThemeConfigSlice = plugin.translateThemeConfig?.({
themeConfig: currentThemeConfig,
translationFiles: plugin.translationFiles,
});
return {
...currentThemeConfig,
...translatedThemeConfigSlice,
};
},
context.siteConfig.themeConfig,
);

return {
plugins: loadedPlugins,
pluginsRouteConfigs,
globalData,
themeConfigTranslated,
};
return {plugins: loadedPlugins, pluginsRouteConfigs, globalData};
}
0