At 2022’s AGConf (1Password’s annual employee conference), every employee received a goodie box to celebrate the event and the company’s successes over the past year. Our theme this year was “space”, so the goodie box included a kit for a Lego rocket ship (very appropriate considering our own CEO is a Lego aficionado).
Building the spaceship brought me back to when I was younger and played endlessly with those little bricks.
For me, though, it wasn’t so much about building the specific items in a kit. Sure, I absolutely loved putting together the houses and planes and cars, but what I was most fascinated by was how I could use tiny bricks to expand my creation and build anything I could dream up. The possibilities were endless, my imagination ran wild, and sometimes – usually through through dumb luck – I built something way cooler than what the kit offered in the first place.
Late last year, I started exploring the React-based documentation framework Docusaurus, and spent a good chunk of time going through the documentation. (Surprise! They use their own product!) I got pretty familiar with how it works under the hood, and the ways in which it can be expanded on. It’s also got a bustling community, which is unsurprising since it’s entirely open source.
When I joined 1Password earlier this year, where I would be driving the effort to stand up a developer portal for our new developer offerings, I was excited to learn that we’d chosen Docusaurus v2 as the framework to power it all. I’ve had a chance to really dig in since then, learning as much as I could about this powerful little static site generator.
And it occurred to me recently that, with the way they’ve set it up, I’m reminded of those Lego creations: at its core it’s really just a bunch of individual pieces cleverly interlocked to create something far greater. It’s also built on a foundation designed to be entirely extensible.
So I’d like to look at how Docusaurus is put together, and why it’s so great for the 1Password developer portal.
Plugins all the way down
Plugins are the building blocks of features in a Docusaurus 2 site. Each plugin handles its own individual feature.
Docusaurus has handy plugin lifecycle APIs. When you start up the development server or generate a static bundle, each plugin kicks in and traverses through every stage of the lifecycle. With it, you can pull in data across all plugins simultaneously, register routes, validate configuration, and inject HTML tags, among many other things. Docusaurus leverages these same APIs to build up the overall user-facing functionality of the framework through their own collection of plugins.
Consider the primary use case for Docusaurus: documentation. The @docusaurus/plugin-content-docs plugin powers this central feature for the framework. Its more immediate functionality comes from using the loadContent
method to look for potentially localized and versioned sets of documentation on the filesystem, and contentLoaded
to provide the structured route data for the core to register and produce HTML files. It also extends Docusaurus’ CLI to allow for tagging a new docs version, and even tells the dev server which files to watch, and in turn run the lifecycles again.
The documentation plugin is obviously a huge part of Docusaurus, but they don’t stop there. Everything from the docs, to blogging and individual pages, all the way down to setting up Google Analytics and generating sitemaps are all powered by plugins.
So, why is this important?
If you’ll allow me to borrow my Lego analogy again: Docusaurus’ plugin APIs mean that, while they provide you with a kit you can set up and build something really cool with, they’ve also provided you with the ability to extend the framework in any direction to build something to suit your exact needs (at least as far as static sites go).
Great examples of this can be found on their community plugins page, where others have built plugins for offline/local search (we even use this today), adding SASS styles loading, and consuming OpenAPI specs to generate full API documentation pages. And it couldn’t be easier to roll your own.
Let’s say you wanted to load in some Google Fonts. Here’s what a plugin that does this by using the injectHtmlTags
method might look like:
module.exports = function pluginGoogleFonts(context, options) {
return {
name: "plugin-google-fonts",
injectHtmlTags: () => ({
// Tell the browser we're going to be loading resources from these origins
headTags: [
{
tagName: "link",
attributes: {
rel: "preconnect",
href: "https://fonts.googleapis.com",
},
},
{
tagName: "link",
attributes: {
rel: "preconnect",
href: "https://fonts.gstatic.com",
crossorigin: "anonymous",
},
},
// Load the Lobster font
{
tagName: "link",
attributes: {
rel: "stylesheet",
href: "https://fonts.googleapis.com/css2?family=Lobster&display=swap",
},
},
],
})
}
};
With this plugin in place, you can now freely use the Lobster font in your CSS. If you wanted to take it a step further and package this plugin up for distribution, you could even allow it to take an array of font names and weights as options to make it truly dynamic.
In the future, as we expand our developer portal, you’re likely to see us build plugins for things like importing and rendering developer blog posts, highlighting integrations built by our developer community, and a whole lot more.
Need to customize it? Swizzle away.
Plugins aren’t limited to just extending functionality, either. They’re what also delivers the look of the framework. Using the getThemePath
method your plugin can tell Docusaurus where to find the React components that make up a theme, selectively overriding components from an existing theme or building your own theme from the ground up.
One of the neatest features of Docusaurus is the ability to Swizzle a component.
[Swizzling] comes from Objective-C and Swift-UI: method swizzling is the process of changing the implementation of an existing selector (method). For Docusaurus, component swizzling means providing an alternative component that takes precedence over the component provided by the theme.
What does this mean in practice? Well, our developer portal currently uses the default Classic theme, but if you check out our footer you’ll notice that it looks nothing like the footer in that theme. We wanted ours to share a consistent look with the one on 1Password.com, so we swizzled the existing Footer component by running the following command:
npm run swizzle @docusaurus/theme-classic Footer -- --eject
This cloned the component out of the Docusaurus package and into our workspace. Now we’ve got full agency over the look and feel of the site’s footer, while still being able to rely on the rest of the theme’s components, which also includes future updates. We’re going to be swizzling a fair bit this year as the developer portal evolves.
The framework ships with the Classic theme, and out of the box it does a fantastic job. As of April 2022 the theme selection is fairly limited for v2 of Docusaurus, with only the Classic theme and some extensions to it available. More are coming, though. One that I’m particularly looking forward to, a Tailwind-powered theme, is also a great example of why I appreciate that they’re an open source project: it started as a community request, grew in popularity, and over time evolved into part of the roadmap.
Markup or Markdown - how about both?
As with every static site generator, it’s expected that Docusaurus would support Markdown - and they took it a step further, using MDX to parse content. MDX allows you to write JSX (React components) alongside your Markdown, allowing seamless native integration with the rest of the React app, which eventually gets all compiled down to HTML. This concept of static site generators interlacing Markdown with another syntax to extend the capabilities of its documentation is not new, but what gets me excited is the power that JSX affords us. You’re not limited to static HTML or shortcodes. Instead you get the full power of JSX components, meaning it’s possible to build fully styled, rich components that you can embed right in your content.
MDX also supports Remark and Rehype plugins, allowing you to augment the syntax and replace content on the fly. What can we do with this? Docusaurus demonstrates this well by creating its own plugins for admonitions, table of contents generation, and creating heading links.
There’s already a huge collection of plugins available for both Remark and Rehype, but if you need something a little more tailored to your specific use case creating these types of plugins is really straightforward, too. Consider this 13-liner that defaults Markdown code blocks to using Shell highlighting:
const visit = require("unist-util-visit");
module.exports = function pluginRemarkShellCode(context, options) {
return (tree) => {
visit(tree, (node) => {
// If the node is a code block, but the language is not set
if (node.type === "code" && !node.lang) {
// Set it to Shell
node.lang = "shell";
}
});
};
};
Using unist-util-visit we can iterate across all nodes and their children to selectively modify the properties or contents of any node that matches our criteria. Now our Markdown files only need to specify language for those code blocks that aren’t using Shell.
Fully Open Source
I’ve been heads down in Docusaurus for quite some time now, and it’s proven to be incredibly robust. But beyond the framework itself, I’ve also really appreciated the community behind it. From contributing my own PRs to the core, to getting help from team members themselves and other eager developers in their Discord server, it’s been a pleasure creating with this extraordinary tool.
Go check out the 1Password developer portal, built with Docusaurus. I’m looking forward to showing off the cool things we’ve got planned for it down the road as we use these building blocks to create something really, really cool.
Tweet about this post