plugin to process JavaScript and TypeScript code with `twoslash` and highlight it with `starry-night`
23
stars
12
commits
JavaScript
primary language
Aug 2, 2024
updated
rehype plugin to process JavaScript and TypeScript code
with twoslash and highlight it with
starry-night.
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:
OptionsConfiguration 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 a twoslash directive (default: true)grammars? (ReadonlyArray<Grammar> | null | undefined)
— grammars for starry-night to support (default:
[sourceJson, sourceJs, sourceTsx, sourceTs])idPrefix? (string | null | undefined)
— prefix before IDs (default: 'rehype-twoslash-')renderers? (Renderers | null | undefined)
— renderers for twoslash annotations (optional)twoslash? (TwoslashOptions | null | undefined)
— options passed to twoslash (optional);
this includes fields such as cache,
customTransformers,
and filterNode;
see
TwoslashOptions from twoslash
for more infoRenderRender 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
RenderResultResult 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 tooltipRenderersRenderers.
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; }
rehypeTwoslash(options) (default)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 with starry-night to all codeSee 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.
12 commits
JavaScript
100.0%
plugin to process JavaScript and TypeScript code with `twoslash` and highlight it with `starry-night`
23
stars
12
commits
JavaScript
primary language
Aug 2, 2024
updated
rehype plugin to process JavaScript and TypeScript code
with twoslash and highlight it with
starry-night.
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:
OptionsConfiguration 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 a twoslash directive (default: true)grammars? (ReadonlyArray<Grammar> | null | undefined)
— grammars for starry-night to support (default:
[sourceJson, sourceJs, sourceTsx, sourceTs])idPrefix? (string | null | undefined)
— prefix before IDs (default: 'rehype-twoslash-')renderers? (Renderers | null | undefined)
— renderers for twoslash annotations (optional)twoslash? (TwoslashOptions | null | undefined)
— options passed to twoslash (optional);
this includes fields such as cache,
customTransformers,
and filterNode;
see
TwoslashOptions from twoslash
for more infoRenderRender 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
RenderResultResult 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 tooltipRenderersRenderers.
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; }
rehypeTwoslash(options) (default)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 with starry-night to all codeSee 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.
12 commits
JavaScript
100.0%