Tale of the Tape: Highlight.js vs Shiki
by Simon MacDonald
on
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:
- GitHub stars
- npm downloads
- Package size
- Performance
Since we will be executing our code in an AWS Lambda function, we will want to keep the following best practices in mind:
- Minimize your deployment package size to its runtime necessities
- Control the dependencies in your function’s deployment package
- 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.
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.
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:
- We deliver the HTML via Lambda functions, and we want the quickest execution time possible to reduce the time to first byte.
- 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.