Node, bundlers and static sites
HaTeX.parse doesn't need a DOM, so documents can be rendered at build time and shipped as plain HTML. Readers then need no JavaScript to read them, and the page appears without waiting for KaTeX or the renderer.
Installing
HaTeX isn't published to npm. Install it from the repository, or copy dist/ into your project:
npm install github:mejistus/hatex katex
KaTeX is a peer dependency, used when you render at build time. Prism (prismjs) is optional.
Rendering in Node
import { readFileSync, writeFileSync } from 'node:fs';
import katex from 'katex';
import HaTeX from 'hatex'; // dist/hatex.mjs
HaTeX.use({ katex }); // before parse
const html = HaTeX.parse(readFileSync('post.tex', 'utf8'));
writeFileSync('post.html', `<article class="hatex">${html}</article>`);
CommonJS works the same way:
const katex = require('katex');
const HaTeX = require('hatex'); // dist/hatex.js
HaTeX.use({ katex });
With highlighting:
import Prism from 'prismjs';
import loadLanguages from 'prismjs/components/index.js';
loadLanguages(['python', 'bash']);
HaTeX.use({ katex, Prism });
examples/node/prerender.mjs is a complete script that renders a .tex file to a standalone page, taking the title from the front matter and printing lint warnings:
npm install
node examples/node/prerender.mjs in.tex out.html
The page around pre-rendered HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<link rel="stylesheet" href="hatex.css">
<article class="hatex">…output of parse…</article>
<!-- optional: page behaviour -->
<script src="hatex.js"></script>
<script>HaTeX.enhance(document.querySelector('.hatex'));</script>
katex.min.cssandhatex.cssare always needed.katex.min.jsis not: the maths is already typeset.hatex.jsandenhanceare needed only for what HTML can't do by itself: TikZ pictures, two columns,\resizeboxfitting, slides,\jiazhuinguji, smooth reference scrolling, copy buttons and zoom. Without them, the page is a readable single-column document, and TikZ boxes are empty.- When enhancing, pass the same options you would pass to
render, e.g.{ tikzSvgBase: '/tikz/' }.
TikZ at build time
TikZ can't be compiled in Node, because TikZJax needs a browser. The output of parse contains each picture's code and hash, and enhance in the reader's browser loads <tikzSvgBase><hash>.svg. So:
- compile the pictures once in a browser (in the editor example, or on the page itself with
tikzSvgBase: null); - save the SVGs from
HaTeX.tikzSvgsinto the folder that your build publishes; - build.
The hashes that parse computes in Node are the same as in the browser, so the saved files match. See Pre-rendering.
Bundlers
With Vite, webpack, esbuild or Rollup, import the ES module and the stylesheet:
import katex from 'katex';
import 'katex/dist/katex.min.css';
import HaTeX from 'hatex';
import 'hatex/hatex.css';
HaTeX.use({ katex });
HaTeX.render(document.querySelector('#post'), source);
The library is about 135 KB before minification (plus 26 KB of CSS) and has no dependencies of its own. TikZJax is not bundled; it is loaded from tikzjaxBase when needed.
A .tex file can be imported as a string with your bundler's raw import, e.g. import source from './post.tex?raw' in Vite.
Frameworks
HaTeX writes into a DOM element, so in React, Vue or Svelte, give it an element and render in an effect:
function Post({ source }) {
const ref = useRef(null);
useEffect(() => { HaTeX.render(ref.current, source); }, [source]);
return <article ref={ref} />;
}
Don't let the framework manage the element's children, since render replaces them. For server-side rendering, call parse on the server, send the HTML with dangerouslySetInnerHTML (or your framework's equivalent), and call enhance on the client after hydration.
Static-site generators
The pattern is the same for Eleventy, Hugo (through a Node step), Astro and others: a transform that turns .tex files into HTML with parse, a layout that includes the two stylesheets, and optionally hatex.js with an enhance call. The front matter in the % --- block can feed the generator's metadata; HaTeX.lint(source, { frontMatter: true }) checks it.
Security
The output is meant for your own writing. \href and \url accept only safe URL schemes, and text is HTML-escaped, but the renderer doesn't aim to be a sanitiser. If you render sources written by other people, pass the output through an HTML sanitiser such as DOMPurify, and allow <script type="text/x-tikz"> if you want their TikZ pictures.
Previous: The HTML output · Contents · Next: Checking sources