Documenter.jl icon indicating copy to clipboard operation
Documenter.jl copied to clipboard

Page load time: first visit and subsequent visits

Open xal-0 opened this issue 6 months ago • 7 comments

Even with fast internet, https://docs.julialang.org/ can take several seconds on first load, and almost as long for subsequent loads. This issue will attempt to summarize the ways we can improve this.

Image

Load optimization laundry list

Don't block rendering

  • [x] Don't block waiting for search index (#2700)
  • [ ] Don't block waiting for fontawesome, other font CSS
  • [ ] Remove require.min.js entirely (#2158)
  • [ ] Don't block for various other "info" scripts (siteinfo.js, versions.js)
  • [ ] Rendering should not block while loading themes that are not in use.
  • [ ] Set font-display: swap; for all fonts

Reduce size of assets

  • [ ] Don't pull in all of fontawesome (260 KiB) for just a handful of icons (consider using svg with <symbol> definitions)

  • [ ] Font subsetting

    We pull in JuliaMono (almost 1 MiB) for good Unicode coverage, but most pages have only ASCII. The CDN we use should serve properly subset fonts so only the necessary parts are downloaded. (Example: Lato from Google Fonts).

  • [ ] Render math before serving

    Could be undesirable to pull in KaTeX and a JavaScript interpreter as a depdency of Documenter.jl, but it's entirely possible to render all the math while building the docs and avoid the KaTeX script dependency entirely.

Cache more

  • [x] Add content hash to search index (#2700)

  • [ ] Host the official docs somewhere that allows us to set a reasonable Cache-Control: max-age

    GitHub pages adds a Cache-Control: max-age=600 header to everything. This hurts return visits to the docs more than almost anything els: if it has been more than 10 minutes since your last visit, we must validate everything in the cache. Unfortunately this is also something we have no control over.

Other random nits

  • [ ] Don't synthesize bold Lato

    Compare synthesized bold on the left, real bold on the right (Chromium macOS): Image

    Apologies for including this here but this is one of those things where it will drive you nuts once you notice it.

xal-0 avatar May 07 '25 00:05 xal-0

Thanks for looking into this!

Regarding fontawesome, I guess one could resurrect PR #2657 for partial progress towards getting rid of it. I can do that if there is interest (still have it in my local git repo).

fingolfin avatar May 07 '25 21:05 fingolfin

Regarding Host the official docs: if this is in reference to https://docs.julialang.org/ then this is out of scope for this repository and should be raised on the main Julia repository.

fingolfin avatar May 07 '25 21:05 fingolfin

On the topic of "font subsetting": are you suggesting we should split the JuliaMono into separate font files, for each class of symbols and serve them separately? And then the browser could only fetch the relevant .woff2 files, depending on what characters it actually needs to render on the page? I.e. for the sake of argument, if the page doesn't have any characters from the cyrillic Unicode range, then it will never try to download the juliamono-cyrillic.woff2 subset?

If so, this also seems like an upstream ask for https://github.com/cormullion/juliamono, to ensure that JuliaMono is distributed in a split way?

mortenpi avatar May 08 '25 04:05 mortenpi

Don't block for various other "info" scripts (siteinfo.js, versions.js)

I'm not against this necessarily, but these are really tiny files, so I am not sure it's worth the effort. If we do make these load async, we'd also have to make some of the UI (version selector and outdated docs warning) also wait for these to load.

Render math before serving

We already have an opt-in feature to do this for code highlighting. I think it might be good to have this as an opt-in feature for math-heavy manuals.

Rendering should not block while loading themes that are not in use.

This is a very reasonable idea, but I am not sure how easy it is to do, given our slightly complex theme switching mechanic. Any ideas if can inject the async attribute dynamically after the fact? But if it can be done, it would be great.

mortenpi avatar May 08 '25 04:05 mortenpi

When Fons was developing Pluto.jl, he asked for a reduced-glyph subset of JuliaMono. This is still released as

https://github.com/cormullion/juliamono/blob/master/webfonts/JuliaMono-RegularLatin.woff2

and

https://github.com/cormullion/juliamono/blob/master/webfonts/JuliaMono-BoldLatin.woff2

These have 630 glyphs each and are about 27KB.

I think his idea was to have this font downloaded initially, and then have the full version downloaded later. I don't know how or whether he did it though.

I suppose you could do font subsetting by making Documenter.jl generate a list of all used Unicode glyphs as part of the build process, and then conflate them into a set of ranges suitable for writing out as a custom CSS rule:

@font-face {
  unicode-range: U+0000-00FF, U+02BB-02BC,...;
}

If so, this also seems like an upstream ask for https://github.com/cormullion/juliamono, to ensure that JuliaMono is distributed in a split way?

This is unlikely to happen... :)

cormullion avatar May 09 '25 14:05 cormullion

In Pluto we optimized the JuliaMono font loading quite a bit, in the end we settled on this:

editor.css

@import url("./fonts/juliamono.css");

:root {
    --julia-mono-font-stack: JuliaMono, Menlo, "Roboto Mono", "Lucida Sans Typewriter", "Source Code Pro", monospace;
}

an-element-that-uses-juliamono {
    font-family: var(--julia-mono-font-stack);
}

fonts/juliamono.css

@font-face {
    font-family: JuliaMono;
    src: url("https://cdn.jsdelivr.net/gh/cormullion/[email protected]/webfonts/JuliaMono-RegularLatin.woff2") format("woff2");
    font-display: swap;
    font-weight: 400;
    unicode-range: U+00-7F; /* Basic Latin characters */
}

@font-face {
    font-family: JuliaMono;
    src: url("https://cdn.jsdelivr.net/gh/cormullion/[email protected]/webfonts/JuliaMono-BoldLatin.woff2") format("woff2");
    font-display: swap;
    font-weight: 700;
    unicode-range: U+00-7F; /* Basic Latin characters */
}

@font-face {
    font-family: JuliaMono;
    src: url("https://cdn.jsdelivr.net/gh/cormullion/[email protected]/webfonts/JuliaMono-Regular.woff2") format("woff2");
    font-display: swap;
    font-weight: 400;
}

@font-face {
    font-family: JuliaMono;
    src: url("https://cdn.jsdelivr.net/gh/cormullion/[email protected]/webfonts/JuliaMono-Bold.woff2") format("woff2");
    font-display: swap;
    font-weight: 700;
}

@font-face {
    font-family: JuliaMono;
    src: url("https://cdn.jsdelivr.net/gh/cormullion/[email protected]/webfonts/JuliaMono-RegularItalic.woff2") format("woff2");
    font-display: swap;
    font-weight: 400;
    font-style: italic;
}

Swap

The most important optimization is font-display: swap together with a fine-tuned fallback font stack. We spent some time on Windows, Mac and Linux finding fallback fonts that look very close to JuliaMono, resulting in --julia-mono-font-stack.

Subset

The regular and bold typefaces have a latin subset. If you have a page that only uses glyphs from this subset, the browser will only load the latin woff2 files which are much smaller.

Good CDN

We use jsdelivr with version-pinning to get good caching across pages. I think we also preload/prefetch the juliamono woff2 files from our index page.

fonsp avatar May 20 '25 10:05 fonsp

Btw @xal-0 thanks for your work, this is a really good list, spot on! I think the performance and look of the Julia doc pages is super important, it reflects the quality of our work.

fonsp avatar May 20 '25 10:05 fonsp