mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
768 lines
17 KiB
Markdown
768 lines
17 KiB
Markdown
---
|
|
title: Two Slash
|
|
icon: material-symbols:experiment-outline
|
|
createTime: 2025/10/08 11:46:49
|
|
permalink: /en/guide/markdown/twoslash/
|
|
outline: [2, 4]
|
|
---
|
|
|
|
## Overview
|
|
|
|
Adds support for [TypeScript TwoSlash](https://www.typescriptlang.org/dev/twoslash/) in code blocks. Provides inline type hints within code blocks.
|
|
|
|
This feature is powered by [shiki](https://shiki.style/) and [@shikijs/twoslash](https://shiki.style/packages/twoslash), and integrated in [@vuepress-plume/plugin-shikiji](https://github.com/pengzhanbo/vuepress-theme-plume/tree/main/plugins/plugin-shikiji).
|
|
|
|
::: important __twoslash__ is an advanced feature that requires proficiency in
|
|
[TypeScript](https://www.typescriptlang.org/) and understanding of [twoslash syntax](https://twoslash.netlify.app/).
|
|
:::
|
|
|
|
::: warning
|
|
`twoslash` is a relatively time-consuming feature. Since it requires type compilation of code,
|
|
if the code imports large packages, it can take considerable time.
|
|
|
|
Specifically, since VuePress pre-compiles all markdown files on startup, this directly affects VuePress startup time.
|
|
If you include many `twoslash` code blocks, this may significantly increase VuePress startup time.
|
|
|
|
For example, without `twoslash`, VuePress startup time typically ranges from `300ms ~ 1000ms`.
|
|
With `twoslash`, compiling a single `twoslash` code block may require an additional `500ms` or more.
|
|
|
|
However, don't worry about compilation time during markdown file hot updates.
|
|
The theme optimizes code highlighting compilation time - even if a single markdown file contains
|
|
multiple code blocks, the theme only compiles __modified code blocks__, so hot updates remain fast.
|
|
:::
|
|
|
|
[twoslash](https://twoslash.netlify.app/) is a `JavaScript` and `TypeScript` markup language.
|
|
You can write code examples that describe entire `JavaScript` projects.
|
|
|
|
`twoslash` treats __double-slash comments__ (`//`) as preprocessor directives for code examples.
|
|
|
|
`twoslash` uses the same compiler APIs as text editors to provide type-driven hover information, accurate errors, and type annotations.
|
|
|
|
## Feature Preview
|
|
|
|
Hover over __variables__ or __functions__ to see the effect:
|
|
|
|
```ts twoslash
|
|
import { createHighlighter } from 'shiki'
|
|
|
|
const highlighter = await createHighlighter({ themes: ['nord'], langs: ['javascript'] })
|
|
// ^?
|
|
//
|
|
|
|
// @log: Custom log message
|
|
const a = 1
|
|
// @error: Custom error message
|
|
const b = 1
|
|
// @warn: Custom warning message
|
|
const c = 1
|
|
// @annotate: Custom annotation message
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Enabling the Feature
|
|
|
|
Before enabling this feature, you need to install the `@vuepress/shiki-twoslash` package:
|
|
|
|
::: npm-to
|
|
|
|
```sh
|
|
npm i @vuepress/shiki-twoslash
|
|
```
|
|
|
|
:::
|
|
|
|
Enable the `twoslash` option in the theme configuration.
|
|
|
|
```ts title=".vuepress/config.ts"
|
|
export default defineUserConfig({
|
|
theme: plumeTheme({
|
|
codeHighlighter: {
|
|
twoslash: true,
|
|
},
|
|
}),
|
|
})
|
|
```
|
|
|
|
::: important
|
|
For most users, `twoslash` is not a necessary feature, and `twoslash`-related dependencies have large
|
|
package sizes. Therefore, all `twoslash` implementations have been moved to `@vuepress/shiki-twoslash`,
|
|
which effectively reduces the initial installation size of the theme.
|
|
|
|
You only need to install `@vuepress/shiki-twoslash` when you require the `twoslash` feature.
|
|
:::
|
|
|
|
### Importing Type Files from `node_modules`
|
|
|
|
The main feature of __twoslash__ is type compilation of code blocks, which natively supports importing type files from your project's `node_modules`.
|
|
|
|
For example, if you need type hints for `express`, you need to install the `@types/express` dependency in your project:
|
|
|
|
::: npm-to
|
|
|
|
```sh
|
|
npm i -D @types/express
|
|
```
|
|
|
|
:::
|
|
|
|
Then, you can use `express` types in code blocks as follows:
|
|
|
|
```` md
|
|
```ts twoslash
|
|
import express from 'express'
|
|
|
|
const app = express()
|
|
```
|
|
````
|
|
|
|
### Importing Local Type Files
|
|
|
|
For importing local type files, since it's difficult to determine the real path of code in code blocks
|
|
during compilation, it's not intuitive to import type files via __relative paths__ in code blocks.
|
|
|
|
#### Reading Path Mappings from `tsconfig.json`
|
|
|
|
The theme supports reading path mappings from `compilerOptions.paths` in `tsconfig.json` at the project root to solve this issue.
|
|
|
|
Assume your project's `tsconfig.json` is configured as follows:
|
|
|
|
```json title="tsconfig.json"
|
|
{
|
|
"compilerOptions": {
|
|
"baseUrl": ".",
|
|
"paths": {
|
|
"@/*": ["./src/*"]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
You can directly use paths starting with `@/` in code blocks to import type files from the `src` directory:
|
|
|
|
````md
|
|
```ts twoslash
|
|
import type { Foo } from '@/foo'
|
|
|
|
const foo: Foo = 1
|
|
```
|
|
````
|
|
|
|
#### Reading Path Mappings from `shiki.twoslash`
|
|
|
|
You can configure `compilerOptions` in `shiki.twoslash` to solve this issue:
|
|
|
|
```ts title=".vuepress/config.ts"
|
|
import path from 'node:path'
|
|
|
|
export default defineUserConfig({
|
|
theme: plumeTheme({
|
|
codeHighlighter: {
|
|
twoslash: {
|
|
compilerOptions: { // [!code hl:8]
|
|
paths: {
|
|
// Relative to working directory `process.cwd()`
|
|
'@/*': ['./src/*'],
|
|
// Using absolute paths
|
|
'@@/*': [path.resolve(process.cwd(), './src/*')],
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}),
|
|
})
|
|
```
|
|
|
|
You can directly use paths starting with `@/` in code blocks to import type files from the `src` directory:
|
|
|
|
````md
|
|
```ts twoslash
|
|
import type { Foo } from '@/foo'
|
|
|
|
const foo: Foo = 1
|
|
```
|
|
````
|
|
|
|
::: important
|
|
Using `plugins.shiki.twoslash.compilerOptions` allows more flexible configuration of type compilation.
|
|
You can modify `baseUrl` and other configuration options here.
|
|
|
|
[Refer to CompilerOptions](https://www.typescriptlang.org/tsconfig/#compilerOptions)
|
|
|
|
Usually you only need to configure the `paths` option, keeping other options default unless you understand what you're configuring.
|
|
:::
|
|
|
|
## Usage
|
|
|
|
::: warning `twoslash` only supports `typescript` and `vue` code blocks.
|
|
:::
|
|
|
|
After enabling this feature, simply add the `twoslash` keyword after the code language declaration in your existing markdown code block syntax:
|
|
|
|
````md{1}
|
|
```ts twoslash
|
|
const a = 1
|
|
```
|
|
````
|
|
|
|
The theme only processes code blocks with the `twoslash` keyword.
|
|
|
|
## Syntax Reference
|
|
|
|
Complete syntax reference: [ts-twoslasher](https://github.com/microsoft/TypeScript-Website/tree/v2/packages/ts-twoslasher) and [shikijs-twoslash](https://twoslash.netlify.app/)
|
|
|
|
`twoslash` treats __double slashes__ as preprocessor directives for code examples. Therefore, all annotations are added after `//`.
|
|
|
|
### Symbol Annotations
|
|
|
|
Common `twoslash` annotations:
|
|
|
|
#### `^?` {#extract-type}
|
|
|
|
Use `^?` to extract type information for specific identifiers on the code line above it.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
const hi = 'Hello'
|
|
const msg = `${hi}, world`
|
|
// ^?
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
const hi = 'Hello'
|
|
const msg = `${hi}, world`
|
|
// ^?
|
|
//
|
|
```
|
|
|
|
::: important The symbol `^` must correctly point to the variable whose type you want to highlight.
|
|
:::
|
|
|
|
#### `^|` {#completions}
|
|
|
|
Use `^|` to extract autocompletion information at a specific position.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @noErrors
|
|
console.e
|
|
// ^|
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @noErrors
|
|
console.e
|
|
// ^|
|
|
//
|
|
```
|
|
|
|
::: important The symbol `^` must correctly point to the position where you want content prediction.
|
|
:::
|
|
|
|
Twoslash requests TypeScript for autocompletion suggestions at the `^` position,
|
|
then filters possible outputs based on letters after the `.`. Up to 5 inline results are displayed,
|
|
and if a completion item is marked as deprecated, the output reflects this.
|
|
|
|
In this case, Twoslash requests completion suggestions for `console` from TypeScript,
|
|
then filters for items starting with `e`. Note the `// @noErrors` compiler flag is set because `console.e`
|
|
is a failing TypeScript code example, but we don't care about that.
|
|
|
|
#### `^^^` {#highlighting}
|
|
|
|
Use `^^^` to highlight specific ranges on the line above it.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
function add(a: number, b: number) {
|
|
// ^^^
|
|
return a + b
|
|
}
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
function add(a: number, b: number) {
|
|
// ^^^
|
|
return a + b
|
|
}
|
|
```
|
|
|
|
::: important Use consecutive `^` symbols to correctly point to the range you want to highlight.
|
|
:::
|
|
|
|
### `@filename` {#import-files}
|
|
|
|
`@filename: <filename>` declares which file subsequent code will come from. You can import this file via `import` in other parts of the code.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @filename: sum.ts
|
|
export function sum(a: number, b: number): number {
|
|
return a + b
|
|
}
|
|
|
|
// @filename: ok.ts
|
|
import { sum } from './sum'
|
|
sum(1, 2)
|
|
|
|
// @filename: error.ts
|
|
// @errors: 2345
|
|
import { sum } from './sum'
|
|
sum(4, 'woops')
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @filename: sum.ts
|
|
export function sum(a: number, b: number): number {
|
|
return a + b
|
|
}
|
|
|
|
// @filename: ok.ts
|
|
import { sum } from './sum'
|
|
sum(1, 2)
|
|
|
|
// @filename: error.ts
|
|
// @errors: 2345
|
|
import { sum } from './sum'
|
|
sum(4, 'woops')
|
|
```
|
|
|
|
### Code Cutting
|
|
|
|
#### `---cut-before---` {#cut-before}
|
|
|
|
After TypeScript generates the project and extracts all editor information (like identifiers,
|
|
queries, highlights, etc.), cutting operations adjust all offsets and line numbers to fit the smaller output.
|
|
|
|
Users see content below `// ---cut-before---`. The shorthand `// ---cut---` is also supported.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
const level: string = 'Danger'
|
|
// ---cut---
|
|
console.log(level)
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
const level: string = 'Danger'
|
|
// ---cut---
|
|
console.log(level)
|
|
```
|
|
|
|
Show only a single file:
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @filename: a.ts
|
|
export const helloWorld: string = 'Hi'
|
|
// ---cut---
|
|
// @filename: b.ts
|
|
import { helloWorld } from './a'
|
|
|
|
console.log(helloWorld)
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @filename: a.ts
|
|
export const helloWorld: string = 'Hi'
|
|
// ---cut---
|
|
// @filename: b.ts
|
|
import { helloWorld } from './a'
|
|
|
|
console.log(helloWorld)
|
|
```
|
|
|
|
Only the last two lines are displayed, but to TypeScript, this is a program with two files,
|
|
and all IDE information is correctly connected between files. This is why `// @filename: [file]`
|
|
is the only Twoslash command not removed, as it can be `---cut---` if irrelevant.
|
|
|
|
#### `---cut-after---` {#cut-after}
|
|
|
|
The sibling of `---cut-before---`, used to trim everything after the symbol:
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
const level: string = 'Danger'
|
|
// ---cut-before---
|
|
console.log(level)
|
|
// ---cut-after---
|
|
console.log('This is not shown')
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
const level: string = 'Danger'
|
|
// ---cut-before---
|
|
console.log(level)
|
|
// ---cut-after---
|
|
console.log('This is not shown')
|
|
```
|
|
|
|
#### `---cut-start---` and `---cut-end---` {#cut-start-end}
|
|
|
|
You can also use the `---cut-start---` and `---cut-end---` pair to cut code segments between two symbols.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
const level: string = 'Danger'
|
|
// ---cut-start---
|
|
console.log(level) // This part is cut
|
|
// ---cut-end---
|
|
console.log('This is shown')
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
const level: string = 'Danger'
|
|
// ---cut-start---
|
|
console.log(level) // This part is cut
|
|
// ---cut-end---
|
|
console.log('This is shown')
|
|
```
|
|
|
|
Multiple instances are supported to cut multiple sections, but symbols must appear in pairs.
|
|
|
|
### Custom Output Messages {id=twoslash-custom-message}
|
|
|
|
`@log`, `@error`, `@warn`, and `@annotate` output custom messages at different levels to users.
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @log: Custom log message
|
|
const a = 1
|
|
// @error: Custom error message
|
|
const b = 1
|
|
// @warn: Custom warning message
|
|
const c = 1
|
|
// @annotate: Custom annotation message
|
|
```
|
|
````
|
|
|
|
```ts twoslash
|
|
// @log: Custom log message
|
|
const a = 1
|
|
// @error: Custom error message
|
|
const b = 1
|
|
// @warn: Custom warning message
|
|
const c = 1
|
|
// @annotate: Custom annotation message
|
|
```
|
|
|
|
### Output Compiled Files
|
|
|
|
Running Twoslash code examples triggers full TypeScript compilation runs that create files in a virtual
|
|
file system. You can replace code example content with results from running TypeScript on the project.
|
|
|
|
#### `@showEmit`
|
|
|
|
`// @showEmit` is the primary command to tell Twoslash you want to replace the code example output with the equivalent `.js` file.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @showEmit
|
|
const level: string = 'Danger'
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @showEmit
|
|
const level: string = 'Danger'
|
|
```
|
|
|
|
The result shows the `.js` file corresponding to this `.ts` file. You can see TypeScript output removes `: string` and adds `export {}`.
|
|
|
|
#### `@showEmittedFile: [file]`
|
|
|
|
While `.js` files may be the most useful out of the box, TypeScript does emit other files (`.d.ts` and `.map`)
|
|
when proper flags are enabled, and with multi-file code examples, you may need to tell Twoslash which file to
|
|
display. For all these cases, you can also add `@showEmittedFile: [file]` to tell Twoslash which file you want to display.
|
|
|
|
__Display `.d.ts` file for TypeScript code example:__
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @declaration
|
|
// @showEmit
|
|
// @showEmittedFile: index.d.ts
|
|
export const hello = 'world'
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @declaration
|
|
// @showEmit
|
|
// @showEmittedFile: index.d.ts
|
|
export const hello = 'world'
|
|
```
|
|
|
|
__Display `.map` file from JavaScript to TypeScript:__
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @sourceMap
|
|
// @showEmit
|
|
// @showEmittedFile: index.js.map
|
|
export const hello = 'world'
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @sourceMap
|
|
// @showEmit
|
|
// @showEmittedFile: index.js.map
|
|
export const hello = 'world'
|
|
```
|
|
|
|
__Display `.map` for `.d.ts` files (mainly for project references):__
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @declaration
|
|
// @declarationMap
|
|
// @showEmit
|
|
// @showEmittedFile: index.d.ts.map
|
|
export const hello: string = 'world'
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @declaration
|
|
// @declarationMap
|
|
// @showEmit
|
|
// @showEmittedFile: index.d.ts.map
|
|
export const hello: string = 'world'
|
|
```
|
|
|
|
__Generate `.js` file for `b.ts`:__
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @showEmit
|
|
// @showEmittedFile: b.js
|
|
// @filename: a.ts
|
|
export const helloWorld: string = 'Hi'
|
|
|
|
// @filename: b.ts
|
|
import { helloWorld } from './a'
|
|
console.log(helloWorld)
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @showEmit
|
|
// @showEmittedFile: b.js
|
|
// @filename: a.ts
|
|
export const helloWorld: string = 'Hi'
|
|
|
|
// @filename: b.ts
|
|
import { helloWorld } from './a'
|
|
console.log(helloWorld)
|
|
```
|
|
|
|
### `@errors`
|
|
|
|
`@errors: <error code>` shows how code produces errors:
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @errors: 2322 2588
|
|
const str: string = 1
|
|
str = 'Hello'
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @errors: 2322 2588
|
|
const str: string = 1
|
|
str = 'Hello'
|
|
```
|
|
|
|
You need to declare corresponding TypeScript error codes after `@errors`. Separate multiple error codes with spaces.
|
|
|
|
::: note
|
|
If you don't know which error code to add, try writing the code first and wait for compilation to fail.
|
|
You should see relevant error information in the console, then find the corresponding error code in the
|
|
error message's `description`. Then add the error code to `@errors`.
|
|
|
|
Don't worry about compilation failures terminating the process - the theme displays error information
|
|
when compilation fails while outputting uncompiled code in the code block.
|
|
:::
|
|
|
|
### `@noErrors`
|
|
|
|
Suppresses all errors in code. You can also provide error codes to suppress specific errors.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @noErrors
|
|
const str: string = 1
|
|
str = 'Hello'
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @noErrors
|
|
const str: string = 1
|
|
str = 'Hello'
|
|
```
|
|
|
|
### `@noErrorsCutted`
|
|
|
|
Ignores errors occurring in cut code.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @noErrorsCutted
|
|
const hello = 'world'
|
|
// ---cut-after---
|
|
hello = 'hi' // Should be an error but ignored because it's cut.
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @noErrorsCutted
|
|
const hello = 'world'
|
|
// ---cut-after---
|
|
hello = 'hi' // Should be an error but ignored because it's cut.
|
|
```
|
|
|
|
### `@noErrorValidation`
|
|
|
|
Disables error validation. Error messages will still render, but Twoslash won't throw errors from compile-time code.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @noErrorValidation
|
|
const str: string = 1
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @noErrorValidation
|
|
const str: string = 1
|
|
```
|
|
|
|
### `@keepNotations`
|
|
|
|
Tells Twoslash not to remove any comments and keep original code unchanged. Nodes will contain position information of original code.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @keepNotations
|
|
// @module: esnext
|
|
// @errors: 2322
|
|
const str: string = 1
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @keepNotations
|
|
// @module: esnext
|
|
// @errors: 2322
|
|
const str: string = 1
|
|
```
|
|
|
|
### Overriding Compiler Options
|
|
|
|
Use `// @name` and `// @name: value` comments to override TypeScript's
|
|
[compiler options](https://www.typescriptlang.org/tsconfig#compilerOptions). These comments will be removed from output.
|
|
|
|
__Input:__
|
|
|
|
````md
|
|
```ts twoslash
|
|
// @noImplicitAny: false
|
|
// @target: esnext
|
|
// @lib: esnext
|
|
// This should throw an error,
|
|
// but since we disabled noImplicitAny, it won't.
|
|
const fn = a => a + 1
|
|
```
|
|
````
|
|
|
|
__Output:__
|
|
|
|
```ts twoslash
|
|
// @noImplicitAny: false
|
|
// @target: esnext
|
|
// @lib: esnext
|
|
// This should throw an error,
|
|
// but since we disabled noImplicitAny, it won't.
|
|
const fn = a => a + 1
|
|
```
|