HTML Code Snippets with Syntax Highlighting
When switching to the new post layout, I took into account preserving syntax-highlighted code snippets. I planned to use a semi-manual method I described in How to Highlight Code Syntax in Google Slides. I assumed that the output of the Shiki playground will be pasted into a text editor as HTML. Guess what? That didn't work.
Before I dove into debugging, I realized that the playground output has only a single theme. The post layout uses both light and dark theme. After taking a look at a possibility of manually merging colors in the snippet, I scrapped this idea.
That's when I came up with an idea to use Shiki directly1—not through the playground.
The Script
After some tinkering, I came up with a JavaScript script to run with Node. Nothing too fancy. Use filename and language arguments to highlight file's contents with a given language's parser.
// Usage:
// node highlight.js FILE [LANG] | pbcopy
import { readFileSync } from "node:fs";
import { codeToHtml } from "shiki";
const fileName = process.argv[2];
const lang = process.argv[3] ?? "text";
const code = readFileSync(fileName, { encoding: "utf-8" }).replace(/\n$/, "");
const result = await codeToHtml(code, {
lang: lang,
themes: {
light: "github-light",
dark: "github-dark",
},
defaultColor: false,
});
console.log(result);
That's not the end of it, though! By default, Shiki outputs an HTML snippet that has light theme's colors and dark theme's CSS variables. To make the dark theme work an additional CSS code is required.
The CSS to Enable the Dual Theme
Browsing through the documentation, I didn't like the fact that CSS
snippets used !important
to enable
the dark theme. Luckily, I have spotted a
defaultColor
option. Using it moves light theme color's
to CSS variables. This allowed me to write the following code that
doesn't use !important
! đ
.shiki,
.shiki span {
color: var(--shiki);
background-color: var(--shiki-bg);
@media (prefers-color-scheme: light) {
--shiki: var(--shiki-light);
--shiki-bg: var(--shiki-light-bg);
}
@media (prefers-color-scheme: dark) {
--shiki: var(--shiki-dark);
--shiki-bg: var(--shiki-dark-bg);
}
}
Bonus: CSS to Add Line Numbers
If you would like to add line numbers like the ones I have, then this is the snippet to do that:
.shiki {
counter-reset: line;
span.line {
counter-increment: line;
&:nth-child(-n + 9) {
padding-left: 0.5rem;
}
&::before {
border-right: 1px solid var(--line-counter);
color: var(--line-counter);
content: counter(line);
margin-right: 0.75rem;
padding-right: 0.5rem;
}
@media (prefers-color-scheme: light) {
--line-counter: rgb(111, 111, 111);
}
@media (prefers-color-scheme: dark) {
--line-counter: hsl(210 10.5% 56.4%);
}
}
}
Optionally, you can move --line-counter
declarations to
the snippet enabling dark mode; that's how I have it!
Footnotes
- I know Shiki has a CLI. However, the CLI doesn't support the dual theming. âŠī¸