articles

Rustdoc cleanups and improvements

We recently improved a lot of things in rustdoc. However, these changes are mostly invisible for users even though they're making their browsing experience better overall. I thought that it would be interesting to talk about these improvements and cleanups to see what impact they have.

Reducing rustdoc generated documentation

rustdoc generates a lot of HTML files and some of them are really huge. It has a negative impact on the browsing experience and as such, we need to do better. We came up with a few improvements to reduce the pages size, so let's go through them!

Simplifying codeblock tooltip

Before we go any further, here is a codeblock tooltip:


In #101600, the DOM of the tooltip was moved inside the codeblock. It then allowed to remove some JavaScript to handle mouse events, simplify some CSS and reduce the generated HTML. A big win on all accounts!

Then #101613 went even further and move some CSS class attributes directly onto the parent so it wouldn't be duplicated amongst the children. To put into code to make it more clear:

<a>
    <b class="compile_fail">_
    <c class="compile_fail"></c>
</a>

became:

<a class="compile_fail">
    __
    <c></c>
</a>

It also allowed to remove a tag wrapping the tooltip, simplifying even more the DOM.

Use a more compact encoding for source-files.js

This file contains the list of source code files in your project and is used when generating the source code pages sidebar. Here is an image of it (it's the element pointed by the arrow):


By making this format more compact, it improves the loading speed and reduce the disk size usage.

Using details for source code sidebar

Just like the previous improvement, #98773 targets the source code pages sidebar as well. The file hierarchy was using some ad-hoc HTML elements instead of the <details> HTML element (specifically made for this). Switching to <details> allowed to simplify the JavaScript and the CSS.

Reducing the right-side element's size

Here what the "right-side element" is:


So this whole part const: 1.39.0 · source is wrapped in one parent element to make both children elements (const: 1.39.0 and source) stick together. However, if there is only one of them (like just source), there is no need for this parent element anymore. Explanation in code:

<div>
    <span>const: 1.39.0</span> · <a href="some-location">source</a>
</div>

So here, we have more than one element, the div keeps them together. But if we only have the source:

<div>
    <a href="some-location">source</a>
</div>

There is no need to keep "them" together anymore since it's only one element. So we can instead just keep:

<a href="some-location">source</a>

This change was made in #100956. The DOM size was reduced by up to 39.2% on the std's Default page. You can find more numbers on the pull request first message.

Prevent empty item description block

When on a module page, if an item has documentation, you will see the first sentence on its right. However, if the item doesn't have documentation, we still generated the wrapping DOM for this documentation block even though it was empty. In #101018, we stopped generating such wrapping DOM if there is no documentation.

Merge code block elements

In rust code blocks (either in code examples or in the source code pages), the highlight is not generated with JavaScript but directly when we generate the documentation. To do so, we parse the Rust source code and add some CSS classes on the various elements. For example, pub fn foo will become:

<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="ident">foo</span>

From reading this, we realized that elements with the same CSS class only separated by a whitespace could be merged together. So the previous HTML became:

<span class="keyword">pub fn</span> <span class="ident">foo</span>

Quite a lot shorter! But that's not all. Since we don't use syntax highlighting for idents, we simply decided to remove it altogether. So it became:

<span class="keyword">pub fn</span> foo

Same display, much shorter HTML. It allowed us to reduce the DOM size by up to 39.7%. You can find more numbers in the pull request: #100429.

Then #100775 went even further and merged some other elements (mostly attributes), allowing to reduce the DOM size by up to 24% (on top of the previous improvement!). Again, more numbers in the pull request.

It all started from #100409 which was removing the wrapping DOM for the operators (9% reduction!).

Removing unneeded attributes

Some HTML tags have default values for some attributes. For example, the type attribute of <script> has text/javascript as default value and rustdoc was setting it every time. So if you multiply the number of <script> tags on each page by the number of pages, the gain starts to be quite interesting! It was done in #101062.

Cleaning up rustdoc's HTML whitespace

rustdoc generated HTML had some unneeded whitespace because of a typo or simply that we forgot to set the right tag in the template. A few pull requests fixed it:

Optimize loading of source sidebar

Copy-paste from #98310 first message:

The source sidebar has a setting to remember whether it should be open or closed. Previously, this setting was handled in source-script.js, which is loaded with defer, meaning it is often run after the document is rendered. Since CSS renders the source sidebar as closed by default, changing this after the initial render triggers a recomputation of the page layout.

Instead, handle the setting in storage.js, which is the first script to load and is the only script that blocks render. This avoids a relayout and means faster navigation between files with the sidebar open.

Improve loading of crates.js and sidebar-items.js

#98124 is all about making the loading of the page smoother by changing how the JavaScript scripts are loaded while also simplifying it overall. If you want all the technical details, I recommend you to go read the pull request's first message.

Simplification of CSS themes

rustdoc provides three themes by default: ayu, dark and light. To prevent some chaos, only the CSS rules impacting colours are in these files. However, it creates a lot of duplication and every change needs to be made three times. A solution to greatly reduce this problem was found: using CSS variables. So now, instead of redefining a CSS rule three times, we just create one CSS variable in the three files and that's it. Let's take an example:

// In rustdoc.css:
.ignore.example-wrap {
    border-left: 2px solid;
}

// In dark.css:
.ignore.example-wrap {
    border-left-color: rgba(255, 142, 0, .6);
}
// Same in ayu.css and light.css...

It's a lot of code for just setting a colour... Now let's do the same with CSS variables:

// In rustdoc.css:
.ignore.example-wrap {
    border-left: 2px solid var(--codeblock-ignore-color);
}

// In dark.css:
:root {
    --codeblock-ignore-color: rgba(255, 142, 0, .6);
}
// Same in ayu.css and light.css...

Now the whole CSS is handled in one place (rustdoc.css) whereas the theme files will only have to define a list of variables and that's it.

Considering that this migration is not trivial since we need to ensure at every step that we didn't change the UI by mistake, a lot of pull requests were made:

Cleaning up rustdoc's CSS

rustdoc went through a lot of UI changes. Because of that, the CSS changed quite a lot and some parts of it just became dead code. @notriddle started a big cleanup to remove them, greatly reducing the maintenance cost. Just so you have an idea, here is the complete list of his contributions for this. Each pull request has a description of why it was added and why it became unneeded so if you want some rustdoc history, I highly recommend reading them:

Other pull requests working on this subject were done by other people as well:

Conclusion

As you can see, there a lot of different ways to improve users' browsing experience. Hopefully, a lot more of them will come up in the future! If you have ideas of improvements, don't hesitate to open issues on the rust repository or to join us on the #rustdoc channel on zulip.

Posted on the 13/09/2022 at 11:00 by @GuillaumeGomez

Next article

rustdoc: Recent UI and UX changes in generated documentation 2

Previous article

rustdoc: Recent UI and UX changes in generated documentation
Back to articles list
RSS feedRSS feed