The documentation concerning
the npm modules of

Shiki-Twoslash

In which markdown code samples are powered by
the syntax engine of Visual Studio Code
mixed with the TypeScript compiler’s information

Decoration

By orta therox

Purveyor of renowned open source code
and ex-TypeScript compiler team member

Shiki Logo
The word 'shiki'

Visual Studio Code’s syntax highlighter packaged for running in a web browser and statically via Node.js.

Supports all possible languages available on the VS Code extension marketplace. That’s over 200 languages. All you need is a .tmlanguage file for anything not shipped with Shiki.

Shiki colours your code with any VS Code theme. That’s 4900+ last time we checked.

Fig 1.

A diagram showing syntax tokens

The word 'twoslash'

Think of twoslash as a pre-processor for your code-samples.

Twoslash is a JavaScript and TypeScript markup language. You can write a single code sample which describes an entire JavaScript project.

Twoslash uses the same compiler APIs as your text editors to provide type-driven hover information, accurate errors and type callouts.

Each code sample is compiled in isolation, so you can be certain all your examples still compile and work right a few major versions down the line.

Fig 2.

A diagram showing a twoslash code sample being converted to HTML

The word 'shiki'
The word 'twoslash'

Mixing these two ideas is Shiki Twoslash. The goal being that you can write ergonomic code samples which are backed by the TypeScript compiler.

All code samples use Shiki, then you can opt-in to have Twoslash markup inside specific TypeScript / JavaScript code blocks.

Shiki Twoslash is built to generate completely server-side syntax highlighted code samples with no reliance on client-side JavaScript.

Fig 3.

A diagram of markdown -> HTML with Shiki Twoslash

#Chapter 1:
Twoslash Markup

By default all codeblocks in Shiki Twoslash act like traditional static code samples, making Shiki Twoslash backwards compatible with existing codebases.

ts
// Removes 'readonly' attributes from a type's properties
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
 
type LockedAccount = {
readonly id: string;
readonly name: string;
};
 
type UnlockedAccount = CreateMutable<LockedAccount>;
 

However, on JavaScript-y code samples, you can opt-in a code sample to use Twoslash. Try moving your cursor into this code sample:

ts
// Removes 'readonly' attributes from a type's properties
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
 
type LockedAccount = {
readonly id: string;
readonly name: string;
};
 
type UnlockedAccount = CreateMutable<LockedAccount>;
 

The name Twoslash refers to specially formatted comments which can be used to set up your environment, like compiler flags or separate input files. For example, here is a code sample showing export/importing a function:

Source
ts
// @module: esnext
// @filename: maths.ts
export function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}
// @filename: index.ts
import {absolute} from "./maths"
const value = absolute(-1)
Output
ts
// @filename: maths.ts
export function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}
 
// @filename: index.ts
import {absolute} from "./maths"
const value = absolute(-1)
 

Compiler flag comments are removed from the output, but we keep the filename around to help people understand that you've made a multi-file code sample.

You can write comment queries to have the twoslash powered code-samples highlight types without user interaction.

Source
ts
// @module: esnext
// @filename: maths.ts
export function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}
// @filename: index.ts
import {absolute} from "./maths"
const value = absolute(-1)
// ^?
Output
ts
// @filename: maths.ts
export function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}
 
// @filename: index.ts
import {absolute} from "./maths"
const value = absolute(-1)
const value: number

And if a code sample becomes un-focused, you can snip it down to just the bit that matters. The compiler still verifies everything ahead of time.

Source
ts
// @module: esnext
// @filename: maths.ts
export function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}
// @filename: index.ts
// ---cut---
import {absolute} from "./maths"
const value = absolute(-1)
// ^?
Output
ts
import {absolute} from "./maths"
const value = absolute(-1)
const value: number

To some extent, anything your editor can show you about code, Twoslash can show. For example, here is the real auto-complete for an express app in JS:

Source
js
// @noErrors
// @esModuleInterop
import express from "express"
const app = express()
app.get("/", function (req, res) {
res.sen
// ^|
})
app.listen(3000)
Output
js
import express from "express"
const app = express()
 
app.get("/", function (req, res) {
res.sen
         
})
 
app.listen(3000)
 

Are you wondering where the types come from? Express is a JavaScript library, it does not ship types. During the build process Shiki-Twoslash can use types from your app’s node_modules folder. I just had to run: yarn add @types/express.

You probably don't want to only show golden-path code too, showing how code goes wrong is also a critical way to understand code. Shiki Twoslash has native support for TypeScript compiler errors.

Source
ts
// @errors: 2339
const welcome = "Tudo bem gente?"
const words = welcome.contains(" ")
Output
ts
const welcome = "Tudo bem gente?"
const words = welcome.contains(" ")
Property 'contains' does not exist on type '"Tudo bem gente?"'.2339Property 'contains' does not exist on type '"Tudo bem gente?"'.

You see how we declared which errors were expected in the source? That means if this code sample errors with something else, Shiki Twoslash will fail to render.

Failing rocks because your CI will tell you that your code samples are out of date.

#Chapter 2:
Shiki Twoslash

