Skip to content

Commit

Permalink
perf: improve shiki twoslash options (#350)
Browse files Browse the repository at this point in the history
  • Loading branch information
pengzhanbo authored Feb 16, 2025
1 parent f994d2c commit d5aa2e3
Show file tree
Hide file tree
Showing 15 changed files with 367 additions and 192 deletions.
28 changes: 26 additions & 2 deletions docs/plugins/markdown/shiki.md
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,29 @@ body > div {

### twoslash

- Type: `boolean | TwoslashOptions`
- Type: `boolean | ShikiTwoslashOptions`

```ts
interface ShikiTwoslashOptions extends TransformerTwoslashOptions {
/**
* Requires adding `twoslash` to the code block explicitly to run twoslash
* @default true
*/
explicitTrigger?: RegExp | boolean

/**
* twoslash options
*/
twoslashOptions?: TransformerTwoslashOptions['twoslashOptions'] &
VueSpecificOptions

/**
* The options for caching resolved types
* @default true
*/
typesCache?: TwoslashTypesCache | boolean
}
```

- Default: `false`

Expand All @@ -688,7 +710,9 @@ body > div {
- Also see:

- [Shiki > Twoslash](https://shiki.style/packages/twoslash)
- [Twoslash > TwoslashOptions](https://github.com/twoslashes/twoslash/blob/main/packages/twoslash/src/types/options.ts#L18)
- [Twoslash > TransformerTwoslashOptions](https://github.com/shikijs/shiki/blob/main/packages/twoslash/src/types.ts#L30)
- [Twoslash > VueSpecificOptions](https://github.com/twoslashes/twoslash/blob/main/packages/twoslash-vue/src/index.ts#L36)
- [TwoslashTypesCache](https://github.com/vuepress/ecosystem/blob/main/tools/shiki-twoslash/src/node/options.ts#L47)

- Example:

Expand Down
28 changes: 26 additions & 2 deletions docs/zh/plugins/markdown/shiki.md
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,29 @@ body > div {

### twoslash

- 类型: `boolean | TwoslashOptions`
- 类型: `boolean | ShikiTwoslashOptions`

```ts
interface ShikiTwoslashOptions extends TransformerTwoslashOptions {
/**
* 是否需要显式地将 `twoslash` 添加到代码块中以运行 twoslash
* @default true
*/
explicitTrigger?: RegExp | boolean

/**
* twoslash 配置
*/
twoslashOptions?: TransformerTwoslashOptions['twoslashOptions'] &
VueSpecificOptions

/**
* 缓存解析后类型
* @default true
*/
typesCache?: TwoslashTypesCache | boolean
}
```

- 默认值: `false`

Expand All @@ -690,7 +712,9 @@ body > div {
- 参考:

- [Shiki > Twoslash](https://shiki.style/packages/twoslash)
- [Twoslash > TwoslashOptions](https://github.com/twoslashes/twoslash/blob/main/packages/twoslash/src/types/options.ts#L18)
- [Twoslash > TransformerTwoslashOptions](https://github.com/shikijs/shiki/blob/main/packages/twoslash/src/types.ts#L30)
- [Twoslash > VueSpecificOptions](https://github.com/twoslashes/twoslash/blob/main/packages/twoslash-vue/src/index.ts#L36)
- [TwoslashTypesCache](https://github.com/vuepress/ecosystem/blob/main/tools/shiki-twoslash/src/node/options.ts#L47)

- 示例:

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { createRequire } from 'node:module'
import type { TwoslashTransformer } from '@vuepress/shiki-twoslash'
import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki'
import type {
BundledLanguage,
BundledTheme,
HighlighterGeneric,
ShikiTransformer,
} from 'shiki'
import { createHighlighter, isSpecialLang } from 'shiki'
import { createSyncFn } from 'synckit'
import type { App } from 'vuepress'
import { isPlainObject } from 'vuepress/shared'
import type { ShikiPluginOptions } from '../../options.js'
import type { ShikiResolveLang } from '../../resolveLang.js'
import { vPreTransformer } from '../../transformers/vuepressTransformers.js'
import { resolveLanguage } from '../../utils.js'

const require = createRequire(import.meta.url)
Expand All @@ -16,16 +22,20 @@ const resolveLangSync = createSyncFn<ShikiResolveLang>(

export type ShikiLoadLang = (lang: string) => boolean

export const createShikiHighlighter = async ({
langs = [],
langAlias = {},
defaultLang,
shikiSetup,
...options
}: ShikiPluginOptions = {}): Promise<{
export const createShikiHighlighter = async (
app: App,
{
langs = [],
langAlias = {},
defaultLang,
shikiSetup,
...options
}: ShikiPluginOptions = {},
enableVPre = true,
): Promise<{
highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>
loadLang: ShikiLoadLang
twoslashTransformer: TwoslashTransformer
extraTransformers: ShikiTransformer[]
}> => {
const highlighter = await createHighlighter({
langs: [...langs, ...Object.values(langAlias)],
Expand Down Expand Up @@ -63,19 +73,31 @@ export const createShikiHighlighter = async ({
return rawGetLanguage.call(highlighter, name)
}

let twoslashTransformer: TwoslashTransformer = () => []
const extraTransformers: ShikiTransformer[] = []

if (enableVPre) extraTransformers.push(vPreTransformer)

if (options.twoslash) {
const { createTwoslashTransformer } = await import(
'@vuepress/shiki-twoslash'
const { createTwoslashTransformer, createFileSystemTypesCache } =
await import('@vuepress/shiki-twoslash')

const { typesCache, ...twoslashOptions } = isPlainObject(options.twoslash)
? options.twoslash
: {}
extraTransformers.push(
await createTwoslashTransformer({
...twoslashOptions,
typesCache:
typesCache === true || typeof typesCache === 'undefined'
? createFileSystemTypesCache({
dir: app.dir.cache('markdown/twoslash'),
})
: typesCache,
}),
)

twoslashTransformer = await createTwoslashTransformer({
twoslashOptions: isPlainObject(options.twoslash) ? options.twoslash : {},
})
}

await shikiSetup?.(highlighter)

return { highlighter, loadLang, twoslashTransformer }
return { highlighter, loadLang, extraTransformers }
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { transformerCompactLineOptions } from '@shikijs/transformers'
import type { TwoslashTransformer } from '@vuepress/shiki-twoslash'
import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki'
import type {
BundledLanguage,
BundledTheme,
HighlighterGeneric,
ShikiTransformer,
} from 'shiki'
import {
getTransformers,
whitespaceTransformer,
Expand All @@ -21,9 +25,9 @@ type MarkdownItHighlight = (
export const getHighLightFunction = (
highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>,
options: ShikiHighlightOptions,
extraTransformers: ShikiTransformer[] | undefined,
loadLang: ShikiLoadLang,
markdownFilePathGetter: MarkdownFilePathGetter,
twoslashTransformer: TwoslashTransformer = () => [],
): MarkdownItHighlight => {
const transformers = getTransformers(options)

Expand All @@ -44,7 +48,7 @@ export const getHighLightFunction = (
? [transformerCompactLineOptions(attrsToLines(attrs))]
: []),
...whitespaceTransformer(attrs, options.whitespace),
...twoslashTransformer(attrs),
...(extraTransformers ?? []),
...(options.transformers ?? []),
],
...('themes' in options
Expand Down
12 changes: 10 additions & 2 deletions plugins/markdown/plugin-shiki/src/node/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {
MarkdownItCollapsedLinesOptions,
MarkdownItLineNumbersOptions,
} from '@vuepress/highlighter-helper'
import type { TwoslashOptions } from '@vuepress/shiki-twoslash'
import type { ShikiTwoslashOptions } from '@vuepress/shiki-twoslash'
import type { MarkdownItPreWrapperOptions } from './markdown/index.js'
import type { ShikiHighlightOptions } from './types.js'

Expand All @@ -20,5 +20,13 @@ export type ShikiPluginOptions = MarkdownItLineNumbersOptions &
*
* @default false
*/
twoslash?: TwoslashOptions | boolean
twoslash?:
| boolean
| (ShikiTwoslashOptions & {
/**
* The options for caching resolved types
* @default true
*/
typesCache?: ShikiTwoslashOptions['typesCache'] | true
})
}
41 changes: 24 additions & 17 deletions plugins/markdown/plugin-shiki/src/node/shikiPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,43 @@ export const shikiPlugin = (_options: ShikiPluginOptions = {}): Plugin => {
options.twoslash = false
}

/**
* Whether to enable the `v-pre` configuration of the code block
*/
let enableVPre = true

return {
name: '@vuepress/plugin-shiki',

extendsMarkdownOptions: (opts) => {
/**
* Turn off the `v-pre` configuration of the code block.
*/
if (opts.vPre !== false) {
const vPre = isPlainObject(opts.vPre) ? opts.vPre : { block: true }
if (vPre.block) {
opts.vPre ??= {}
opts.vPre.block = false
}
enableVPre = vPre.block ?? true
} else {
enableVPre = false
}
},

extendsMarkdown: async (md) => {
const { preWrapper, lineNumbers, collapsedLines } = options

const markdownFilePathGetter = createMarkdownFilePathGetter(md)
const { highlighter, loadLang, twoslashTransformer } =
await createShikiHighlighter(options)
const { highlighter, loadLang, extraTransformers } =
await createShikiHighlighter(app, options, enableVPre)

md.options.highlight = getHighLightFunction(
highlighter,
options,
extraTransformers,
loadLang,
markdownFilePathGetter,
twoslashTransformer,
)

md.use(highlightLinesPlugin)
Expand All @@ -77,20 +98,6 @@ export const shikiPlugin = (_options: ShikiPluginOptions = {}): Plugin => {
}
},

extendsMarkdownOptions: (opts) => {
/**
* After injecting twoslash & floating-vue,
* it is necessary to turn off the `v-pre` configuration of the code block.
*/
if (options.twoslash && opts.vPre !== false) {
const vPre = isPlainObject(opts.vPre) ? opts.vPre : { block: true }
if (vPre.block) {
opts.vPre ??= {}
opts.vPre.block = false
}
}
},

clientConfigFile: () => prepareClientConfigFile(app, options),
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,10 @@ export const emptyLineTransformer: ShikiTransformer = {
})
},
}

export const vPreTransformer: ShikiTransformer = {
name: 'vuepress:v-pre',
pre(node) {
node.properties['v-pre'] = ''
},
}
4 changes: 3 additions & 1 deletion plugins/markdown/plugin-shiki/tests/shiki-preWrapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@vuepress/highlighter-helper'
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'
import type { App } from 'vuepress'
import type { MarkdownItPreWrapperOptions } from '../src/node/markdown/index.js'
import {
createMarkdownFilePathGetter,
Expand All @@ -18,7 +19,7 @@ import {
} from '../src/node/markdown/index.js'
import type { ShikiPluginOptions } from '../src/node/options.js'

const { highlighter, loadLang } = await createShikiHighlighter()
const { highlighter, loadLang } = await createShikiHighlighter({} as App)

const createMarkdown = ({
preWrapper = true,
Expand All @@ -33,6 +34,7 @@ const createMarkdown = ({
md.options.highlight = getHighLightFunction(
highlighter,
options,
[],
loadLang,
markdownFilePathGetter,
)
Expand Down
36 changes: 36 additions & 0 deletions tools/shiki-twoslash/src/node/createFileSystemTypesCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import type { TwoslashReturn } from 'twoslash'
import { hash as createHash } from 'vuepress/utils'
import type { TwoslashTypesCache } from './options.js'

export interface FileSystemTypeResultCacheOptions {
/**
* The directory to store the cache files.
*/
dir: string
}

export const createFileSystemTypesCache = ({
dir,
}: FileSystemTypeResultCacheOptions): TwoslashTypesCache => ({
init() {
mkdirSync(dir, { recursive: true })
},
read(code) {
const hash = createHash(code)
const filePath = join(dir, `${hash}.json`)
if (!existsSync(filePath)) {
return null
}
return JSON.parse(
readFileSync(filePath, { encoding: 'utf-8' }),
) as TwoslashReturn
},
write(code, data) {
const hash = createHash(code)
const filePath = join(dir, `${hash}.json`)
const json = JSON.stringify(data)
writeFileSync(filePath, json, { encoding: 'utf-8' })
},
})
Loading

0 comments on commit d5aa2e3

Please sign in to comment.