Example Twoslash

This post is to see twoslash


@shikijs/twoslash

A Shiki transformer for Twoslash, provide inline type hover inside code blocks.

TwoSlash Notation Reference.

Install

npm install -D @shikijs/twoslash

This package is a transformer addon to Shiki. This means that for every integration that supports passing Shiki transformers, you can use this package.

ts
import {
  function transformerTwoslash(options?: TransformerTwoslashIndexOptions): ShikiTransformer
Factory function to create a Shiki transformer for twoslash integrations.
transformerTwoslash
,
} from '@shikijs/twoslash' import { const codeToHtml: (code: string, options: CodeToHastOptions<BundledLanguage, BundledTheme>) => Promise<string>codeToHtml, } from 'shiki' const const html: stringhtml = await function codeToHtml(code: string, options: CodeToHastOptions<BundledLanguage, BundledTheme>): Promise<string>codeToHtml(`console.log()`, { lang: "ts"lang: 'ts', CodeOptionsSingleTheme<BundledTheme>.theme: ThemeRegistrationAny | StringLiteralUnion<BundledTheme, string>theme: 'vitesse-dark', TransformerOptions.transformers?: ShikiTransformer[] | undefined
Transformers for the Shiki pipeline.
transformers
: [
function transformerTwoslash(options?: TransformerTwoslashIndexOptions): ShikiTransformer
Factory function to create a Shiki transformer for twoslash integrations.
transformerTwoslash
(), // <-- here
// ... ], })

The default 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:


rendererRich

Source code

Tip

This is the default renderer since v0.10.0.

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

ts
import { function rendererRich(options?: RendererRichOptions): TwoslashRenderer
An alternative renderer that providers better prefixed class names, with syntax highlight for the info text.
rendererRich
, function transformerTwoslash(options?: TransformerTwoslashIndexOptions): ShikiTransformer
Factory function to create a Shiki transformer for twoslash integrations.
transformerTwoslash
} from '@shikijs/twoslash'
function transformerTwoslash(options?: TransformerTwoslashIndexOptions): ShikiTransformer
Factory function to create a Shiki transformer for twoslash integrations.
transformerTwoslash
({
TransformerTwoslashOptions.renderer?: TwoslashRenderer | undefined
Custom renderers to decide how each info should be rendered
renderer
: function rendererRich(options?: RendererRichOptions): TwoslashRenderer
An alternative renderer that providers better prefixed class names, with syntax highlight for the info text.
rendererRich
() // <--
})

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

ts
interface Todo {
  Todo.title: stringtitle: string
}
 
const const todo: Readonly<Todo>todo: type Readonly<T> = { readonly [P in keyof T]: T[P]; }
Make all properties in T readonly
Readonly
<Todo> = {
title: string
title
: 'Delete inactive users'.String.toUpperCase(): string
Converts all the alphabetic characters in a string to uppercase.
toUpperCase
(),
} const todo: Readonly<Todo>todo.title = 'Hello'
Cannot assign to 'title' because it is a read-only property.
var Number: NumberConstructor
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number
.p
  • parseFloat
  • parseInt
  • prototype
NumberConstructor.parseInt(string: string, radix?: number): number
Converts A string to an integer.
@paramstring A string to convert into a number.@paramradix A value between 2 and 36 that specifies the base of the number in `string`. If this argument is not supplied, strings with a prefix of '0x' are considered hexadecimal. All other strings are considered decimal.
arseInt
('123', 10)
// //
ts
import { function createHighlighterCore(options: HighlighterCoreOptions<false>): Promise<HighlighterCore>
Create a Shiki core highlighter instance, with no languages or themes bundled. Wasm and each language and theme must be loaded manually.
@seehttp://shiki.style/guide/bundles#fine-grained-bundle
createHighlighterCore
} from 'shiki/core'
import { function createJavaScriptRegexEngine(options?: JavaScriptRegexEngineOptions): RegexEngine
Use the modern JavaScript RegExp engine to implement the OnigScanner. As Oniguruma supports some features that can't be emulated using native JavaScript regexes, some patterns are not supported. Errors will be thrown when parsing TextMate grammars with unsupported patterns, and when the grammar includes patterns that use invalid Oniguruma syntax. Set `forgiving` to `true` to ignore these errors and skip any unsupported or invalid patterns.
createJavaScriptRegexEngine
} from 'shiki/engine/javascript'
const const highlighter: HighlighterCorehighlighter = await function createHighlighterCore(options: HighlighterCoreOptions<false>): Promise<HighlighterCore>
Create a Shiki core highlighter instance, with no languages or themes bundled. Wasm and each language and theme must be loaded manually.
@seehttp://shiki.style/guide/bundles#fine-grained-bundle
createHighlighterCore
({
HighlighterCoreOptions<false>.engine: Awaitable<RegexEngine>
Custom RegExp engine.
engine
: function createJavaScriptRegexEngine(options?: JavaScriptRegexEngineOptions): RegexEngine
Use the modern JavaScript RegExp engine to implement the OnigScanner. As Oniguruma supports some features that can't be emulated using native JavaScript regexes, some patterns are not supported. Errors will be thrown when parsing TextMate grammars with unsupported patterns, and when the grammar includes patterns that use invalid Oniguruma syntax. Set `forgiving` to `true` to ignore these errors and skip any unsupported or invalid patterns.
createJavaScriptRegexEngine
()
}) const const a: 1a = 1
Custom log message
const const b: 1b = 1
Custom error message
const const c: 1c = 1
Custom warning message
Custom annotation message

rendererClassic

Source code

This renderer aligns with the output of legacy shiki-twoslash.

ts
import { function rendererClassic(): TwoslashRenderer
The default renderer aligning with the original `@shikijs/twoslash` output.
rendererClassic
, function transformerTwoslash(options?: TransformerTwoslashIndexOptions): ShikiTransformer
Factory function to create a Shiki transformer for twoslash integrations.
transformerTwoslash
} from '@shikijs/twoslash'
function transformerTwoslash(options?: TransformerTwoslashIndexOptions): ShikiTransformer
Factory function to create a Shiki transformer for twoslash integrations.
transformerTwoslash
({
TransformerTwoslashOptions.renderer?: TwoslashRenderer | undefined
Custom renderers to decide how each info should be rendered
renderer
: function rendererClassic(): TwoslashRenderer
The default renderer aligning with the original `@shikijs/twoslash` output.
rendererClassic
() // <--
})

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.

rendererFloatingVue

Source code

This renderer outputs Vue template syntax that using Floating Vue as the popup component (to render it outside the containers). This renderer is NOT directly usable but an internal renderer for the VitePress integration. Listing it here for reference if you want to create your own renderer.

Options

Explicit Trigger

When integrating with @shikijs/markdown-it or rehype-shiki, 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 { function transformerTwoslash(options?: TransformerTwoslashIndexOptions): ShikiTransformer
Factory function to create a Shiki transformer for twoslash integrations.
transformerTwoslash
} from '@shikijs/twoslash'
function transformerTwoslash(options?: TransformerTwoslashIndexOptions): ShikiTransformer
Factory function to create a Shiki transformer for twoslash integrations.
transformerTwoslash
({
TransformerTwoslashOptions.explicitTrigger?: boolean | RegExp | undefined
Requires `twoslash` to be presented in the code block meta to apply this transformer
@defaultfalse
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
```

Integrations

While you can set up Twoslash with Shiki on your own with the instructions above, you can also find high-level integrations with frameworks and tools here:

  • VitePress - A plugin to enable Twoslash support in VitePress.
  • Nuxt - A module to enable Twoslash for Nuxt Content.
  • Vocs - Vocs has TwoSlash support built-in.
  • Slidev - Slidev has TwoSlash support built-in.

Recipes

CDN Usage

By default 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
// FIXME: Replace with explicit versions in production
import { createTransformerFactory, rendererRich } from 'https://esm.sh/@shikijs/twoslash@latest/core'
import { codeToHtml } from 'https://esm.sh/shiki@latest'
import { createTwoslashFromCDN } from 'https://esm.sh/twoslash-cdn@latest'
import { createStorage } from 'https://esm.sh/unstorage@latest'
import indexedDbDriver from 'https://esm.sh/unstorage@latest/drivers/indexedb'
 
// ============= 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! Shiki + 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],
})