Tale of the Tape: Highlight.js vs Shiki

avatar
Simon MacDonald
November 9, 2021

Architect Logo Photo by Prateek Katyal

One of the best things about JavaScript is the ecosystem. There always seems to be a package to accomplish your task. However, one of the worst things about JavaScript is that many packages often do the same thing.

It’s not always obvious which package is best. How do we know which one to use? Do we judge by:

Since we will be executing our code in an AWS Lambda function, we will want to keep the following best practices in mind:

  1. Minimize your deployment package size to its runtime necessities
  2. Control the dependencies in your function’s deployment package
  3. Fast execution

This post will compare two popular packages that work with markdown-it for syntax highlighting in JavaScript, highlight.js, and shiki.

Popularity

First, let’s take a look at each package’s popularity.

highlight.js Popularity shiki
5,526,141 weekly npm downloads 219,361
19.1k GitHub stars 2.7k

In this case, highlight.js is the runaway winner, but popularity is not the best way to compare packages. Checking in with both packages’ GitHub repositories, you can tell that both are under active development and have had new releases in the past month.

That didn’t tell us too much, so let’s head over to the weigh-in.

Weight

Using BundlePhobia to check the bundle size, shiki comes out on top as it is the lighter package.

highlight.js Weight shiki
886.4kB bundle size 108.4kB
279.5kB bundle size + gzip’d 30.9kB
5.59s download time (slow 3G) 0.62s
319ms download time (4G) 35ms

But let’s go a bit deeper by running slow-deps. This command will show you the size and install time of each package, including its dependencies.

npx slow-deps
Analyzing 2 dependencies...
[====================] 100% 0.0s
------------------------------------------
| Dependency   | Time  | Size   | # Deps |
------------------------------------------
| shiki        | 1.5s  | 9.1 MB | 6      |
| highlight.js | 824ms | 3.9 MB | 1      |
------------------------------------------

While shiki starts off looking good in this category, highlight.js comes back strong as it doesn’t have any external dependencies, while shiki has five increasing its unpacked size to 9.1 MB.

Functionality

Okay, highlight.js is out to an early lead, but how well do these two packages work? We’ll run the same text through both packages and see what happens.

highlight.js

The following code:

const hljs = require('highlight.js/lib/common');

const html = hljs.highlight(`// async
let arc = require ('@architect/functions')
exports. handler = arc. events. subscribe (handler)
async function handler (event) {
console. log (event)
return
}`, {language: 'js'}).value

console.log(html)

produces the following output when viewed in a browser.

highlight.js

shiki

While similar code using shiki:

const shiki = require('shiki')

shiki
  .getHighlighter({theme: 'nord'})
  .then(highlighter => {
    let html = highlighter.codeToHtml(`// async
    let arc = require ('@architect/functions')
    exports. handler = arc. events. subscribe (handler)
    async function handler (event) {
    console. log (event)
    return
    }`, 'js')
    console.log(html)
  })

It looks like this when viewed in a browser.

shiki

What's the difference?

I’ll forgive you if it doesn’t jump out at you, but the exports keyword is colored differently in highlight.js and shiki. Other than that, both packages do a great job of highlighting the code using our Atom One Dark theme. Advantage, no one.

Speed

Finally, let’s compare the speed of the two packages. We’ll add console.time to our above code to profile how long each package takes to execute.

highlight.js Speed shiki
6.588ms time 312.826ms
$0.0000000147 cost $0.0000006573
$0.0147 cost per 1M executions $0.6573

And here is the point where highlight.js lands the knockout punch. Highlight.js ends up being 44 times faster than shiki. Speed is essential to us for two reasons:

  1. We deliver the HTML via Lambda functions, and we want the quickest execution time possible to reduce the time to first byte.
  2. Lambdas are billed in milliseconds, so lower execution times will help keep the cost low.

Conclusion

After evaluating the two packages, it was clear that highlight.js is the best package for our use case, coming out on top in three of the four categories we evaluated.

highlight.js Category shiki
🟢 Popularity 🔴
🟢 Weight 🔴
🟡 Functionality 🟡
🟢 Speed 🔴

That doesn’t mean you shouldn’t use shiki as your use case may be different than ours. For instance, if you are doing static site generation (SSG), you may not be as concerned about the overall build time as your end-user will never see that delay.

We are trying to highlight (pun intended) that it pays to do your due diligence when choosing a package. The most popular package may not end up being the best choice, and you may see wild swings in performance that could affect your final choice.