diff --git "a/docs/notes/theme/guide/\344\273\243\347\240\201/twoslash.md" "b/docs/notes/theme/guide/\344\273\243\347\240\201/twoslash.md"
index 310e0d9c8..d4e533660 100644
--- "a/docs/notes/theme/guide/\344\273\243\347\240\201/twoslash.md"
+++ "b/docs/notes/theme/guide/\344\273\243\347\240\201/twoslash.md"
@@ -1,12 +1,12 @@
---
title: Two Slash
-author: pengzhanbo
icon: material-symbols:experiment-outline
createTime: 2024/03/06 11:46:49
-permalink: /guide/markdown/experiment/
+permalink: /guide/markdown/twoslash/
+outline: [2, 4]
---
-## twoslash
+## 概述
为代码块添加支持 [TypeScript TwoSlash](https://www.typescriptlang.org/dev/twoslash/) 支持。
在代码块内提供内联类型提示。
@@ -14,9 +14,7 @@ permalink: /guide/markdown/experiment/
该功能由 [shiki](https://shiki.style/) 和 [@shikijs/twoslash](https://shiki.style/packages/twoslash) 提供支持,
并整合在 [@vuepress-plume/plugin-shikiji](https://github.com/pengzhanbo/vuepress-theme-plume/tree/main/plugins/plugin-shikiji) 中。
-:::important
-从 `vuepress@2.0.0-rc.12` 开始,不再需要对 `@vuepress/markdown` 进行额外的 hack 操作,
-因此,现在你可以安全的使用这项功能了!
+::: important __twoslash__ 是一个高级功能,您需要熟练掌握 [TypeScript](https://www.typescriptlang.org/) 的知识,并且了解 [twoslash 语法](https://twoslash.netlify.app/)。
:::
::: warning
@@ -29,26 +27,26 @@ permalink: /guide/markdown/experiment/
可能某一个 `twoslash` 的代码块编译耗时就需要额外再等待 `500ms` 以上。
但不必担心 markdown 文件热更新时的编译耗时,主题针对 代码高亮编译 耗时做了优化,即使 单个 markdown 文件
-中包含多个 代码块,主题也仅会对 **有变更的代码块** 进行编译,因此热更新的速度依然非常快。
+中包含多个 代码块,主题也仅会对 __有变更的代码块__ 进行编译,因此热更新的速度依然非常快。
:::
-### 概述
-
[twoslash](https://twoslash.netlify.app/) 是一种 `javascript` 和 `typescript` 标记语言。
你可以编写一个代码示例来描述整个 `javascript` 项目。
-`twoslash` 将 **双斜杠** 作为 代码示例的预处理器。
+`twoslash` 将 __双斜杠注释__ (`//`) 视为 代码示例的预处理器。
`twoslash` 使用与文本编辑器相同的编译器 API 来提供类型驱动的悬停信息、准确的错误和类型标注。
-### 功能预览
+## 功能预览
+
+将鼠标悬停在 __变量__ 或 __函数__ 上查看效果:
-````md
```ts twoslash
import { getHighlighterCore } from 'shiki/core'
const highlighter = await getHighlighterCore({})
// ^?
+//
// @log: Custom log message
const a = 1
@@ -58,41 +56,114 @@ const b = 1
const c = 1
// @annotate: Custom annotation message
```
-````
-将鼠标悬停在 `highlighter` 变量上查看效果:
+## 配置
+
+### 启用功能
+
+在 主题配置中,启用 `twoslash` 选项。
+
+::: code-tabs
+@tab .vuepress/config.ts
+
+```ts
+export default defineUserConfig({
+ theme: plumeTheme({
+ plugins: {
+ shiki: { twoslash: true },
+ },
+ }),
+})
+```
+:::
+
+### 从 `node_modules` 中导入类型文件
+
+__twoslash__ 最大的特点就是对代码块进行类型编译,默认支持从项目的 `node_modules` 中导入类型文件。
+
+例如,如果您需要使用 `express`的类型提示,你需要在项目中安装 `@types/express` 依赖:
+
+::: npm-to
+
+```sh
+npm i -D @types/express
+```
+
+:::
+
+然后,可以在 代码块中使用 `express` 的类型,如下所示:
+
+```` md
```ts twoslash
-import { getHighlighterCore } from 'shiki/core'
+import express from 'express'
-const highlighter = await getHighlighterCore({})
-// ^?
+const app = express()
+```
+````
-// @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
+### 导入本地类型文件
+
+对于导入本地类型文件,由于代码块中的代码在编译时,并不容易确定代码的真实路径,我们很难直观的在 代码块中通过
+__相对路径__ 导入类型文件。
+
+#### 从 `tsconfig.json` 中读取路径映射
+
+主题支持从项目根目录下的 `tsconfig.json`,读取 `compilerOptions.paths` 中的路径映射,来解决这个问题。
+
+假设你的项目的 `tsconfig.json` 配置如下:
+
+::: code-tabs
+@tab tsconfig.json
+
+```json
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
```
-::: tip
-`twoslash` 仅支持 `typescript` 和 `vue` 的 代码块。
:::
-### 开启功能
+你可以直接在 代码块中使用 `@/` 开头的路径,导入 `src` 目录下的类型文件,如下所示:
-在 主题配置中,启用 `twoslash` 选项。
+````md
+```ts twoslash
+import type { Foo } from '@/foo'
+
+const foo: Foo = 1
+```
+````
+
+#### 从 `shiki.twoslash` 中读取路径映射
+
+你可以在 `shiki.twoslash` 中配置 `compilerOptions`,来解决这个问题,如下所示:
::: code-tabs
@tab .vuepress/config.ts
```ts
+import path from 'node:path'
+
export default defineUserConfig({
theme: plumeTheme({
plugins: {
- shiki: { twoslash: true },
+ shiki: {
+ twoslash: {
+ compilerOptions: { // [!code hl:8]
+ paths: {
+ // 相对于工作目录 `process.cwd()`
+ '@/*': ['./src/*'],
+ // 使用绝对路径
+ '@@/*': [path.resolve(process.cwd(), './src/*')],
+ }
+ }
+ }
+ },
},
}),
})
@@ -100,7 +171,28 @@ export default defineUserConfig({
:::
-### 使用
+你可以直接在 代码块中使用 `@/` 开头的路径,导入 `src` 目录下的类型文件,如下所示:
+
+````md
+```ts twoslash
+import type { Foo } from '@/foo'
+
+const foo: Foo = 1
+```
+````
+
+::: important
+使用 `plugins.shiki.twoslash.compilerOptions` 可以更灵活的配置 类型编译,你可以在这里修改 `baseUrl` 等配置选项。
+
+[参考 CompilerOptions](https://www.typescriptlang.org/tsconfig/#compilerOptions)
+
+通常您只需要配置 `paths` 选项,其它选项保持默认即可,除非您了解您在配置什么。
+:::
+
+## 使用
+
+::: warning `twoslash` 仅支持 `typescript` 和 `vue` 的 代码块。
+:::
启用该功能后,你只需要在 原有的 markdown 代码块语法中,在代码语言声明后添加 `twoslash` 关键词即可:
@@ -112,120 +204,257 @@ const a = 1
主题仅会对有 `twoslash` 关键词的代码进行编译处理。
-### 语法参考
+## 语法参考
完整语法请参考 [ts-twoslasher](https://github.com/microsoft/TypeScript-Website/tree/v2/packages/ts-twoslasher) 和 [shikijs-twoslash](https://twoslash.netlify.app/)
-`twoslash` 将 **双斜杠** 视为代码示例的预处理器。
+`twoslash` 将 __双斜杠__ 视为代码示例的预处理器。
因此,所有的标记都是在 `//` 之后添加的。
+### 符号标记
+
常用的 `twoslash` 标记:
-#### `^?` {id=twoslash-hover}
+#### `^?` {#extract-type}
+
+使用 `^?` 可以提取位于其上方代码行中特定标识符的类型信息。
-`^?` 用于突出显示类型,而无需用户进行悬停交互:
+__输入:__
````md
```ts twoslash
-const a = 1
-// ^?
+const hi = 'Hello'
+const msg = `${hi}, world`
+// ^?
```
````
+__输出:__
+
```ts twoslash
-const a = 1
-// ^?
+const hi = 'Hello'
+const msg = `${hi}, world`
+// ^?
//
```
-**需要注意的是,`^`必须正确指向需要突出显示类型的变量**
+::: important 符号 `^`必须正确指向需要突出显示类型的变量
+:::
+
+#### `^|` {#completions}
-#### `^|` {id=twoslash-list}
+使用 `^|` ,可以提取特定位置的自动补全信息。
-`^|` 可以将展示代码编辑过程中的内容预测列表,如编辑器中的自动完成功能:
+__输入:__
````md
```ts twoslash
// @noErrors
-// @esModuleInterop
-import express from 'express'
-const app = express()
-app.get('/', (req, res) => {
- res.sen
-// ^|
-})
-app.listen(3000)
+console.e
+// ^|
```
````
+__输出:__
+
```ts twoslash
// @noErrors
-// @esModuleInterop
-import express from 'express'
-const app = express()
-app.get('/', (req, res) => {
- res.sen
+console.e
// ^|
-})
-app.listen(3000)
-//
-//
//
```
-**需要注意的是,`^`必须正确指向需要进行内容预测的位置**
+::: important 符号`^`必须正确指向需要进行内容预测的位置
+:::
-这些类型提示并不是凭空而来的,它依赖于你的项目中的 `node_modules`。比如 `express`。
-你需要在项目中 安装 `@types/express` 才能使用这些类型提示。
+Twoslash 会向 TypeScript 请求获取在 `^` 位置的自动补全建议,然后根据 `.` 后面的字母过滤可能的输出。
+最多会显示 5 个内联结果,如果某个补全项被标记为已弃用,输出中会予以体现。
-#### `@errors` {id=twoslash-errors}
+因此,在这种情况下,Twoslash 向 TypeScript 请求 `console` 的补全建议,然后筛选出以 `e` 开头的补全项。
+注意,设置 `// @noErrors` 编译器标志,因为 `console.e` 是一个失败的 TypeScript 代码示例,但我们并不关心这一点。
-`@errors: ` 显示代码是如何出现错误的:
+#### `^^^` {#highlighting}
+
+使用 `^^^` 来突出显示其上方某行的特定范围。
+
+__输入:__
````md
```ts twoslash
-// @errors: 2339
-const welcome = 'Tudo bem gente?'
-const words = welcome.contains(' ')
+function add(a: number, b: number) {
+ // ^^^
+ return a + b
+}
```
````
+__输出:__
+
```ts twoslash
-// @errors: 2339
-const welcome = 'Tudo bem gente?'
-const words = welcome.contains(' ')
+function add(a: number, b: number) {
+ // ^^^
+ return a + b
+}
```
-你需要在 `@errors` 后面,声明对应的 `typescript` 错误码。
-
-::: note
-如果你不知道应该添加哪个 错误码,你可以先尝试直接编写好代码,然后等待编译失败,
-你应该能够在控制台中查看到相关的错误信息,然后在错误信息的 `description` 中找到对应的错误码。
-然后再将错误码添加到 `@errors` 中
+::: important 使用连续多个符号`^`正确指向需要突出显示的范围
:::
-#### `---cut---` {id=twoslash-cut}
+### `@filename` {#import-files}
-`---cut---` 用于将代码分割, 被 `---cut---` 标记的,在其之前的代码将被忽略,不会显示:
+`@filename: ` 用于声明后续的代码将来自哪个文件,
+你可以在其他部分的代码中通过 `import` 导入该文件。
+
+__输入:__
````md
```ts twoslash
-const hi = 'hi'
+// @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')
+```
+````
+
+__输出:__
+
+```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')
+```
+
+### 剪切代码
+
+#### `---cut-before---` {#cut-before}
+
+在 TypeScript 生成项目并提取所有编辑器信息(如标识符、查询、高亮等)后,剪切操作会修正所有偏移量和行号,
+以适应较小的输出。
+
+用户所见的内容为 `// ---cut-before---` 以下的部分。此外,还可使用简写形式 `// ---cut---` 。
+
+__输入:__
+
+````md
+```ts twoslash
+const level: string = 'Danger'
+// ---cut---
+console.log(level)
+````
+
+__输出:__
+
+```ts twoslash
+const level: string = 'Danger'
// ---cut---
-const msg = `${hi} words` as const
-// ^?
+console.log(level)
+```
+
+仅显示单个文件:
+
+__输入:__
+
+````md
+```ts twoslash
+// @filename: a.ts
+export const helloWorld: string = 'Hi'
+// ---cut---
+// @filename: b.ts
+import { helloWorld } from './a'
+
+console.log(helloWorld)
```
````
+__输出:__
+
```ts twoslash
-const hi = 'hi'
+// @filename: a.ts
+export const helloWorld: string = 'Hi'
// ---cut---
-const msg = `${hi} words` as const
-// ^?
-//
+// @filename: b.ts
+import { helloWorld } from './a'
+
+console.log(helloWorld)
+```
+
+只显示最后两行,但对 TypeScript 来说,这是一个包含两个文件的程序,并且所有 IDE 信息在文件间都正确连接。
+这就是为什么 `// @filename: [file]` 是唯一不会被移除的 Twoslash 命令,因为如果不相关,它可以被 `---cut---` 掉。
+
+#### `---cut-after---` {#cut-after}
+
+`---cut-before---` 的兄弟,用于修剪符号后的所有内容:
+
+__输入:__
+
+````md
+```ts twoslash
+const level: string = 'Danger'
+// ---cut-before---
+console.log(level)
+// ---cut-after---
+console.log('This is not shown')
+```
+````
+
+__输出:__
+
+```ts twoslash
+const level: string = 'Danger'
+// ---cut-before---
+console.log(level)
+// ---cut-after---
+console.log('This is not shown')
+```
+
+#### `---cut-start---` 和 `---cut-end---` {#cut-start-end}
+
+你也可以使用 `---cut-start---` 和 `---cut-end---` 对来剪切两个符号之间的代码段。
+
+__输入:__
+
+````md
+```ts twoslash
+const level: string = 'Danger'
+// ---cut-start---
+console.log(level) // 这里是被剪切的
+// ---cut-end---
+console.log('This is shown')
```
+````
-#### 自定义输出信息 {id=twoslash-custom-message}
+__输出:__
+
+```ts twoslash
+const level: string = 'Danger'
+// ---cut-start---
+console.log(level) // 这里是被剪切的
+// ---cut-end---
+console.log('This is shown')
+```
+
+支持多个实例以剪切多个部分,但符号必须成对出现。
+
+### 自定义输出信息 {id=twoslash-custom-message}
`@log`, `@error`, `@warn` 和 `@annotate` 用于向用户输出不同级别的自定义信息
@@ -251,45 +480,284 @@ const c = 1
// @annotate: Custom annotation message
```
-#### import_files {id=twoslash-import-files}
+### 输出已编译文件
-`@filename: ` 用于声明后续的代码将来自哪个文件,
-你可以在其他部分的代码中通过 `import` 导入该文件。
+运行 Twoslash 代码示例会触发完整的 TypeScript 编译运行,该运行会在虚拟文件系统中创建文件。
+你可以将代码示例的内容替换为对项目运行TypeScript后的结果。
+
+#### `@showEmit`
+
+`// @showEmit` 是 告诉 Twoslash 你希望将代码示例的输出替换为等效的 ·.js· 文件的主要命令。
+
+__输入:__
````md
```ts twoslash
-// @filename: sum.ts
-export function sum(a: number, b: number): number {
- return a + b
-}
+// @showEmit
+const level: string = 'Danger'
+```
+````
-// @filename: ok.ts
-import { sum } from './sum'
-sum(1, 2)
+__输出:__
-// @filename: error.ts
-// @errors: 2345
-import { sum } from './sum'
-sum(4, 'woops')
+```ts twoslash
+// @showEmit
+const level: string = 'Danger'
+```
+
+结果将显示此 `.ts` 文件所对应的 `.js` 文件。
+可以看到 TypeScript 输出的内容中移除了 `: string` ,并添加了 `export {}`。
+
+#### `@showEmittedFile: [file]`
+
+虽然 `.js` 文件可能是开箱即用最有用的文件,但 TypeScript 确实会在启用正确标志时发出其他文件(`.d.ts`和 `.map`),
+并且在有多文件代码示例时,你可能需要告诉 Twoslash 显示哪个文件。
+对于所有这些情况,你也可以添加 `@showEmittedFile: [file]` 来告诉 Twoslash 你想显示哪个文件。
+
+__显示TypeScript代码示例的 `.d.ts` 文件:__
+
+__输入:__
+
+````md
+```ts twoslash
+// @declaration
+// @showEmit
+// @showEmittedFile: index.d.ts
+export const hello = 'world'
```
````
+__输出:__
+
```ts twoslash
-// @filename: sum.ts
-export function sum(a: number, b: number): number {
- return a + b
-}
+// @declaration
+// @showEmit
+// @showEmittedFile: index.d.ts
+export const hello = 'world'
+```
-// @filename: ok.ts
-import { sum } from './sum'
-sum(1, 2)
+__显示 JavaScript 到 TypeScript 的 `.map` 文件:__
-// @filename: error.ts
-// @errors: 2345
-import { sum } from './sum'
-sum(4, 'woops')
+__输入:__
+
+````md
+```ts twoslash
+// @sourceMap
+// @showEmit
+// @showEmittedFile: index.js.map
+export const hello = 'world'
```
+````
-::: warning
-在本节中未提及的其他标记,由于未正式验证其是否可用,请谨慎使用。
+__输出:__
+
+```ts twoslash
+// @sourceMap
+// @showEmit
+// @showEmittedFile: index.js.map
+export const hello = 'world'
+```
+
+__显示 `.d.ts` 文件的 `.map`(主要用于项目引用):__
+
+__输入:__
+
+````md
+```ts twoslash
+// @declaration
+// @declarationMap
+// @showEmit
+// @showEmittedFile: index.d.ts.map
+export const hello: string = 'world'
+```
+````
+
+__输出:__
+
+```ts twoslash
+// @declaration
+// @declarationMap
+// @showEmit
+// @showEmittedFile: index.d.ts.map
+export const hello: string = 'world'
+```
+
+__为 `b.ts` 生成 `.js` 文件:__
+
+__输入:__
+
+````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)
+```
+````
+
+__输出:__
+
+```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: ` 显示代码是如何出现错误的:
+
+__输入:__
+
+````md
+```ts twoslash
+// @errors: 2322 2588
+const str: string = 1
+str = 'Hello'
+````
+
+__输出:__
+
+```ts twoslash
+// @errors: 2322 2588
+const str: string = 1
+str = 'Hello'
+```
+
+你需要在 `@errors` 后面,声明对应的 `typescript` 错误码。使用空格分隔多个错误代码。
+
+::: note
+如果你不知道应该添加哪个 错误码,你可以先尝试直接编写好代码,然后等待编译失败,
+你应该能够在控制台中查看到相关的错误信息,然后在错误信息的 `description` 中找到对应的错误码。
+然后再将错误码添加到 `@errors` 中。
+
+不用担心变异失败会终止进程,主题会在编译失败时显示错误信息,同时在代码块中输出未编译的代码。
:::
+
+### `@noErrors`
+
+在代码中屏蔽所有错误。你还可以提供错误代码来屏蔽特定错误。
+
+__输入:__
+
+````md
+```ts twoslash
+// @noErrors
+const str: string = 1
+str = 'Hello'
+```
+````
+
+__输出:__
+
+```ts twoslash
+// @noErrors
+const str: string = 1
+str = 'Hello'
+```
+
+### `@noErrorsCutted`
+
+忽略在剪切代码中发生的错误。
+
+__输入:__
+
+````md
+```ts twoslash
+// @noErrorsCutted
+const hello = 'world'
+// ---cut-after---
+hello = 'hi' // 本应为错误,但因被截断而忽略。
+```
+````
+
+__输出:__
+
+```ts twoslash
+// @noErrorsCutted
+const hello = 'world'
+// ---cut-after---
+hello = 'hi' // 本应为错误,但因被截断而忽略。
+```
+
+### `@noErrorValidation`
+
+禁用错误验证,错误信息仍将呈现,但 Twoslash 不会抛出编译时代码中的错误。
+
+__输入:__
+
+````md
+```ts twoslash
+// @noErrorValidation
+const str: string = 1
+```
+````
+
+__输出:__
+
+```ts twoslash
+// @noErrorValidation
+const str: string = 1
+```
+
+### `@keepNotations`
+
+告知Twoslash不要移除任何注释,并保持原始代码不变。节点将包含原始代码的位置信息。
+
+__输入:__
+
+````md
+```ts twoslash
+// @keepNotations
+// @module: esnext
+// @errors: 2322
+const str: string = 1
+```
+````
+
+__输出:__
+
+```ts twoslash
+// @keepNotations
+// @module: esnext
+// @errors: 2322
+const str: string = 1
+```
+
+### 覆盖编译器选项
+
+使用 `// @name` 和 `// @name: value` 注释来覆盖 TypeScript 的[编译器选项](https://www.typescriptlang.org/tsconfig#compilerOptions)。
+这些注释将从输出中移除。
+
+__输入:__
+
+````md
+```ts twoslash
+// @noImplicitAny: false
+// @target: esnext
+// @lib: esnext
+// 这本应抛出一个错误,
+// 但由于我们禁用了noImplicitAny,所以不会抛出错误。
+const fn = a => a + 1
+```
+````
+
+__输出:__
+
+```ts twoslash
+// @noImplicitAny: false
+// @target: esnext
+// @lib: esnext
+// 这本应抛出一个错误,
+// 但由于我们禁用了noImplicitAny,所以不会抛出错误。
+const fn = a => a + 1
+```
diff --git a/plugins/plugin-shikiji/src/node/highlight/highlight.ts b/plugins/plugin-shikiji/src/node/highlight/highlight.ts
index 6f05d4bd3..667da5fed 100644
--- a/plugins/plugin-shikiji/src/node/highlight/highlight.ts
+++ b/plugins/plugin-shikiji/src/node/highlight/highlight.ts
@@ -1,7 +1,7 @@
import type { HighlighterOptions, ThemeOptions } from '../types.js'
import { customAlphabet } from 'nanoid'
import { bundledLanguages, createHighlighter } from 'shiki'
-import { logger } from 'vuepress/utils'
+import { colors, logger } from 'vuepress/utils'
import { getLanguage } from './getLanguage.js'
import { baseTransformers, getInlineTransformers } from './transformers.js'
@@ -45,7 +45,13 @@ export async function highlight(
lang,
transformers: [
...baseTransformers,
- ...getInlineTransformers({ attrs, lang, enabledTwoslash, whitespace }),
+ ...getInlineTransformers({
+ attrs,
+ lang,
+ enabledTwoslash,
+ whitespace,
+ twoslash: options.twoslash,
+ }),
...userTransformers,
],
meta: { __raw: attrs },
@@ -59,7 +65,11 @@ export async function highlight(
return rendered
}
catch (e) {
- logger.error(e)
+ logger.error(
+ (e as Error)?.message,
+ '\n',
+ (e as Error)?.stack ? colors.gray(String((e as Error)?.stack)) : '',
+ )
return str
}
}
diff --git a/plugins/plugin-shikiji/src/node/highlight/index.ts b/plugins/plugin-shikiji/src/node/highlight/index.ts
index e01cb0d39..6e06de950 100644
--- a/plugins/plugin-shikiji/src/node/highlight/index.ts
+++ b/plugins/plugin-shikiji/src/node/highlight/index.ts
@@ -1,2 +1,3 @@
export * from './highlight.js'
+export * from './resolveTsPaths.js'
export * from './scanLanguages.js'
diff --git a/plugins/plugin-shikiji/src/node/highlight/resolveTsPaths.ts b/plugins/plugin-shikiji/src/node/highlight/resolveTsPaths.ts
new file mode 100644
index 000000000..0988456ea
--- /dev/null
+++ b/plugins/plugin-shikiji/src/node/highlight/resolveTsPaths.ts
@@ -0,0 +1,28 @@
+import fs from 'node:fs/promises'
+import path from 'node:path'
+import process from 'node:process'
+
+export async function resolveTsPaths(): Promise | undefined> {
+ const tsconfigPath = path.join(process.cwd(), 'tsconfig.json')
+ try {
+ const tsconfig = JSON.parse(await fs.readFile(tsconfigPath, 'utf-8'))
+ const paths = tsconfig.compilerOptions?.paths ?? undefined
+ const baseUrl = tsconfig.compilerOptions?.baseUrl
+
+ if (baseUrl && paths) {
+ const dirname = path.join(process.cwd(), baseUrl)
+ for (const key in paths) {
+ const value = paths[key]
+ if (Array.isArray(value))
+ paths[key] = value.map(v => path.resolve(dirname, v))
+ else if (value)
+ paths[key] = path.resolve(dirname, value)
+ }
+ }
+
+ return paths
+ }
+ catch {
+ return undefined
+ }
+}
diff --git a/plugins/plugin-shikiji/src/node/highlight/transformers.ts b/plugins/plugin-shikiji/src/node/highlight/transformers.ts
index b8394cc5e..c15559b5f 100644
--- a/plugins/plugin-shikiji/src/node/highlight/transformers.ts
+++ b/plugins/plugin-shikiji/src/node/highlight/transformers.ts
@@ -1,5 +1,8 @@
+import type { TransformerTwoslashOptions } from '@shikijs/twoslash/core'
import type { ShikiTransformer } from 'shiki'
+import type { VueSpecificOptions } from 'twoslash-vue'
import type { WhitespacePosition } from '../utils/index.js'
+import process from 'node:process'
import {
transformerCompactLineOptions,
transformerNotationDiff,
@@ -10,7 +13,9 @@ import {
transformerRemoveNotationEscape,
transformerRenderWhitespace,
} from '@shikijs/transformers'
+import { defaultTwoslashOptions } from '@shikijs/twoslash/core'
import { addClassToHast } from 'shiki'
+import { isPlainObject } from 'vuepress/shared'
import { defaultHoverInfoProcessor, transformerTwoslash } from '../twoslash/rendererTransformer.js'
import { attrsToLines, resolveWhitespacePosition } from '../utils/index.js'
@@ -53,11 +58,12 @@ export const baseTransformers: ShikiTransformer[] = [
]
const vueRE = /-vue$/
-export function getInlineTransformers({ attrs, lang, enabledTwoslash, whitespace }: {
+export function getInlineTransformers({ attrs, lang, enabledTwoslash, whitespace, twoslash }: {
attrs: string
lang: string
enabledTwoslash: boolean
whitespace: boolean | WhitespacePosition
+ twoslash?: boolean | TransformerTwoslashOptions['twoslashOptions'] & VueSpecificOptions
}): ShikiTransformer[] {
const vPre = vueRE.test(lang) ? '' : 'v-pre'
const inlineTransformers: ShikiTransformer[] = [
@@ -65,10 +71,20 @@ export function getInlineTransformers({ attrs, lang, enabledTwoslash, whitespace
]
if (enabledTwoslash) {
+ const { compilerOptions, ...twoslashOptions } = isPlainObject(twoslash) ? twoslash : {}
+ const defaultOptions = defaultTwoslashOptions()
inlineTransformers.push(transformerTwoslash({
processHoverInfo(info) {
return defaultHoverInfoProcessor(info)
},
+ twoslashOptions: {
+ ...defaultOptions,
+ ...twoslashOptions,
+ compilerOptions: {
+ baseUrl: process.cwd(),
+ ...compilerOptions,
+ },
+ },
}))
}
else {
diff --git a/plugins/plugin-shikiji/src/node/shikiPlugin.ts b/plugins/plugin-shikiji/src/node/shikiPlugin.ts
index ab21f3e03..efcb5fbaf 100644
--- a/plugins/plugin-shikiji/src/node/shikiPlugin.ts
+++ b/plugins/plugin-shikiji/src/node/shikiPlugin.ts
@@ -8,7 +8,7 @@ import type {
import { isPlainObject } from 'vuepress/shared'
import { colors } from 'vuepress/utils'
import { copyCodeButtonPlugin } from './copy-code-button/index.js'
-import { highlight, scanLanguages } from './highlight/index.js'
+import { highlight, resolveTsPaths, scanLanguages } from './highlight/index.js'
import {
collapsedLinesPlugin,
highlightLinesPlugin,
@@ -47,7 +47,7 @@ export function shikiPlugin({
clientConfigFile: app => prepareClientConfigFile(app, {
copyCode: copyCode !== false,
- twoslash: options.twoslash ?? false,
+ twoslash: !!options.twoslash,
}),
extendsMarkdown: async (md, app) => {
@@ -64,6 +64,18 @@ export function shikiPlugin({
}
}
+ if (options.twoslash) {
+ const paths = await resolveTsPaths()
+ if (paths) {
+ options.twoslash = isPlainObject(options.twoslash) ? options.twoslash : {}
+ options.twoslash.compilerOptions ??= {}
+ options.twoslash.compilerOptions.paths = {
+ ...paths,
+ ...options.twoslash.compilerOptions.paths,
+ }
+ }
+ }
+
md.options.highlight = await highlight(theme, options)
if (app.env.isDebug) {
logger.info(`highlight Loaded in: ${(performance.now() - start).toFixed(2)}ms`)
diff --git a/plugins/plugin-shikiji/src/node/twoslash/rendererTransformer.ts b/plugins/plugin-shikiji/src/node/twoslash/rendererTransformer.ts
index 71fbf412a..f37b0e0fc 100644
--- a/plugins/plugin-shikiji/src/node/twoslash/rendererTransformer.ts
+++ b/plugins/plugin-shikiji/src/node/twoslash/rendererTransformer.ts
@@ -24,8 +24,6 @@ export interface VitePressPluginTwoslashOptions extends TransformerTwoslashVueOp
/**
* Create a Shiki transformer for VitePress to enable twoslash integration
- *
- * Add this to `markdown.codeTransformers` in `.vitepress/config.ts`
*/
export function transformerTwoslash(options: VitePressPluginTwoslashOptions = {}): ShikiTransformer {
const {
diff --git a/plugins/plugin-shikiji/src/node/types.ts b/plugins/plugin-shikiji/src/node/types.ts
index db434228e..ee62105b8 100644
--- a/plugins/plugin-shikiji/src/node/types.ts
+++ b/plugins/plugin-shikiji/src/node/types.ts
@@ -1,3 +1,4 @@
+import type { TransformerTwoslashOptions } from '@shikijs/twoslash/core'
import type {
BuiltinTheme,
BundledLanguage,
@@ -8,6 +9,7 @@ import type {
StringLiteralUnion,
ThemeRegistration,
} from 'shiki'
+import type { VueSpecificOptions } from 'twoslash-vue'
import type { LocaleConfig } from 'vuepress/shared'
export type ShikiLang =
@@ -73,7 +75,7 @@ export interface HighlighterOptions {
* Enable transformerTwoslash
* @default false
*/
- twoslash?: boolean
+ twoslash?: boolean | TransformerTwoslashOptions['twoslashOptions'] & VueSpecificOptions
/**
* Enable transformerRenderWhitespace
diff --git a/theme/src/client/styles/twoslash.css b/theme/src/client/styles/twoslash.css
index 00e4ebfc1..9c3dccf4e 100644
--- a/theme/src/client/styles/twoslash.css
+++ b/theme/src/client/styles/twoslash.css
@@ -18,6 +18,8 @@
--twoslash-tag-warn-bg: var(--vp-c-warning-soft);
--twoslash-tag-annotate-color: var(--vp-c-green-1);
--twoslash-tag-annotate-bg: var(--vp-c-green-soft);
+ --twoslash-highlighted-bg: var(--vp-c-gray-soft);
+ --twoslash-highlighted-border: var(--vp-c-border);
}
/* Respect people's wishes to not have animations */