Skip to content

shikiji-twoslash

NPM versionNPM downloadsGitHub

A Shikiji transformer for TypeScript TwoSlash, provide inline type hover inside code blocks. Inspired by shiki-twoslash.

Install

bash
npm i -D shikiji-twoslash

Unlike shiki-twoslash that wraps around shiki, this package is a transformer addon to Shikiji. This means that for every integration that supports Shikiji transformers, you can use this package.

ts
import {
  
codeToHtml
,
} from 'shikiji' import {
transformerTwoSlash
,
} from 'shikiji-twoslash' const
html
= await
codeToHtml
(`console.log()`, {
lang
: 'ts',
theme
: 'vitesse-dark',
transformers
: [
transformerTwoSlash
(), // <-- here
// ... ], })

Just like shiki-twoslash, the output is unstyled. You need to add some extra CSS to make them look good.

If you want to run TwoSlash on browsers or workers, reference to the CDN Usage section.

Renderers

Thanks to the flexibility of hast, this transformer allows customizing how each piece of information is rendered in the output HTML with ASTs.

We provide two renderers built-in, and you can also create your own:

rendererClassic

Source code

This is the default renderer that aligns with the output of shiki-twoslash.

You might need to reference shiki-twoslash's CSS to make it look good. Here we also copied the CSS from shiki-twoslash but it might need some cleanup.

rendererRich

Source code

This renderer provides a more explicit class name that is always prefixed with twoslash- for better scoping. In addition, it runs syntax highlighting on the hover information.

ts
import { 
rendererRich
,
transformerTwoSlash
} from 'shikiji-twoslash'
transformerTwoSlash
({
renderer
:
rendererRich
() // <--
})

Here are a few examples with the built-in style-rich.css:

ts
interface Todo {
  
title
: string
} const
todo
:
Readonly
<Todo> = {
title
: 'Delete inactive users'.
toUpperCase
(),
}
todo
.title = 'Hello'
Cannot assign to 'title' because it is a read-only property.
Number
.p
  • parseFloat
  • parseInt
  • prototype
arseInt
('123', 10)
// //
ts
import { 
getHighlighterCore
} from 'shikiji/core'
const
highlighter
= await
getHighlighterCore
({})
const
a
= 1
Custom log message
const
b
= 1
Custom error message
const
c
= 1
Custom warning message
Custom annotation message

Options

Explicit Trigger

When integrating with markdown-it-shikiji or rehype-shikiji, we may not want TwoSlash to run on every code block. In this case, we can set explicitTrigger to true to only run on code blocks with twoslash presented in the codeframe.

ts
import { 
transformerTwoSlash
} from 'shikiji-twoslash'
transformerTwoSlash
({
explicitTrigger
: true // <--
})
md
In markdown, you can use the following syntax to trigger TwoSlash:

```ts
// this is a normal code block
```

```ts twoslash
// this will run TwoSlash
```

Recipes

CDN Usage

By default @typescript/twoslash runs on Node.js and relies on your local system to resolve TypeScript and types for the imports. Import it directly in non-Node.js environments would not work.

Luckily, TwoSlash implemented a virtual file system, which allow you to provide your own files for TypeScript to resolve in memory. However, loading these files in the browser is still a challenge. Thanks to the work on the TypeScript WebSite, the TypeScript team has provided some utilities to fetch types on demand through CDN, they call it Automatic Type Acquisition (ATA).

We make tiny wrappers around the building blocks and provide an easy-to-use API in twoslash-cdn. For example:

js
// TODO: Replace with explicit versions in production
import { createTransformerFactory, rendererRich } from 'https://esm.sh/shikiji-twoslash@latest/core'
import { codeToHtml } from 'https://esm.sh/shikiji@latest'
import { createStorage } from 'https://esm.sh/unstorage@latest'
import indexedDbDriver from 'https://esm.sh/unstorage@latest/drivers/indexedb'
import { createTwoSlashFromCDN } from 'https://esm.sh/twoslash-cdn@latest'

// ============= Initialization =============

// An example using unstorage with IndexedDB to cache the virtual file system
const storage = createStorage({
  driver: indexedDbDriver({ base: 'twoslash-cdn' }),
})

const twoslash = createTwoSlashFromCDN({
  storage,
  compilerOptions: {
    lib: ['esnext', 'dom'],
  },
})

const transformerTwoSlash = createTransformerFactory(twoslash.runSync)({
  renderer: rendererRich(),
})

// ============= Execution =============

const app = document.getElementById('app')

const source = `
import { ref } from 'vue'

console.log("Hi! Shikiji + TwoSlash on CDN :)")

const count = ref(0)
//     ^?
`.trim()

// Before rendering, we need to prepare the types, so that the rendering can happen synchronously
await twoslash.prepareTypes(source)

// Then we can render the code
app.innerHTML = await codeToHtml(source, {
  lang: 'ts',
  theme: 'vitesse-dark',
  transformers: [transformerTwoSlash],
})

Integrations

  • VitePress - Enable TwoSlash support in VitePress.

Released under the MIT License.