How I use Shiki in Eleventy

• 2 min read

When I migrated to Eleventy one crucial feature I wanted to keep was the server-rendered code blocks.

In the Laravel version of my site, I used the spatie/laravel-shiki package for this, which was a wrapper around the Node binary of Shiki. As Eleventy is already running in JavaScript, adding Shiki to the build was quite easy.

Thanks to this great post by Raphael Höser, I got Shiki running in less than 15 minutes.


The perfectionist I am, I noticed that the cold-start of running Eleventy took a bit longer: instead of 0.5 seconds it took 3 seconds to boot the Eleventy dev server.

I can see you – dear reader – rolling your eyes at what seems like an insignificant delay.
The three seconds to start the dev server is still quite fast compared with the many other static site generators out there.
But still, I wanted to find a solution, that adding more and more posts with code blocks doesn't increase the boot time of my Eleventy instance further.

So I've added a caching layer to my Shiki-plugin using flat-cache.
The plugin now looks like this.

import {createHash} from 'crypto';
import flatCache from 'flat-cache';
import {resolve} from 'node:path';

export default async function (eleventyConfig) {
  // empty call to notify 11ty that we use this feature
  // eslint-disable-next-line no-empty-function
  eleventyConfig.amendLibrary('md', () => {});

  eleventyConfig.on('eleventy.before', async () => {

    const shiki = await import('shiki');
    const highlighter = await shiki.getHighlighter({
      themes: ['github-dark', 'github-light'],
      langs: [
        'html',
        'blade',
        'php',
        'yaml',
        'js',
        'ts',
        'shell',
        'diff',
        ],
    });

    eleventyConfig.amendLibrary('md', function (mdLib) {
      return mdLib.set({
        highlight: function (code, lang) {
          const hash = createHash('md5').update(code).digest('hex');
          const cache = flatCache.load(hash, resolve('./.cache/shiki'));
          const cachedValue = cache.getKey(hash);

          if (cachedValue) {
            return cachedValue;
          }

          let highlightedCode = highlighter.codeToHtml(code, {
            lang: lang,
            themes: {
              light: 'github-dark',
              dark: 'github-dark',
            }
          });

          cache.setKey(hash, highlightedCode);
          cache.save();

          return highlightedCode;
        },
      });
    }
    );
  });
}

The code is very similar to Raphael's solution. The big difference is that in my markdown highlight-function, I generate a hash value of the to be highlighted code. This hash acts as the cache key. If the cache is a hit, the cached value is displayed. If the cache is a miss, Shiki will highlight the given code block and store the value in the file cache.

This caching mechanism reduced Eleventy's boot time back to 1 second.[1]


  1. I'm sure the file-cache is now the bottleneck and a in-memory cache driver like Redis would bring down boot time even more. But let's keep things simple. ↩︎