Maak kennis met MicroLighter

Belangrijkste kenmerken

  • Geen afhankelijkheden (zero-dependencies).
  • Ongeveer 2kb (geminificeerd + gegzipped).
  • Maakt gebruik van CSS ::highlight(token-name) in plaats van <span>-elementen.
  • Maakt gebruik van de taalgrammatica's van Textmate.
  • Mensvriendelijke light-dark() thema's.
  • Alle talen/grammatica's worden on-demand geladen.
  • Verplaatst alle niet-highlight-functionaliteit naar een <micro-lighter> custom element.

De motivatie achter MicroLighter

Op een gegeven moment ging de syntax highlighting op mijn Jekyll-blog kapot. In de loop der jaren heb ik verschillende syntax highlighters gebruikt (zoals Highlight.js, PrismJS, Rouge, Shiki, etc.) en ik heb de voor- en nadelen van verschillende client-side en server-side implementaties ervaren. Toen ik opnieuw een keuze moest maken, wist ik dat ik de techniek van Bramus wilde verkennen: syntax highlighting met de CSS Custom Highlights API.

De CSS ::highlight() pseudo-klasse heeft enkele beperkingen; geen cursief, geen vetgedrukt en geen wisseling van lettertype. Maar verder is het een erg mooie manier om idiomatisch uit te drukken "ik wil dit token highlighten" via CSS, in plaats van overal spans in de HTML te injecteren. Door de Highlight API te gebruiken, voorkom ik DOM-mutaties. De scope van de library wordt hierdoor beperkt tot: codeblokken scannen met regex-patronen en CSS.highlights.set(category, textRanges) versturen om de codeblokken te highlighten.

Taalondersteuning en efficiëntie

Ik heb niet veel syntax highlighting nodig op deze site. Niet alle berichten bevatten code en mijn codevoorbeelden zijn hooguit vijftien regels lang. Mijn uitdaging is echter dat ik vaak van taal wissel. In één bericht gebruik ik soms HTML, CSS en JavaScript. Hier een beetje bash, daar wat ruby, en als traktatie wat markdown. Omdat ik zoveel talen gebruik, groeide de complexiteit voorbij de grenzen van mijn eigen regex-vaardigheden. Daarom besloot ik leunen op de gevestigde collecties patronen van Textmate, die ook door VS Code worden gebruikt. Voor ik het wist, kon mijn kleine highlighter bijna elke taal aan.

Omdat ik vaak verschillende talen gebruik, heb ik als principe vastgesteld dat alle taalgrammatica's on-demand automatisch geladen moeten worden. Dit vermindert de configuratie en de bundle-grootte, zodat je alleen betaalt (in bandbreedte) voor wat je daadwerkelijk gebruikt.

Styling en Thema's

Geïnspireerd door de vereenvoudigde token-categorieën van PrismJS, heb ik de gedetailleerde token-categorieën van Textmate platgeslagen tot een mensvriendelijkere set, waardoor styling eenvoudiger wordt. Daarnaast heb ik een groot irritatiepunt bij de styling van codeblokken aangepakt: vaak zijn lichte en donkere thema's aparte entiteiten. Ik heb deze samengevoegd tot één thema door gebruik te maken van light-dark().

Architectuur en Web Components

De laatste grote ontwerpkeuze was dat de syntax highlighter slechts één taak moest hebben: de taal herkennen en de code in die taal highlighten. Met deze richtlijn in mijn achterhoofd heb ik alle extra functionaliteit (zoals regelnummers, etc.) verplaatst naar een web component. De vanilla web component voegt ongeveer 1 KiB toe aan de grootte, maar het voelt juist goed om UI-elementen te plaatsen in een UI-primitief zoals native custom elements. De ShadowDOM-inkapseling maakt het bovendien eenvoudig om de code te scheiden van de presentatielaag.

Ik ben overduidelijk een web component-fanaat, maar het voelt als een goede scheiding van verantwoordelijkheden in plaats van alles in de kernbibliotheek te proberen te proppen.

Aan de slag

Om MicroLighter op je site te gebruiken, raad ik de zelf-initialiserende geminificeerde bundle aan, maar ik lever ook ESM en een web component.

npm install microlighter
<script type="module" src="path/to/microlighter/microlighter.min.js"></script>

Zoals ik eerder zei, bevatten niet al mijn berichten syntax highlighting. Daarom wacht ik zelfs met het importeren van het script totdat ik weet of er een pagina met code is:

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

Je kunt de ESM-versie gebruiken als je zelf iets geavanceerder wilt doen:

import { highlightAll } from 'microlighter'

highlightAll({
  selector: 'pre.onlyTheseGetHighlights'
})

En je kunt de web component gebruiken als je de extra functies wilt waar ik over sprak:

<micro-lighter data-syntax-theme="github" line-numbers controls="copy">
  <pre><code>Code goes here</code></pre>
</micro-lighter>

Web component-klassen zijn bovendien goed uitbreidbaar. Als ik iets niet ondersteun dat jij nodig hebt, kun je dit "forken" door de basisklasse uit te breiden en je eigen functies toe te voegen.

Eigen thema's maken

Tot slot kun je een van de meegeleverde thema's gebruiken of er zelf een maken. De basisstructuur is als volgt:

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

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

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

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

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

En dat is MicroLighter. Als je het gaat gebruiken of uitproberen, laat me dan weten wat je ervan vindt.