cover image courtesy of Daiga Ellaby

Breed Dinosaur or Ant?

This week, one project I worked on involved recreating a doc site inside Ant Design's repo using its existing docs. Ant Design already has a doc site, building on top of which is a default option on the table. But I was also given the freedom to explore other options and so I've taken the liberty of diving into Docusaurus.

I've been on the maintainer team of Docusaurus since earlier this year. I've had very good development experience with the newest Docusaurus 2 that we are now rapidly iterating. I can build a D2 site like instantly. You can consider this to be a self-promoting post on D2 but I try very hard to be objective. In a few words, Docusaurus 2 is a static site generator geared greatly towards documentation sites.

Docu-slashing the Ant Design doc site

I will omit the Ant Design exploration because that part is more directly related to the business scope of the project. Plus I feel the interesting part is the bringing dinosaur to our modern time so let's buckle up.

Surprisingly, wait, actually, I'm not sure which direction should be "surprisingly". But building a D2 site inside Ant Design's repo did not just work. There are some use cases that are specific to Ant Design's site that I had to tweak to make happen in my Docusaurus site.

Doc files resolution

First, Ant Design does not put all docs into one directory, which is kinda expected by Docusaurus. Each component has its own docs written in a README.md file and a few more files (will talk about this later) in its respective directory. And in fact, I think this use case is a very legit one because it is common in monorepo projects as well. With Docusaurus's current API design, to include the docs that live in the code directories you would have to resolve the parent folder. This ends up scanning a lot of files and you may even include node_modules if you are not careful.

To make my site work, I hacked this by using the include field in the options for Docusaurus' docs plugin. The reason I call it a hack is that include is intended for file extensions. It gets mapped over and concatenated with the resolved directory you specified. But I guess if I use this as resolved subdirectories Slash her majesty won't know it neither.

// related field in Docusaurus' docs plugin
const DEFAULT_OPTIONS = {
  path: 'docs', // Path to data on filesystem, relative to site dir.
  include: ['**/*.md', '**/*.mdx'], // Extensions to include.
}

Docusaurus then builds all the information into a bunch of JSON files that will get prefetched and preloaded following the PRPL pattern. This is a very common pattern nowadays adopted by many static site generators including Gatsby.

Ant Design's generator, on the other hand, generates a bunch of HTML and JS files that contain the data in, say, objects and arrays. The data part is not that different after all. The HTML files imply that every page is initially server side rendered. I find no reason to dislike that. I ran into some caveats later on in one of the things we tried to hack through, but that should not be used to judge the structure here.

Embedding demos as partials

Ant Design's docs are organized in such a way that each doc lives in a directory with a subdirectory for demos. And the site builds those demos to be included in the parent doc's page. From Docusaurus' perspective this should be handled via a customized plugin. But my boss moves fast and I had been struggling to catch up. This is not a complaint and I will struggle less next week. All in all I stopped at a rough idea of how I might proceed implementing it, but did not get into actually doing it.

Things I like about Docusaurus

I had a very tough time playing with Ant Design's underlying generator and that is mainly due to my lack of understanding of their whole project. Regarding anything I share below, please blame me for not understanding it and do not generate any judgement based on my description of it. But I did learn a lot about Docusaurus recently, and so I'll list a point or two on my current understanding about it.

Configurations are decoupled from content and have straightforward APIs

Ant Design's CN / EN switch logic is heavily coupled with the site implementation. The keys to CN and EN assets are flushed together in the underlying site generator's configuration file. And there is overlap between their server rendered and dynamic routing. At the end of the week my brain could not get the routing right.

I had a much easier time setting the site up from scratch with Docusaurus. To specify what shows up on the sidebar and in what grouping and order, you specify a sidebars.js file whose content is simply all the doc ids. As an oversimplified example from a styleless template I created over this weekend:

// sidebars.js
module.exports = {
  docs: {
    Docusaurus: ['doc1', 'doc2', 'doc3'],
    Features: ['mdx'],
  },
}

And within your docs' .md files, the id is all you need. It will be nicer if you specify also the title and the sidebar_label alongside, but they are both optional:

---
id: doc1
---
<!-- ☝️ frontmatter above ☝️ -->

<!-- 👇 actual content below 👇 -->
Slash her majesty will be your friend...

And you can see an in-real-life sidebar configuration for Docusaurus 2's newest site.

However, D2's i18n and versioning features are still under development (my brothers are the heroes). Really looking forward to how these features turn up.

Really very performant

I once wasn't really excited about how performant Docusaurus 2 sites are. They have the fastest build I've ever experienced. But my thought was, how long time would people spend on building the doc site for their project anyway? This argument has a paradoxical flaw as well, because the slower it builds the longer you need to spend with it, isn't it ¯\_(ツ)_/¯?

But now that my whole week's job was basically to build a doc site, I now miss our lovely D2 sites badly because our hot reload server builds and rebuilds in matters of seconds and milliseconds. I'm super duper overly proud of this. And after the past few weeks learning about Docusaurus, here's my entry level understanding of the jungle of our performance optimizations:

One of my brothers cares a lot about performance and has been on fire optimizing our D2 sites. He's also no doubt one of the best Node.js developers I've ever worked with. How we treat performance with Docusaurus is that we take advantages of existing libraries, lazy load them if they are costly, optimize both client experience and server build, and we swap out those libraries with the most performant ones he finds in the wild.

A relevant topic is how the choices are made between what happens in build time and what happens in run time. The matter is, things that happen in build time require restarting the dev server whenever they change. And that in turn affects your build performance. Things that happen in run time, albeit more dynamic, puts burden on your client side and so you'd have to make the call. For some items on this list, the preference is quite clear and all SSGs are doing the same decision. Some others are rather subjective:

  • doc content changes
  • doc deletion / addition
  • component changes
  • configurations

And this question expand even more when we include the A of JAMstack. And some are business decisions. So that's a pointer to a lot of interesting problems to think about.

Thoughts on improvements

Gathering all my shell picking on Docusaurus this week, over the weekend I've spent some time building a "no style" theme and template for D2. That was a huge understanding boost to myself which I'll share more in forms of docs, tweets, and posts. I've also found out some of the things that are bugging me:

  • The init command takes [your project name] in middle of the whole command, before the template name or link to git, this makes it tedious to copy.
  • The themes configuration with options are now in form of an array. This is smart, but not very friendly. I'll prefer it to be less smart and say out loud that I'm taking this theme (object), here is the name, and here is the options, instead of the first item the name and the second item the options.
  • When you update the required fields in docusaurus.config.js, we throw error at your shell. We should do better than that.

I'll probably PR some of these ideas in the upcoming week. We're also taking in feature proposals here, thank you for being part of it.

✌️ Till next time

I haven't been very verbal recently. It's probably a growing up in a temperate climate thing, where things happen on a seasonal basis. I'll show up in a few events later this year, though, of which I'm sure will have a lot to share.