An In-Depth Look at Generating a Sidebar from Code : Docusaurus
Published: 2023-11-01 03:10:41

In this blog post, we will explore the inner workings of a codebase that generates a sidebar for documentation. We'll walk through the code and understand how it extracts and structures documentation, how it handles different categories, and how it eventually sorts and organizes the sidebar items. 
This is my first time reading a code, I realised the importance of code comments or documentation which helped me read such a complex code.

## Walkthrough

### Step 1: Extracting Documentation

The first step in generating a sidebar is extracting the documentation. This is done by the getAutogenDocs function. 

```
function getAutogenDocs(): SidebarItemsGeneratorDoc[] {
  function isInAutogeneratedDir(doc: SidebarItemsGeneratorDoc) {
    // Checks whether the document is in the autogenerated directory
    // ...
  }
  const docs = allDocs.filter(isInAutogeneratedDir);
  if (docs.length === 0) {
    // Handle case when no docs are found in the specified path
  }
  return docs;
}

```
This function filters the documents to include only those that belong to the autogenerated sidebar directory. It checks whether the document is in the correct directory based on its source and directory name. If no docs are found, it logs a warning.

### Step 2: Structuring the Sidebar

The second step is structuring the sidebar by creating a tree-like structure. The treeify function takes the list of documents and organizes them into a hierarchical structure:

```
function treeify(docs: SidebarItemsGeneratorDoc[]): Dir {
    // Get the category breadcrumb of a doc (relative to the dir of the
    // autogenerated sidebar item)
    // autogenDir=a/b and docDir=a/b/c/d => returns [c, d]
    // autogenDir=a/b and docDir=a/b => returns []
    // TODO: try to use path.relative()
    function getRelativeBreadcrumb(doc: SidebarItemsGeneratorDoc): string[] {
      return autogenDir === doc.sourceDirName
        ? []
        : doc.sourceDirName
            .replace(addTrailingSlash(autogenDir), '')
            .split(BreadcrumbSeparator);
    }
    const treeRoot: Dir = {};
    docs.forEach((doc) => {
      const breadcrumb = getRelativeBreadcrumb(doc);
      // We walk down the file's path to generate the fs structure
      let currentDir = treeRoot;
      breadcrumb.forEach((dir) => {
        if (typeof currentDir[dir] === 'undefined') {
          currentDir[dir] = {}; // Create new folder.
        }
        currentDir = currentDir[dir] as Dir; // Go into the subdirectory.
      });
      // We've walked through the path. Register the file in this directory.
      currentDir[path.basename(doc.source)] = doc.id;
    });
    return treeRoot;
  }

```

## Step 3: Converting to Sidebar Items

Once the hierarchical structure is ready, the next step is to convert it into sidebar items. The generateSidebar function performs this transformation:

```
function generateSidebar(fsModel: Dir): WithPosition<NormalizedSidebarItem>[] {
  // Helper functions and logic for creating sidebar items
  // ...
  return Object.entries(fsModel).map(([key, content]) =>
    dirToItem(content, key, key),
  );
}

```
This function creates sidebar items, including documents and categories. It extracts metadata such as labels, positions, and custom properties from the documents to provide a more user-friendly and organized navigation experience.

### Step 4: Sorting Sidebar Items
The final step is to sort the sidebar items. The sortItems function is responsible for sorting the items:

```
function sortItems(
    sidebarItems: WithPosition<NormalizedSidebarItem>[],
  ): NormalizedSidebarItem[] {
    const processedSidebarItems = sidebarItems.map((item) => {
      if (item.type === 'category') {
        return {...item, items: sortItems(item.items)};
      }
      return item;
    });
    const sortedSidebarItems = _.sortBy(processedSidebarItems, [
      'position',
      'source',
    ]);
    return sortedSidebarItems.map(({position, source, ...item}) => item);
  }
```

### Concluding the source code
In conclusion, this code provides valuable insights into how a documentation sidebar is generated. It showcases the importance of careful organization, metadata handling, and user customization. By dissecting each step, we gain a deeper appreciation for the intricacies of such a system.


## Testing :

The provided test suite gives us valuable insights into how the DefaultSidebarItemsGenerator behaves under various scenarios. Let's analyze the tests and incorporate this understanding into our blog post.

### Empty Sidebar Generation with Warning:
The first test ensures that the generator handles an empty set of documents appropriately. It checks if an empty sidebar slice is generated and if a warning is emitted when no documents are found in the specified path.

```
it('generates empty sidebar slice when no docs and emit a warning', async () => {
  const consoleWarn = jest.spyOn(console, 'warn');
  const sidebarSlice = await testDefaultSidebarItemsGenerator({
    docs: [],
  });
  expect(sidebarSlice).toEqual([]);
  expect(consoleWarn).toHaveBeenCalledWith(
    expect.stringMatching(
      /.*\[WARNING\].* No docs found in [^.]*\..*: can't auto-generate a sidebar\..*/,
    ),
  );
});

```
### Generation of Simple Flat Sidebar:

```
it('generates simple flat sidebar', async () => {
  const sidebarSlice = await DefaultSidebarItemsGenerator({
    // Test input data
    // ...
  });

  expect(sidebarSlice).toMatchSnapshot();
});

```

### Generation of Complex Nested Sidebar:
This test explores the generator's ability to handle nested categories and documents. It checks if nested categories are correctly processed, maintaining the correct hierarchy and order.

### Generation of Subfolder Sidebar:
This test focuses on generating a sidebar for a specific subfolder. It ensures that the generator correctly reads category metadata and handles various document placements within subfolders.

### Handling Explicit Links and Index Naming Conventions:
This test assesses how the generator handles explicit links over index and readme file naming conventions. It checks if explicit links are given priority and if the generator respects custom isCategoryIndex logic.

### Custom isCategoryIndex Functionality:
This test verifies if the generator respects a custom isCategoryIndex function. It examines if the generator correctly applies custom logic for categorizing documents as index pages.

### Handling Unknown Index Links:
The last test ensures that the generator throws an error when an unknown index link is specified in the category metadata. This is crucial for catching potential issues with link references.

TO VIEW ALL THE TESTS : - [Tests](https://github.com/facebook/docusaurus/blob/791da2e4a1a53aa6309887059e3f112fcb35bec4/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/generator.test.ts)