DEV Community

Paul Kim
Paul Kim

Posted on

Performing a Code Reading of Docusaurus

For this week in Topics in Open Source Development, I was tasked with performing a code reading of Docusaurus, a popular documentation static site generator.

Docusaurus provides the ability for users to search through documentation. Instead of bootstrapping its own search function, it leverages existing solutions which the developer can choose from, including Algolia DocSearch, Typesense, a variety of community plugins, or one's own search solution. Since Docusaurus does not provide a native search function and relies on third party solutions, this code reading will focus on how it integrates the Algolia Docsearch search functionality into its UI.

Docusaurus Repository: https://github.com/facebook/docusaurus

Documentation

The documentation to configure a search function for one's website is found in website/docs/search.mdx. This same page is hosted officially at https://docusaurus.io/docs/search.

It is a MDX file that combines both markdown and JSX syntax, though it does not use MDX specific syntax. It does however use Front Matter syntax for use with the Front Matter extension, adding a keywords attribute in a front matter block as its header, which defines parseable metadata.

---
keywords:
  - algolia
  - search
---
...
Enter fullscreen mode Exit fullscreen mode

I haven't worked with MDX or Front Matter before, but I've seen (remarkably) similar syntax with Obsidian's Tags syntax. This provides a way to tag and parse otherwise simple markdown files in a powerful, programmatic way.

Docusaurus Classic Preset

Docusaurus features a set of presets - bundles of packages and plugins. Of note, the classic preset supports the Algolia DocSearch by default. It does so by incorporating the theme-search-algolia theme.

export default function preset(
  context: LoadContext,
  opts: Options = {},
): Preset {
  const {siteConfig} = context;
  const {themeConfig} = siteConfig;
  const {algolia} = themeConfig as Partial<ThemeConfig>;
  const isProd = process.env.NODE_ENV === 'production';
  const {
    debug,
    docs,
    blog,
    pages,
    sitemap,
    theme,
    googleAnalytics,
    gtag,
    googleTagManager,
    rest
  } = opts;

  const themes: PluginConfig[] = [];
  themes.push(makePluginConfig('@docusaurus/theme-classic', theme));
  if (algolia) {
    themes.push(require.resolve('@docusaurus/theme-search-algolia'));
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

If a configuration object (resolved into the algolia Partial<ThemeConfig>) is not provided when instantiating this preset, the preset will not include the theme-search-algolia theme. This is due to the fact that usage of the Algolia API requires the API variables to be defined in docusaurus.config.js (details here).

Docusaurus Algolia Search Theme

The Algolia search theme package is found in /packages/docusaurus-theme-search-algolia. The package module and its types are defined in the theme-search-algolia.d.ts file. I've seen and used similar instances of declaring types (and their subtypes) in their own dedicated file for visibility.

The docusaurus-theme-search-algolia/src/index.ts file defines the function themeSearchAlgolia which returns the instantiated plugin of type Plugin<void>, which is a type used to define Docusaurus plugins and contains data members such as its name, webpack configuration, file paths, HTML tags to inject, and translation files. Of note, it takes in a themeConfig object which includes the Algolia API configuration data.

In this package's src folder there are the following directories:

  • __tests__
    • Jest tests including:
      • A minimal configuration using a sample Algolia configuration (including a sample Algolia key and Algolia application ID) (link).
      • Passing an unknown key in this configuration (link).
      • If the Algolia configuration object is undefined (link).
      • If the API key is missing (link).
  • client
    • A function to retrieve the theme configuration object (link).
    • A function to parse URLs returned from Algolia into local paths that can be navigated to within Docusaurus. This is done by first checking whether it is an external domain with a regex pattern match, then converting it to a relative URL (link).
  • templates
  • theme
    • The Search Bar React component (link)
      • Returns a custom DocSearch component (link).
      • This component includes a property titled transformSearchClient (link) which calls the addAlgoliaAgent method from the algoliasearch/lite package, which adds a custom user agent to interact with Algolia API (link).
      • This search bar component is meant to be inserted into the navbar component for a theme, such as the Docusarus classic theme's navbar component (link).
      • To "prepare" the browser to load subsequent queries from the Algolia API, a <link> tag referencing the Algolia service (link) is placed in the <Head> component that is not actually rendered to the client and serves no other purpose.
    • The Search Page React component (link)
      • A dedicated search page component.
      • An Algolia client is constructed using the appId and apiKey stored in the theme configuration object (link).
      • To use this client in a meaningful way, an Algolia helper is constructed where the parent component can define options including how many results to show per page (link).
        • Upon receiving a search result (link), a callback function is called that sanitizes the returned result and parses the result array via the JavaScript map method, returning an array of objects that includes the title, URL, a textual summary, and a trail of breadcrumbs to follow.
        • The searchResultStateDispatcher method is called with the returned and parsed search results to be stored in state.
      • Renders a dedicated link to www.algolia.com (link).

Code Reading Techniques

To perform this code reading, I opened this project in VS Code which I am more comfortable with instead of using GitHub code search or git grep. The "search all" function was the most useful, as well as "Go To Definition" to see where a function alias was originally defined. I started by searching for the instances of the word search throughout the project to find modules that were relevant to the search function. Next, I observed the file and folder structure to understand how the project was laid out.

Directories of note include:

  • __tests__
    • Tests specific to validating the packages.json and tsconfig.json files.
  • admin
    • Instructions for environment setup, testing, debugging, publishing, and other miscellaneous files.
  • examples
    • Example Docusaurus projects.
  • packages
    • Docusaurus packages including presets, scripts, plugins, types, utility functions, and themes.
  • website
    • The official Docusaurus website to be rendered and hosted. Acts as the single source of truth for Docusaurus documentation.

If there was any code that was unfamiliar to me (such as MDX syntax), I pasted it into ChatGPT to identify the type of syntax I was looking at (if I could not discern it from the filename extension). I also looked through the official Docusaurus documentation on how to implement the search function for more clues.

Thanks for reading!

Top comments (0)