Twoslash Shiki is a polite but hard fork of the Shiki code rendering engine. Let's look at a few of the Shiki Twoslash features.

Multi-theme rendering gives you the chance to set up your own custom color themes ahead of time. Shiki Twoslash will render each codeblock multiple times. For example, rendering with these settings uses the site theme and every shipped Shiki theme.

ts
{ "themes": "../shiki-twoslash", "dark-plus", "github-dark", "github-light", "light-plus", "material-theme-default", "material-theme-lighter", "min-light", "min-dark", "monokai", "slack-theme-ochin", "solarized-light", "nord", "slack-theme-dark-mode", "material-theme-ocean", "solarized-dark", "material-theme-palenight"}

Turns into:

ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 
ts
interface Todo {
title: string;
}
 
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
 
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.
 

Each code sample has the theme name as a class:

ts
<pre class="shiki dark-plus" style="..."

Giving you the chance to use CSS to hide the ones which should not be seen.

css
@media (prefers-color-scheme: light) {
.shiki.dark-plus {
display: none;
}
}
@media (prefers-color-scheme: dark) {
.shiki.light-plus {
display: none;
}
}

Highlighting code in your sample can be done via codefence comments:

Source
ts
```ts twoslash {1, 3-4}
function greet(person: string) {
console.log(`Hello ${person},`)
console.log(`How do?`)
}
greet("Maddison")
```
Output
ts
function greet(person: string) {
console.log(`Hello ${person},`)
console.log(`How do?`)
}
 
greet("Maddison")
 

Shiki Twoslash uses CSS classes to set up the highlighting, so style-wise, it's all up to you.

Code blocks which are atomic is great, but can get repetitive in your markdown file. To avoid constantly repeating yourself, Shiki Twoslash has a simple includes system where you can create a hidden codeblock which is imported in parts into your code samples.

Source
markdown
### Adding Values
```twoslash include main
const a = 1
// - 1
const b = 2
// - 2
const c= 3
```
Let's talk a bit about `a`:
```ts twoslash
// @include: main-1
```
`a` can be added to another number
```ts twoslash
// @include: main-1
// ---cut---
const nextA = a + 13
```
Look what happens when you add `a + b`
```ts twoslash
// @include: main-2
// ---cut---
const result = a + b
// ^?
```
Finally here is `c`:
```ts twoslash
// @include: main
// ---cut---
c.toString()
```
Output

Adding Values

Let's talk a bit about a:

ts
const a = 1

a can be added to another number

ts
const nextA = a + 13

Look what happens when you add a + b

ts
const result = a + b
const result: number

Finally here is c:

ts
c.toString()

#Chapter 3:
Integrations

I built plugins for most of the big static site generators in the JavaScript ecosystem. These are production ready, but aside from Gatsby, haven't had a true stress test yet.

The goal of these plugins is to get the markdown parsing set up, then you add the CSS and decide how you want to style it.

Gatsby plugin

Add the package, edit your gatsby-config.js, add CSS.

gatsby-remark-shiki-twoslash

Docusaurus preset

Add the package, edit your docusaurus.config, add CSS.

docusaurus-preset-shiki-twoslash

VuePress plugin

Add the package, edit your ./vuepress/config.ts, add CSS .

vuepress-plugin-shiki-twoslash

Hexo plugin

Add the package, edit your ./config.yml add CSS,

hexo-shiki-twoslash

11ty plugin

Add the package, edit your .eleventy.js, add CSS.

eleventy-plugin-shiki-twoslash

These generator plugins are powered by two markdown engine plugins. Of those, remark-shiki-twoslash does most of the work.

remark-shiki-twoslashmarkdown-it-shiki-twoslash

You can use these libraries to set up in almost any JavaScript tool. There are examples in the Shiki Twoslash monorepo of working with Next.js, Elder.js and MDX.

I'm open to adding more examples.

#Chapter 4:
Tooling

No markdown document is an island. To build out a corpus of markdown documents which rely on Twoslash there are some additional tools which might come in handy.

Twoslash CLI

Render documents via the terminal and verify the code samples all pass. This site uses the CLI for all of the above code samples.

twoslash-cli

Twoslash VS Code

Adds twoslash markup auto-complete to code samples, and offers a one-click link to a Twoslash repl with a reference on the TypeScript website.

vscode-twoslash

Twoslash Playground

Share and create repros of Twoslash code samples. Contains a full API reference for Twoslash commands.

Playground

#Vision

I intend for Shiki Twoslash to be a very long term project. Initially created to power the TypeScript website and handbook, Shiki Twoslash has the potential for being a solid foundation for almost any website which describes JavaScript-y code.

Extracting Shiki Twoslash, documenting, improving and abstracting into generator plugins is work I do on my own time and if that is the sort of work you want to see more of, consider sponsoring me on GitHub Sponsors

github.com/sponsors/orta/

Have a good one!

Big thanks to Danger, Hayes, Pine for Shiki, Ryan Cavanaugh for the idea, starting code and optimism, Nicholas Rougeux whose design work helped me really nail the aesthetic I wanted, The Happy Chappo for art and finally all the folks who helped out build the TypeScript website in Discord.

https://github.com/shikijs/twoslash

undefined