Package detail

shiki-twoslash

shikijs26.9kMIT3.1.2

API primitives to mix Shiki with Twoslash

readme

shiki-twoslash

Documentation / made lovely by counting words / maybe we would read!

Provides the API primitives to mix shiki with @typescript/twoslash to provide rich contextual code samples.

Things it handles:

  • Shiki bootstrapping: createShikiHighlighter
  • Running Twoslash over code, with caching and DTS lookups: runTwoSlash
  • Rendering any code sample with Shiki: renderCodeToHTML

Generic libraries for common tools which use this generator:

Plugins for common Static Site Generators:

Or you can use the API directly in a Node.js script:

import { renderCodeToHTML, runTwoSlash, createShikiHighlighter } from "shiki-twoslash"
import { writeFileSync } from "fs"

const go = async () => {
  const highlighter = await createShikiHighlighter({ theme: "dark-plus" })
  const code = `
// Hello world
const a = "123"
const b = "345"
    `
  const twoslash = runTwoSlash(code, "ts", {})
  const html = renderCodeToHTML(twoslash.code, "ts", { twoslash: true }, {}, highlighter, twoslash)

  fs.writeFileSync("output.html", html, "utf8")
}

User Settings

The config which you pass in is a mix of Shiki's HighlighterOptions

interface HighlighterOptions {
    /**
     * The theme to load upfront.
     */
    theme?: IThemeRegistration;
    /**
     * A list of themes to load upfront.
     *
     * Default to: `['dark-plus', 'light-plus']`
     */
    themes?: IThemeRegistration[];
    /**
     * A list of languages to load upfront.
     *
     * Default to `['html', 'css', 'javascript']`
     */
    langs?: (Lang | ILanguageRegistration)[];
    /**
     * Paths for loading themes and langs. Relative to the package's root.
     */
    paths?: IHighlighterPaths;
}

With twoslash's TwoSlashOptions

export interface TwoSlashOptions {
    /** Allows setting any of the handbook options from outside the function, useful if you don't want LSP identifiers */
    defaultOptions?: Partial<ExampleOptions>;
    /** Allows setting any of the compiler options from outside the function */
    defaultCompilerOptions?: CompilerOptions;
    /** Allows applying custom transformers to the emit result, only useful with the showEmit output */
    customTransformers?: CustomTransformers;
    /** An optional copy of the TypeScript import, if missing it will be require'd. */
    tsModule?: TS;
    /** An optional copy of the lz-string import, if missing it will be require'd. */
    lzstringModule?: LZ;
    /**
     * An optional Map object which is passed into @typescript/vfs - if you are using twoslash on the
     * web then you'll need this to set up your lib *.d.ts files. If missing, it will use your fs.
     */
    fsMap?: Map<string, string>;
    /** The cwd for the folder which the virtual fs should be overlaid on top of when using local fs, opts to process.cwd() if not present */
    vfsRoot?: string;
    /** A set of known `// @[tags]` tags to extract and not treat as a comment */
    customTags?: string[];
}

