rehype plugin to process JavaScript and TypeScript code
with twoslash
and highlight it with
starry-night
.
- What is this?
- When should I use this?
- Install
- Use
- API
- HTML
- Markdown
- CSS
- JavaScript
- Compatibility
- Security
- Related
- Contribute
- License
This package is a unified (rehype) plugin to
process JavaScript and TypeScript code with twoslash
and
highlight it with starry-night
.
twoslash
is a tool to run code through the TypeScript compiler and extract
info about that code.
Info you also see in your editor.
This info can for example be type errors or type info that is shown on hover.
twoslash
also supports a command syntax through comments in the code,
so an author can highlight a particular piece of code,
ignore certain errors,
or show a specific file.
starry-night
is a beautiful syntax highlighter,
like what GitHub uses to highlight code,
but free and in JavaScript.
This plugin is particularly useful for your own website or blog, or any place where you want to talk about JavaScript-y code, and want to improve the experience of your readers by showing them more info about the code.
You can combine this package with
rehype-starry-night
.
That applies syntax highlighting with starry-night
to all code.
If you are not using remark or rehype,
you can instead use twoslash
directly.
If you don’t care for starry-night
,
you can use @shikijs/twoslash
.
This package is ESM only. In Node.js (version 16+), install with npm:
npm install rehype-twoslash
In Deno with esm.sh
:
import rehypeTwoslash from 'https://esm.sh/rehype-twoslash@1'
In browsers with esm.sh
:
<script type="module">
import rehypeTwoslash from 'https://esm.sh/rehype-twoslash@1?bundle'
</script>
Say we have the following file example.md
:
# Jupiter
```js twoslash
const name = 'Jupiter'
console.log('Hello, ' + name + '!')
```
…and our module example.js
contains:
import rehypeStringify from 'rehype-stringify'
import rehypeTwoslash from 'rehype-twoslash'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import {read} from 'to-vfile'
import {unified} from 'unified'
const file = await read('example.md')
await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeTwoslash)
.use(rehypeStringify)
.process(file)
console.log(String(file))
…then running node example.js
yields:
<h1>Jupiter</h1>
<div class="highlight highlight-js">
<pre><code class="language-js"><span class="pl-k">const</span> <span class="rehype-twoslash-popover-target" data-popover-target="rehype-twoslash-cnJcnJcn-0"><span class="pl-c1">name</span></span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>Jupiter<span class="pl-pds">'</span></span>
<span class="rehype-twoslash-popover-target" data-popover-target="rehype-twoslash-cnJcnJcn-1"><span class="pl-en">console</span></span>.<span class="rehype-twoslash-popover-target" data-popover-target="rehype-twoslash-cnJcnJcn-2"><span class="pl-c1">log</span></span>(<span class="pl-s"><span class="pl-pds">'</span>Hello, <span class="pl-pds">'</span></span> <span class="pl-k">+</span> <span class="rehype-twoslash-popover-target" data-popover-target="rehype-twoslash-cnJcnJcn-3">name</span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">'</span>!<span class="pl-pds">'</span></span>)
</code></pre>
<div class="rehype-twoslash-hover rehype-twoslash-popover" id="rehype-twoslash-cnJcnJcn-0" popover=""><pre class="rehype-twoslash-popover-code"><code class="language-ts"><span class="pl-k">const</span> <span class="pl-c1">name</span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>Jupiter<span class="pl-pds">"</span></span></code></pre></div>
<div class="rehype-twoslash-hover rehype-twoslash-popover" id="rehype-twoslash-cnJcnJcn-1" popover=""><pre class="rehype-twoslash-popover-code"><code class="language-ts"><span class="pl-k">var</span> <span class="pl-smi">console</span><span class="pl-k">:</span> <span class="pl-en">Console</span></code></pre></div>
<div class="rehype-twoslash-hover rehype-twoslash-popover" id="rehype-twoslash-cnJcnJcn-2" popover=""><pre class="rehype-twoslash-popover-code"><code class="language-ts">(<span class="pl-smi">method</span>) <span class="pl-c1">Console</span>.<span class="pl-en">log</span>(<span class="pl-k">...</span><span class="pl-smi">data</span>: <span class="pl-smi">any</span>[]): <span class="pl-k">void</span></code></pre><div class="rehype-twoslash-popover-description"><p><a href="https://developer.mozilla.org/docs/Web/API/console/log_static">MDN Reference</a></p></div></div>
<div class="rehype-twoslash-hover rehype-twoslash-popover" id="rehype-twoslash-cnJcnJcn-3" popover=""><pre class="rehype-twoslash-popover-code"><code class="language-ts"><span class="pl-k">const</span> <span class="pl-c1">name</span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>Jupiter<span class="pl-pds">"</span></span></code></pre></div>
</div>
With some CSS and JavaScript that could look like this:
Configuration for rehype-twoslash
.
rehype-twoslash
runs on <code>
elements with a twoslash
directive.
That directive can be passed as a word in markdown (```ts twoslash
) or
as a class in HTML (<code class="language-ts twoslash">
).
The inverse occurs when directive
is false
.
All <code>
where the language class is JavaScript or TypeScript is
processed.
Then no-twoslash
(```ts no-twoslash
,
<code class="language-ts no-twoslash">
) can be used to prevent processing.
directive?
(boolean | null | undefined
) — whether to require atwoslash
directive (default:true
)grammars?
(ReadonlyArray<Grammar> | null | undefined
) — grammars forstarry-night
to support (default:[sourceJson, sourceJs, sourceTsx, sourceTs]
)idPrefix?
(string | null | undefined
) — prefix before IDs (default:'rehype-twoslash-'
)renderers?
(Renderers | null | undefined
) — renderers fortwoslash
annotations (optional)twoslash?
(TwoslashOptions | null | undefined
) — options passed totwoslash
(optional); this includes fields such ascache
,customTransformers
, andfilterNode
; seeTwoslashOptions
fromtwoslash
for more info
Render function.
Takes a particular annotation from the TypeScript compiler (such as an error)
and turns it into hast
(HTML) content.
See lib/render.js
for examples.
You can return Array<ElementContent>
directly instead of a RenderResult
when you don’t have content for a footer.
(
state: State,
annotation: Annotation,
children: Array<ElementContent>
) => Array<ElementContent> | RenderResult
Result from Render
.
content?
(Array<ElementContent> | undefined
) — main inline content to use in the code block; for example a<span>
that causes a tooltip to showfooter?
(Array<ElementContent> | undefined
) — extra content to use that relates to the code block; for example a<div>
for a tooltip
Renderers.
Each key is a type of an annotation (such as error
or hover
) and each
value a corresponding render function.
{ completion?: Render<NodeCompletion> | null | undefined; error?: Render<NodeError> | null | undefined; highlight?: Render<NodeHighlight> | null | undefined; hover?: Render<...> | ... 1 more ... | undefined; query?: Render<...> | ... 1 more ... | undefined; }
Plugin to process JavaScript and TypeScript code with twoslash
and highlight it with starry-night
.
options?
(Readonly<Options> | null | undefined
) — configuration (optional)
Transform ((tree: Root, file: VFile) => Promise<Root>
).
On the input side,
this plugin looks for code blocks with a twoslash
class.
So:
<pre><code class="language-ts twoslash">console.log('Hello, Mercury!')</code></pre>
It will warn when that class is used with a programming language that
twoslash
does not understand (such as Rust).
If you want to process all JavaScript and TypeScript code blocks,
you can set directive: false
in options.
Then the language-*
class is enough and no directive is needed.
You can still prevent processing of a particular block with a no-twoslash
class:
<pre><code class="language-ts no-twoslash">console.log('Hello, Mars!')</code></pre>
On the output side,
this plugin generates markup that can be enhanced with
CSS and JavaScript into tooltips and
more.
You can also choose to generate different HTML by passing custom render
functions in options.renderers
.
To illustrate,
here is an example of a tooltip target for the identifier in a variable
declaration (const name = …
):
<span
class="rehype-twoslash-popover-target"
data-popover-target="rehype-twoslash-cnJcnJcn-3"
><span class="pl-c1">name</span></span>
It has a corresponding tooltip:
<div
class="rehype-twoslash-hover rehype-twoslash-popover"
id="rehype-twoslash-cnJcnJcn-3"
popover=""
>
<pre class="rehype-twoslash-popover-code"><code class="language-ts"><span class="pl-k">const</span> <span class="pl-c1">name</span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>Jupiter<span class="pl-pds">"</span></span></code></pre>
</div>
Observe that there are sufficient classes to hook into with CSS and JavaScript and that unique identifiers connect the popover and its popover target together.
When combined with remark-parse
and
remark-rehype
,
this plugin works similarly on markdown to how it does on HTML as described
above.
It then understands the twoslash
and no-twoslash
word in the info string,
right after the language.
To illustrate:
```ts twoslash
console.log('Hello, Venus!')
```
```ts no-twoslash
console.log('Hello, Earth!')
```
This plugin generates sufficient classes that can be styled with CSS.
Which ones to use and how to style them depends on the rest of your website
and your heart’s desire.
To illustrate,
see demo/index.css
.
But get creative!
This plugin generates markup that needs to be made interactive with JavaScript.
What to do exactly,
and how to do it,
depends on your website and your preferences.
For inspiration,
see demo/index.js
.
Projects maintained by the unified collective are compatible with maintained versions of Node.js.
When we cut a new major release, we drop support for unmaintained versions of
Node.
This means we try to keep the current release line, rehype-twoslash@1
,
compatible with Node.js 16.
Use of rehype-twoslash
is likely not safe on arbitrary user content,
as it passes code through the TypeScript compiler,
which I assume has some access to the file system and there might be ways to
exploit it.
rehype-starry-night
— apply syntax highlighting withstarry-night
to all code
See contributing.md
in rehypejs/.github
for ways to get started.
See support.md
for ways to get help.
This project has a code of conduct. By interacting with this repository, organization, or community you agree to abide by its terms.