Introducing MicroLighter

tobr2 pts0 comments

Introducing Microlighter - daverupert.com

I made a smol client-side syntax highlighter that uses the CSS Custom Highlights API called MicroLighter. I’ll talk about why I made it in a bit but first I wanted to… ahem… highlight… some of the features.

Zero-dependencies

~2kb minifed+gzipped

Uses CSS ::highlight(token-name), not spans

Leverages Textmate’s language grammars

Human-readable light-dark() themes

All languages/grammars loaded on-demand

Moves all non-highlight functionality to a custom element

At some point I broke syntax highlighting on my Jekyll blog. I’ve used a handful of syntax highlighters over the years (Highlight.js, PrismJS, Rouge, Shiki, etc, etc) and I’ve felt the trade-offs between different client-side and server-side implementations. Faced with picking another, I knew I wanted to explore Bramus’s technique of syntax highlighting with the CSS Custom Highlights API.

There’s some limitations with the CSS ::highlight() pseudo; no italics, no bold, no font swapping – but otherwise it’s pretty cool syntax to idiomatically express “I want to highlight this token” via CSS instead of injecting spans everywhere. Using the Highlight API means I avoid any DOM mutation and the scope of the library shrinks down to: scan code blocks using regex patterns and send CSS.highlights.set(category, textRanges) to highlight the code blocks.

I don’t need much syntax highlighting on this site. Not all posts have code and my code samples are a whole fifteen lines long at best. My struggle is that I swap languages often. I’ll do HTML, CSS, and JavaScript all in the same post. A little bash here, a little ruby there, some markdown as a treat. Using so all those languages grew the complexity beyond the limits of my regex-fu, so I decided to lean on Textmate’s established collections of patterns used by VS Code. And before I knew it, my little highlighter could do almost any language.

Knowing I use different languages often, one principle I established was that all language grammars should be auto-loaded on-demand to reduce configuration and bundle size. That way you only pay for what you use.

Inspired by PrismJS’s simplified token categories I flattened down Textmate’s granular token categories to a more human-friendly set, making it easier to style. In addition to that, one enormous nit-pick I have with codeblock styling is that light and dark themes are separate entities, so I merged them into one theme using light-dark().

The last big opinion I baked in was that I wanted the syntax highlighter to do one job: infer language and highlight code in that language. With that a guideline, I moved all extra functionality (like line-numbers, etc) over to a web component. The vanilla web component adds about ~1 KiB in size, but co-locating UI into a UI primitive like native custom elements feels right and the ShadowDOM encapsulation makes it easy to separate the code from the presentational UI.

Obviously, I’m a web component pervert but it feels like a great separation of concerns versus trying to cram everything into core library.

Try it out

To get started on your site, I’d use the self-initializing minified bundle, but I’m also shipping ESM and a web component.

npm install microlighter

Like I said above, not all my posts have syntax highlighting, so I even wait to import the script unless I know there’s a page with code on it.

if(document.querySelector('pre>code').length) {<br>import('path/to/microlighter/microlighter.min.js');

You can use the ESM version if you’re going to do something fancy yourself:

import { highlightAll } from 'microlighter'

highlightAll({<br>selector: 'pre.onlyTheseGetHighlights'<br>})

And you can also use the web component if you want those extra features I talked about:

Code goes here

Web component classes are pretty extendible too so if I don’t support something you need, you can “fork” it by extending the base class and adding your own features.

Lastly, you can use one of the pre-supplied themes or roll your own. The basic structure is:

/**<br>* Setup semantic `--syntax-*` tokens<br>* @value background | foreground | comment | keyword |<br>* operator |string | constant | function | type | variable |<br>* property | tag | selector | inserted | deleted<br>*/<br>[data-syntax-theme="my-theme-name"] {<br>color-scheme: light dark;

/* Code block tokens */<br>--syntax-background: light-dark(#f8f8f8, #3a3a3a);<br>--syntax-foreground: light-dark(#3a3a3a, #f8f8f8);

/* Highlight tokens */<br>--syntax-comment: light-dark(#6e7781, #8b949e);<br>--syntax-function: light-dark(#8250df, #d2a8ff);<br>/* ...etc... */

[data-syntax-theme="my-theme-name"] pre:has(code) {<br>background-color:var(--syntax-background);<br>color:var(--syntax-foreground)

::highlight(comment) { color: var(--syntax-comment) }<br>::highlight(function) { color: var(--syntax-function) }<br>/* ...etc... */

And that’s MicroLighter. If you end up using it and trying it out, let me know what you think.

syntax highlight code microlighter light dark

Related Articles