export async function loadPlugins()

in packages/docusaurus/src/server/plugins/index.ts [33:173]


export async function loadPlugins(context: LoadContext): Promise<{
  plugins: LoadedPlugin[];
  pluginsRouteConfigs: RouteConfig[];
  globalData: GlobalData;
}> {
  // 1. Plugin Lifecycle - Initialization/Constructor.
  const plugins: InitializedPlugin[] = await initPlugins(context);

  plugins.push(
    createBootstrapPlugin(context),
    createMDXFallbackPlugin(context),
  );

  // 2. Plugin Lifecycle - loadContent.
  // 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?.();
      const rawTranslationFiles =
        (await plugin?.getTranslationFiles?.({content})) ?? [];
      const translationFiles = await Promise.all(
        rawTranslationFiles.map((translationFile) =>
          localizePluginTranslationFile({
            locale: context.i18n.currentLocale,
            siteDir: context.siteDir,
            translationFile,
            plugin,
          }),
        ),
      );
      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};
    }),
  );

  const allContent: AllContent = _.chain(loadedPlugins)
    .groupBy((item) => item.name)
    .mapValues((nameItems) =>
      _.chain(nameItems)
        .groupBy((item) => item.options.id)
        .mapValues((idItems) => idItems[0]!.content)
        .value(),
    )
    .value();

  // 3. Plugin Lifecycle - contentLoaded.
  const pluginsRouteConfigs: RouteConfig[] = [];
  const globalData: GlobalData = {};

  await Promise.all(
    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 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,
              __context: 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, actions, allContent});
    }),
  );

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

      // 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);
    }),
  );

  // Sort the route config. This ensures that route with nested
  // routes are always placed last.
  sortConfig(pluginsRouteConfigs, context.siteConfig.baseUrl);

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