The Twoslash ExampleOptions looks like (these are things which can be set via // @[flag] in a code sample):

/** Available inline flags which are not compiler flags */
export interface ExampleOptions {
    /** Lets the sample suppress all error diagnostics */
    noErrors: boolean;
    /** An array of TS error codes, which you write as space separated - this is so the tool can know about unexpected errors */
    errors: number[];
    /** Shows the JS equivalent of the TypeScript code instead */
    showEmit: boolean;
    /**
     * Must be used with showEmit, lets you choose the file to present instead of the source - defaults to index.js which
     * means when you just use `showEmit` above it shows the transpiled JS.
     */
    showEmittedFile: string;
    /** Whether to disable the pre-cache of LSP calls for interesting identifiers, defaults to false */
    noStaticSemanticInfo: boolean;
    /** Declare that the TypeScript program should edit the fsMap which is passed in, this is only useful for tool-makers, defaults to false */
    emit: boolean;
    /** Declare that you don't need to validate that errors have corresponding annotations, defaults to false */
    noErrorValidation: boolean;
}

And one extra for good luck:

export interface TwoslashShikiOptions {
    /** A way to turn on the try buttons seen on the TS website */
    addTryButton?: true;
    /** A way to disable implicit React imports on tsx/jsx language codeblocks */
    disableImplicitReactImport?: true;
    /** A way to add a div wrapper for multi-theme outputs */
    wrapFragments?: true;
    /** Include JSDoc comments in the hovers */
    includeJSDocInHover?: true;
    /** Instead of showing twoslash exceptions inline, throw the entire process like it will on CI */
    alwayRaiseForTwoslashExceptions?: true;
    /** Ignore transforming certain code blocks */
    ignoreCodeblocksWithCodefenceMeta?: string[];
}

That said, most people will just want to set a theme:

{
  resolve: "gatsby-remark-shiki-twoslash",
  options: {
    theme: "github-light"
  },
}

You can find all built-in themes here and all built-in languages here.

Common Use Case

Default Compiler Options

You can set a default set of TypeScript options via defaultCompilerOptions

[
  require("remark-shiki-twoslash").default,
  {
    themes: ["min-light", "min-dark"],
    defaultCompilerOptions: {
      types: ["node"],
    },
  },
]
Node Types in a Code Sample

To set up globals for one-off cases, import them via an inline triple-slash reference:

```ts twoslash
/// <reference types="jest" />
import { createHighlightedString } from "../src/utils"

describe(createHighlightedString, () => {
  it("handles passing the LSP info through in a way that the CSS renderer can understand", () => {
    const result = createHighlightedString([], "longest")

    expect(result).toMatchInlineSnapshot(
      `"<data-lsp lsp='function longest&lt;number[]>(a: number[], b: number[]): number[]' >longest</data-lsp>"`
    )
  })
})
```

API

The user-exposed parts of the API is a well documented single file, you might find it easier to just read that: src/index.ts.

createShikiHighlighter

Sets up the highlighter for Shiki, accepts shiki options:

async function visitor(highlighterOpts) {
  const highlighter = await createShikiHighlighter(userOpts)
  visit(markdownAST, "code", visitor(highlighter, userOpts))
}
renderCodeToHTML
/**
 * Renders a code sample to HTML, automatically taking into account:
 *
 *  - rendering overrides for twoslash and tsconfig
 *  - whether the language exists in shiki
 *
 * @param code the source code to render
 * @param lang the language to use in highlighting
 * @param info additional metadata which lives after the codefence lang (e.g. ["twoslash"])
 * @param highlighter optional, but you should use it, highlighter
 * @param twoslash optional, but required when info contains 'twoslash' as a string
 */
export declare const renderCodeToHTML: (
  code: string,
  lang: string,
  info: string[],
  shikiOptions?: import("shiki/dist/renderer").HtmlRendererOptions | undefined,
  highlighter?: Highlighter | undefined,
  twoslash?: TwoSlashReturn | undefined
) => string

For example:

const results = renderCodeToHTML(node.value, lang, node.meta || [], {}, highlighter, node.twoslash)
node.type = "html"
node.value = results
node.children = []

Uses:

  • renderers.plainTextRenderer for language which shiki cannot handle
  • renderers.defaultRenderer for shiki highlighted code samples
  • renderers.twoslashRenderer for twoslash powered TypeScript code samples
  • renderers.tsconfigJSONRenderer for extra annotations to JSON which is known to be a TSConfig file

These will be used automatically for you, depending on whether the language is available or what the info param is set to.

To get access to the twoslash renderer, you'll need to pass in the results of a twoslash run to renderCodeToHTML:

const highlighter = await createShikiHighlighter(highlighterOpts)
const twoslashResults = runTwoSlash(code, lang)
const results = renderCodeToHTML(
  twoslashResults.code,
  twoslashResults.lang,
  node.meta || ["twoslash"],
  {},
  highlighter,
  node.twoslash
)

runTwoSlash

Used to run Twoslash on a code sample. In this case it's looking at a code AST node and switching out the HTML with the twoslash results:

if (node.meta && node.meta.includes("twoslash")) {
  const results = runTwoSlash(node.value, node.lang, settings)
  node.value = results.code
  node.lang = results.extension
  node.twoslash = results
}

changelog

shiki-twoslash

3.1.2

Patch Changes

  • 7777f35: Fixed renderer completion result closing tag.

3.1.1

Patch Changes

  • bfca2ac: Moves typescript to peer dependencies

3.1.0

Minor Changes

  • 53d3730: Bump version of "shiki" to 0.10.1

3.0.2

Patch Changes

  • 4b83df9: Updates the twoslash dependency

3.0.1

Patch Changes

  • bc5330b: shiki-twoslash: fix HTML comment syntax when we can't find a language for the code sample. Use correct cache path after splitting based on node_modules

3.0.0

Major Changes

  • 8fffcd9: Three main things:

    • Playground: entirely new set of user-centered docs for understanding how to make a Shiki Twoslash code sample
    • Annotations: A way to provide meta-commentary on code in a code sample
    • Logging: Abuse the trust your users have in your code samples by using first-class primitives for showing logs

    The major bump is because I changed the codefence highlighting syntax to be similar to the IDE's line numbers, they start at 1, not 0.

    See the docs: in https://shikijs.github.io/twoslash/playground

2.1.3

Patch Changes

  • bbba24f: Adds inline errors for fenceparser errors - fixes #101

2.1.2

Patch Changes

  • 71b0697: Checks for existing React import before adding it automatically in TSX code blocks.

2.1.1

Patch Changes

  • 61a6af5: Adds support for an annotation system. This is still work in progress, but the goal is to allow you to provide a way to write meta-commentary on a code-sample from the outside of the code block by having an arrow and some comments.

    For example

    ```js twoslash
    function compact(arr) {
    // @annotate: left 56 - No editor warnings in JavaScript files<br/><br/>This crashes at runtime.
      if (orr.length > 10) return arr
      return arr
    }
    ```

    Would create a codeblock with:

    function compact(arr) {
      if (orr.length > 10) return arr;
      return arr;
    }

    And a little SVG arrow and the text "No editor warnings in JavaScript files

    This crashes at runtime." next to it. I'll be tweaking the syntax over time, but for now the syntax is // @annotate: [left/right] [arrow degree rotatation] [text degree rotatation] - Text to show

2.0.3

Patch Changes

  • 8a82e13: Remove dom.ts which is no longer in use.

2.0.2

Patch Changes

  • 8a0fcc0: Switch to use a new package which we've extracted out from Shiki Twoslash for handling parsing the different potential formats for codefence attributes: fenceparser which means a breaking change in the remark plugin API. The semver major shouldn't affect anyone using the library via another tool (e.g. via the docusaurus plugins etc).

2.0.1

Patch Changes

  • f92d030: Instead of throwing the process when Shiki Twoslash gets a failing test, it will replace the code sample with information on the issue and recommendations on how to fix it. This also comes with an overhaul of the error messaging in @typescript/twoslash.

2.0.0

Major Changes

  • e0574f2: In the process of adding the option to have JSDoc comments included in the hovers, I changed the exposed types for renderCodeToHTML in shiki-twoslash this major sermver bump only really affects users who are using the shiki-twoslash API directly which I've not heard of any yet.