Extend Astro Syntax Highlighting

Shiki is by far my favorite tool for syntax highlighting. It is (now) ESM-compatible, framework-agnostic, even runtime-agnostic, and has native support for TextMate grammars, which are used by MANY code editors and IDEs. This allows you to quickly integrate Shiki wherever you need it, supporting whichever languages you want, and having the best theme out there for your needs.

Astro with its content-focused integrations, provides support for syntax highlighting in code blocks out of the box with Shiki if you are making use of Markdown (or MDX) or their <Code> component. Such integration saves a lot of time and makes it pretty easy to customize.

Recently, I updated this very blog from Astro v3 to Astro v4 (btw, their upgrade guide is awesome!) and noticed that while there were very minimal breaking changes between the two versions, one thing was never mentioned: In Astro v3, the configuration property markdown.shikiConfig.langs extended the list of languages for which Shiki enabled syntax highlighting; while in Astro v4, this same property now overrides the list of languages.

This one was pretty easy to catch as there is only one language (at the moment) that I use in this blog that is not part of the official list of languages included in Shiki: Caddyfile.

Checkout my post about HTTPS behind VPNs using Caddy!

After a quick dive into Shiki’s source code, I noticed that the 1.0 release made some big changes in the way languages are loaded, being this probably the reason behind the change from extending to overriding the language list via the configuration property.

In my case, I don’t want to manually “opt-in” all the languages that I may (or may not) be using in this blog, I just want to extend the available languages with the grammar files for those not-officially packed with Shiki… And it is actually pretty easy to do!

// astro.config.mjs

// 1. Pull in the languages that are bundled with Shiki
import { bundledLanguages } from 'shiki'
// 2. Import the grammar file for the language(s) you want to add
import caddyLang from 'path/to/caddyfile.tmLanguage.json' assert { type: 'json' }

export default defineConfig({
    // ...
    markdown: {
        shikiConfig: {
            // ...
            langs: [
                // 3. As bundled language are already registered,
                //    we just pull their names.
                ...Object.keys(bundledLanguages),
                // 4. Then we add our custom language(s) to the list
                {
                    id: 'caddyfile',
                    scopeName: 'source.Caddyfile',
                    aliases: ['caddy'],
                    ...caddyLang,
                },
            ]
        }
    }
})

That’s all! Now, the caddyfile language is back in this blog without needing to manually opt in to all the other languages.

Happy Easter!