tivac.com

Static syntax highlighting with CodeMirror

I recently built a new version of the site for modular-css and struggled for a while with syntax highlighting. A previous iteration of the site had used prism.js, but I was never thrilled with how well its language grammars caught various edge cases. During development I also tried out shiki which is a really interesting idea in that it can natively read textmate themes. In theory this meant that it could exactly match the theme used for the REPL but in practice it made adjusting the theme incredibly awkward.

And then I began to wonder. I already had a theme I liked set up for the REPL and had even customized the grammar to support some of the custom modular-css bits and bobs. Could I use CodeMirror staticly while generating pages on the server? After some spelunking I found a section in the CodeMirror manual that indicated it should be possible, runmode/runmode.js.

Unfortunately that just links to an old version of the script itself, no usage info or anything else. I futzed around with the version of runmode/runmode.node.js I had in node_modules from installing CodeMirror to run on the client for the REPL but didn't have much luck. Some searching around led me to a post on Marijn Haverbeke's blog about the mode system, where the author of CodeMirror himself described how the mode system worked. I read that and... still couldn't really wrap my head around how to get it working until I stumbled onto a clue.

In fact, the syntax highlighting on this blog is powered by CodeMirror modes, driven by a browserless node.js version of runMode.

Great, then it was time to figure out how Marijn's blog works! Unsurprisingly it's open-source on Github and I found highlightCode.js which was just about exactly what I needed to get things going.

It took me a bit to figure out how to extend the node-specific version of CodeMirror and the browser version in the same way, but after some spelunking into the code and swearing about docs I figured it out. The important bits for me since I'm extending the existing text/css MIME-type in CodeMirror was to use .resolveMode("text/css") to get access to the existing mode and then using .defineMIME("text/modular-css") to add my own custom MIME type with extra behavior.

// Load CodeMirror itself
const codemirror = require("codemirror/addon/runmode/runmode.node.js");

// Load up the mode you care about
require(`codemirror/mode/css/css.js`);

// Get a reference to the original CSS mode
const mode = codemirror.resolveMode("text/css");

// Create a custom mime type mode for modular-css!
codemirror.defineMIME("text/modular-css", {
...mode,

propertyKeywords : {
...mode.propertyKeywords,
composes : true,
},

// ...
});

There's a bit of plumbing I'm not showing here that connects CodeMirror and markdown-it inside a custom rollup plugin for building the site, but that's pretty specific to my needs. Here it is if you want to see the full thing, rollup-plugin-md.js and codemirror-mcss-mime.js handles extending the default text/css mode.

Now that it's all figured out m-css.com has consistent syntax highlighting that understands composes and @value across the entire site whether it's client-side in the REPL or one of the static embedded code blocks.

← Home