Markup Features

Dated Apr 27, 2024; last modified on Sat, 27 Apr 2024

Syntax Highlighting

Previously, we’d highlight code on the client by loading src/lib/highlight.pack.js, a bundle downloaded from but served from our domain, and then execute hljs.highlightBlock on demand, e.g., on page load, when showing a card, etc. This doesn’t work well with a web-component-centric design. Running hljs.highlightBlock through possible Shadow DOM boundaries is a hassle.

Back in 2018 , we installed highlightjs, a shim for the official HighlightJS. Times have changed and has an official Node package, highlight.js . Now, on the server, compute the syntax-highlighted HTML and have it stored in Card.descriptionHTML. All that’s left is to provide CSS on the client.

LaTeX Rendering

Like syntax highlighting, our current approach is to process LaTeX on the client by calling MathJax.Hub.Queue(["Typeset", MathJax.Hub, elementId]) on demand. We did try to use it from the server back then, but that was going against the grain . The situation doesn’t seem to have changed since .

promises to be fast in the browser and easy to use for server-side-rendering. Will give KaTeX a shot and see how it works out. The main package provides syntax like const s = katex.renderToString('e = mc^2') which results in <span class="katex">...</span>.

Rendered string expanded with whitespace for legibility
<span class="katex">
  <span class="katex-mathml">
    <math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
      <semantics>
        <mrow>
          <mi>e</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup>
        </mrow>
        <annotation encoding="application/x-tex">e = mc^2</annotation>
      </semantics>
    </math>
  </span>
  <span class="katex-html" aria-hidden="true">
    <span class="base">
      <span class="strut" style="height: 0.4306em;"></span>
      <span class="mord mathnormal">e</span>
      <span class="mspace" style="margin-right: 0.2778em;"></span>
      <span class="mrel">=</span>
      <span class="mspace" style="margin-right: 0.2778em;"></span>
    </span>
    <span class="base">
      <span class="strut" style="height: 0.8641em;"></span>
      <span class="mord mathnormal">m</span>
      <span class="mord">
        <span class="mord mathnormal">c</span>
        <span class="msupsub">
          <span class="vlist-t">
            <span class="vlist-r">
              <span class="vlist" style="height: 0.8641em;">
                <span class="" style="top: -3.113em; margin-right: 0.05em;">
                  <span class="pstrut" style="height: 2.7em;"></span>
                  <span class="sizing reset-size6 size3 mtight">
                    <span class="mord mtight">2</span>
                  </span>
                </span>
              </span>
            </span>
          </span>
        </span>
      </span>
    </span>
  </span>
</span>

noted that on a math-heavy page, a page that would otherwise be 50K gets inflated to ~1MB after SSR. With compression, the difference is ~ the size of the compressed KaTeX library, so might as well do CSR. Each card is ~2KB in total, and looking at a cost of ~40KB per card. Skipping out on the MathML to save on data size isn’t worth it as MathML is used by ATs such as VoiceOver, Orca, JAWS, and NVDA.

Another challenge with using KaTeX is that parsing delimiters does not come out of the box. provides a renderMathInElement(elem: HTMLElement): void API, but assumes a browser environment, e.g., calling document.createDocumentFragment. provides a <katex-element> web component, but to know what to put in there, I’d still need delimited text – having all of the card description in a <katex-element> would lead to undesirable styling in non-math sections.

Can we solve ’s dependence on the DOM using ? Or can we extract the delimiter logic from ? The former will break when we upgrade katex, and the latter will miss out on future bug fixes.

What about ? That needs replacing with as the markdown-to-HTML converter, but that should be mostly fine. Even better, preserves language hints for code-blocks and should avoid the need for using , promising advantages over .

References

  1. highlight.js. highlightjs.org . Accessed Apr 27, 2024.
  2. typescript - import mathjax throws error @types/mathjax/index.d.ts is not a module. stackoverflow.com . Accessed Apr 27, 2024.
  3. MathJax-demos-node/simple at master · mathjax/MathJax-demos-node. github.com . Accessed Apr 27, 2024.
  4. KaTeX – The fastest math typesetting library for the web. katex.org . Accessed Apr 27, 2024.
  5. Problems with math rendering on the web (2020) | Hacker News. news.ycombinator.com . news.ycombinator.com . Accessed Apr 27, 2024.
  6. MathML Accessibility Gap Analysis | MathML Document Repository. w3c.github.io . Accessed Apr 27, 2024.
  7. Auto-render Extension · KaTeX. katex.org . Accessed Apr 27, 2024.
  8. georges-gomes/katex-element: `<custom-element>` for katex. github.com . Accessed Apr 27, 2024.
  9. goessner/markdown-it-texmath: Support TeX math equations with your Markdown documents. github.com . Accessed Apr 27, 2024.
  10. Showdownjs - A markdown to HTML converter. showdownjs.com . Accessed Apr 27, 2024.
  11. markdown-it demo. markdown-it.github.io . Accessed Apr 27, 2024.
  12. jsdom/jsdom: A JavaScript implementation of various web standards, for use with Node.js. github.com . Accessed Apr 27, 2024.