From bb501521489c0030ee8175ba87efdb34c4fe69bd Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Fri, 24 Jan 2025 23:21:50 +0800 Subject: [PATCH] feat(plugin-shiki): add built-in twoslash support --- .vscode/settings.json | 1 + docs/.vuepress/config.ts | 49 +++++-------- docs/.vuepress/theme.ts | 17 +---- docs/package.json | 1 + docs/plugins/markdown/shiki.md | 38 +++++++++++ plugins/markdown/plugin-shiki/package.json | 6 ++ .../src/client/styles/twoslash.scss | 9 +++ .../markdown/plugin-shiki/src/node/options.ts | 11 ++- .../src/node/prepareClientConfigFile.ts | 8 +++ .../src/node/transformers/getTransformers.ts | 31 ++++++++- pnpm-lock.yaml | 68 +++++++++++++------ 11 files changed, 168 insertions(+), 71 deletions(-) create mode 100644 plugins/markdown/plugin-shiki/src/client/styles/twoslash.scss diff --git a/.vscode/settings.json b/.vscode/settings.json index fa4846d636..ff286c4f93 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -77,6 +77,7 @@ "trackpad", "tsbuildinfo", "twikoo", + "twoslash", "umami", "unmount", "vuejs", diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts index 2920c98557..38b3064c6b 100644 --- a/docs/.vuepress/config.ts +++ b/docs/.vuepress/config.ts @@ -1,4 +1,5 @@ import process from 'node:process' +import { transformerTwoslash } from '@shikijs/twoslash' import { viteBundler } from '@vuepress/bundler-vite' import { webpackBundler } from '@vuepress/bundler-webpack' import { getModulePath } from '@vuepress/helper' @@ -155,39 +156,21 @@ export default defineUserConfig({ NpmBadge: path.resolve(__dirname, './components/NpmBadge.vue'), }, }), - // only enable shiki plugin in production mode - IS_PROD - ? shikiPlugin({ - langs: [ - 'bash', - 'diff', - 'json', - 'md', - 'scss', - 'ts', - 'vue', - 'less', - 'java', - 'py', - 'vb', - 'bat', - 'cs', - 'cpp', - ], - themes: { - light: 'one-light', - dark: 'one-dark-pro', - }, - lineNumbers: 10, - notationDiff: true, - notationErrorLevel: true, - notationFocus: true, - notationHighlight: true, - notationWordHighlight: true, - whitespace: true, - collapsedLines: false, - }) - : [], + shikiPlugin({ + themes: { + light: 'one-light', + dark: 'one-dark-pro', + }, + lineNumbers: 10, + notationDiff: true, + notationErrorLevel: true, + notationFocus: true, + notationHighlight: true, + notationWordHighlight: true, + whitespace: true, + collapsedLines: false, + twoslash: true, + }), cachePlugin(), ], diff --git a/docs/.vuepress/theme.ts b/docs/.vuepress/theme.ts index d3244a2f84..95a4b0754f 100644 --- a/docs/.vuepress/theme.ts +++ b/docs/.vuepress/theme.ts @@ -67,21 +67,6 @@ export default defaultTheme({ alert: true, }, // use shiki plugin in production mode instead - prismjs: IS_PROD - ? false - : { - themes: { - light: 'one-light', - dark: 'one-dark', - }, - lineNumbers: 10, - notationDiff: true, - notationErrorLevel: true, - notationFocus: true, - notationHighlight: true, - notationWordHighlight: true, - whitespace: true, - collapsedLines: false, - }, + prismjs: false, }, }) diff --git a/docs/package.json b/docs/package.json index 3c7dc223df..01e02e180d 100644 --- a/docs/package.json +++ b/docs/package.json @@ -9,6 +9,7 @@ "docs:serve": "http-server -a localhost .vuepress/dist" }, "dependencies": { + "@shikijs/twoslash": "^2.1.0", "@vuepress/bundler-vite": "2.0.0-rc.19", "@vuepress/bundler-webpack": "2.0.0-rc.19", "@vuepress/helper": "workspace:*", diff --git a/docs/plugins/markdown/shiki.md b/docs/plugins/markdown/shiki.md index 0628c4ebf7..613009005d 100644 --- a/docs/plugins/markdown/shiki.md +++ b/docs/plugins/markdown/shiki.md @@ -671,6 +671,44 @@ body > div { - Also seeļ¼š - [Shiki > Render Whitespace](https://shiki.style/packages/transformers#transformerrenderwhitespace) +### twoslash + +- Type: `boolean` + +- Default: `false` + +- Details: Whether enable [twoslash](https://github.com/twoslashes/twoslash). + + ::: tip + + For size reasons, the plugin does not include the `@shiki/twoslash` package by default. If you want to use it, you need to install it manually. + + ::: + +- Also see: + + - [Shiki > Twosplash](https://shiki.style/packages/twoslash) + +- Example: + + **Input:** + + ````md + ```ts twoslash + const a = 1 + const b = 2 + console.log(a + b) + ``` + ```` + + **Output:** + + ```ts twoslash + const a = 1 + const b = 2 + console.log(a + b) + ``` + ## Advanced Options ### defaultLang diff --git a/plugins/markdown/plugin-shiki/package.json b/plugins/markdown/plugin-shiki/package.json index d2f9f2a80c..35be403e20 100644 --- a/plugins/markdown/plugin-shiki/package.json +++ b/plugins/markdown/plugin-shiki/package.json @@ -47,8 +47,14 @@ "synckit": "^0.9.2" }, "peerDependencies": { + "@shikijs/twoslash": "^2.1.0", "vuepress": "2.0.0-rc.19" }, + "peerDependenciesMeta": { + "@shikijs/twoslash": { + "optional": true + } + }, "publishConfig": { "access": "public" }, diff --git a/plugins/markdown/plugin-shiki/src/client/styles/twoslash.scss b/plugins/markdown/plugin-shiki/src/client/styles/twoslash.scss new file mode 100644 index 0000000000..5bfbd269e3 --- /dev/null +++ b/plugins/markdown/plugin-shiki/src/client/styles/twoslash.scss @@ -0,0 +1,9 @@ +div[data-highlighter='shiki'] { + pre.twoslash { + --twoslash-popup-bg: var(--code-c-bg, var(--shiki-light-bg)); + + [data-theme='dark'] & { + --twoslash-popup-bg: var(--code-c-bg, var(--shiki-dark-bg)); + } + } +} diff --git a/plugins/markdown/plugin-shiki/src/node/options.ts b/plugins/markdown/plugin-shiki/src/node/options.ts index b1d1f4902c..8ac8420d32 100644 --- a/plugins/markdown/plugin-shiki/src/node/options.ts +++ b/plugins/markdown/plugin-shiki/src/node/options.ts @@ -11,4 +11,13 @@ import type { ShikiHighlightOptions } from './types.js' export type ShikiPluginOptions = MarkdownItLineNumbersOptions & MarkdownItPreWrapperOptions & Pick & - ShikiHighlightOptions + ShikiHighlightOptions & { + /** + * Enable twoslash + * + * @description You should install `@shikijs/twoslash` manually. + * + * @default false + */ + twoslash?: boolean + } diff --git a/plugins/markdown/plugin-shiki/src/node/prepareClientConfigFile.ts b/plugins/markdown/plugin-shiki/src/node/prepareClientConfigFile.ts index 63ca6113c8..f608d6880b 100644 --- a/plugins/markdown/plugin-shiki/src/node/prepareClientConfigFile.ts +++ b/plugins/markdown/plugin-shiki/src/node/prepareClientConfigFile.ts @@ -15,6 +15,7 @@ export const prepareClientConfigFile = ( notationHighlight, notationWordHighlight, whitespace, + twoslash, }: ShikiPluginOptions, ): Promise => { const imports: string[] = [ @@ -80,6 +81,13 @@ export const prepareClientConfigFile = ( setups.push('setupCollapsedLines()') } + if (twoslash) { + imports.push( + `import "${getModulePath('@shikijs/twoslash/style-rich.css', import.meta)}"`, + `import "${getModulePath(`${PLUGIN_NAME}/styles/twoslash.css`, import.meta)}"`, + ) + } + let code = imports.join('\n') if (setups.length) { diff --git a/plugins/markdown/plugin-shiki/src/node/transformers/getTransformers.ts b/plugins/markdown/plugin-shiki/src/node/transformers/getTransformers.ts index 51c62f5647..34f94c5090 100644 --- a/plugins/markdown/plugin-shiki/src/node/transformers/getTransformers.ts +++ b/plugins/markdown/plugin-shiki/src/node/transformers/getTransformers.ts @@ -7,10 +7,13 @@ import { transformerNotationWordHighlight, transformerRenderWhitespace, } from '@shikijs/transformers' +import type { TransformerTwoslashIndexOptions } from '@shikijs/twoslash' import type { WhitespacePosition } from '@vuepress/highlighter-helper' import { resolveWhitespacePosition } from '@vuepress/highlighter-helper' import type { ShikiTransformer } from 'shiki' +import { colors } from 'vuepress/utils' import type { ShikiHighlightOptions } from '../types.js' +import { logger } from '../utils.js' import { addClassTransformer, cleanupTransformer, @@ -18,8 +21,20 @@ import { removeEscapeTransformer, } from './vuepressTransformers.js' +let transformerTwoslash: + | ((options?: TransformerTwoslashIndexOptions) => ShikiTransformer) + | null + +try { + ;({ transformerTwoslash } = await import('@shikijs/twoslash')) +} catch { + transformerTwoslash = null +} + export const getTransformers = ( - options: ShikiHighlightOptions, + options: ShikiHighlightOptions & { + twoslash?: boolean + }, ): ShikiTransformer[] => { const transformers: ShikiTransformer[] = [] @@ -66,6 +81,20 @@ export const getTransformers = ( transformers.push(transformerMetaWordHighlight()) } + if (options.twoslash) { + if (transformerTwoslash) + transformers.push( + transformerTwoslash({ + explicitTrigger: true, + }), + ) + else { + logger.error( + `${colors.cyan('twoslash')} is enabled, but ${colors.magenta('@shikijs/twoslash')} is not installed, please install it manually`, + ) + } + } + transformers.push( addClassTransformer, cleanupTransformer, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91a4c0cfb2..021760edc1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -124,6 +124,9 @@ importers: docs: dependencies: + '@shikijs/twoslash': + specifier: ^2.1.0 + version: 2.1.0(typescript@5.7.3) '@vuepress/bundler-vite': specifier: 2.0.0-rc.19 version: 2.0.0-rc.19(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(sass-embedded@1.83.4)(sass@1.83.4)(terser@5.37.0)(tsx@4.19.2)(typescript@5.7.3)(yaml@2.7.0) @@ -930,6 +933,9 @@ importers: '@shikijs/transformers': specifier: ^2.1.0 version: 2.1.0 + '@shikijs/twoslash': + specifier: ^2.1.0 + version: 2.1.0(typescript@5.7.3) '@vuepress/helper': specifier: workspace:* version: link:../../../tools/helper @@ -2797,42 +2803,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.0': resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.0': resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.0': resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.0': resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.0': resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.0': resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} @@ -2970,61 +2970,51 @@ packages: resolution: {integrity: sha512-3pA7xecItbgOs1A5H58dDvOUEboG5UfpTq3WzAdF54acBbUM+olDJAPkgj1GRJ4ZqE12DZ9/hNS2QZk166v92A==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.32.0': resolution: {integrity: sha512-Y7XUZEVISGyge51QbYyYAEHwpGgmRrAxQXO3siyYo2kmaj72USSG8LtlQQgAtlGfxYiOwu+2BdbPjzEpcOpRmQ==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.32.0': resolution: {integrity: sha512-r7/OTF5MqeBrZo5omPXcTnjvv1GsrdH8a8RerARvDFiDwFpDVDnJyByYM/nX+mvks8XXsgPUxkwe/ltaX2VH7w==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.32.0': resolution: {integrity: sha512-HJbifC9vex9NqnlodV2BHVFNuzKL5OnsV2dvTw6e1dpZKkNjPG6WUq+nhEYV6Hv2Bv++BXkwcyoGlXnPrjAKXw==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.32.0': resolution: {integrity: sha512-VAEzZTD63YglFlWwRj3taofmkV1V3xhebDXffon7msNz4b14xKsz7utO6F8F4cqt8K/ktTl9rm88yryvDpsfOw==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.32.0': resolution: {integrity: sha512-Sts5DST1jXAc9YH/iik1C9QRsLcCoOScf3dfbY5i4kH9RJpKxiTBXqm7qU5O6zTXBTEZry69bGszr3SMgYmMcQ==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.32.0': resolution: {integrity: sha512-qhlXeV9AqxIyY9/R1h1hBD6eMvQCO34ZmdYvry/K+/MBs6d1nRFLm6BOiITLVI+nFAAB9kUB6sdJRKyVHXnqZw==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.32.0': resolution: {integrity: sha512-8ZGN7ExnV0qjXa155Rsfi6H8M4iBBwNLBM9lcVS+4NcSzOFaNqmt7djlox8pN1lWrRPMRRQ8NeDlozIGx3Omsw==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.32.0': resolution: {integrity: sha512-VDzNHtLLI5s7xd/VubyS10mq6TxvZBp+4NRWoW+Hi3tgV05RtVm4qK99+dClwTN1McA6PHwob6DEJ6PlXbY83A==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.32.0': resolution: {integrity: sha512-qcb9qYDlkxz9DxJo7SDhWxTWV1gFuwznjbTiov289pASxlfGbaOD54mgbs9+z94VwrXtKTu+2RqwlSTbiOqxGg==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.32.0': resolution: {integrity: sha512-pFDdotFDMXW2AXVbfdUEfidPAk/OtwE/Hd4eYMTNVVaCQ6Yl8et0meDaKNL63L44Haxv4UExpv9ydSf3aSayDg==} @@ -3062,6 +3052,9 @@ packages: '@shikijs/transformers@2.1.0': resolution: {integrity: sha512-3sfvh6OKUVkT5wZFU1xxiq1qqNIuCwUY3yOb9ZGm19y80UZ/eoroLE2orGNzfivyTxR93GfXXZC/ghPR0/SBow==} + '@shikijs/twoslash@2.1.0': + resolution: {integrity: sha512-tgZEk78/g1ceC/mS3xA50aIc2rArl+oiphZEdAXaoioLVNebDChhV93NzcXu4NAq4pCogfBbD5HV8qO38+fQyQ==} + '@shikijs/types@2.1.0': resolution: {integrity: sha512-OFOdHA6VEVbiQvepJ8yqicC6VmBrKxFFhM2EsHHrZESqLVAXOSeRDiuSYV185lIgp15TVic5vYBYNhTsk1xHLg==} @@ -3342,6 +3335,11 @@ packages: resolution: {integrity: sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/vfs@1.6.0': + resolution: {integrity: sha512-hvJUjNVeBMp77qPINuUvYXj4FyWeeMMKZkxEATEU3hqBAQ7qdTBCUFT7Sp0Zu0faeEtFf+ldXxMEDr/bk73ISg==} + peerDependencies: + typescript: '*' + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -5781,28 +5779,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.29.1: resolution: {integrity: sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.29.1: resolution: {integrity: sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.29.1: resolution: {integrity: sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.29.1: resolution: {integrity: sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==} @@ -7909,6 +7903,14 @@ packages: twikoo@1.6.41: resolution: {integrity: sha512-Hrp/fvk2N5oqin+9bkFVULstL+YMwQjn/vVL8r5FPIhyRBA0zWAgUri4hhCji4FsXegKR6+ihyMPTXBRENjnnA==} + twoslash-protocol@0.2.12: + resolution: {integrity: sha512-5qZLXVYfZ9ABdjqbvPc4RWMr7PrpPaaDSeaYY55vl/w1j6H6kzsWK/urAEIXlzYlyrFmyz1UbwIt+AA0ck+wbg==} + + twoslash@0.2.12: + resolution: {integrity: sha512-tEHPASMqi7kqwfJbkk7hc/4EhlrKCSLcur+TcvYki3vhIfaRMXnXjaYFgXpoZRbT6GdprD4tGuVBEmTpUgLBsw==} + peerDependencies: + typescript: '*' + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -10579,6 +10581,15 @@ snapshots: '@shikijs/core': 2.1.0 '@shikijs/types': 2.1.0 + '@shikijs/twoslash@2.1.0(typescript@5.7.3)': + dependencies: + '@shikijs/core': 2.1.0 + '@shikijs/types': 2.1.0 + twoslash: 0.2.12(typescript@5.7.3) + transitivePeerDependencies: + - supports-color + - typescript + '@shikijs/types@2.1.0': dependencies: '@shikijs/vscode-textmate': 10.0.1 @@ -10913,6 +10924,13 @@ snapshots: '@typescript-eslint/types': 8.21.0 eslint-visitor-keys: 4.2.0 + '@typescript/vfs@1.6.0(typescript@5.7.3)': + dependencies: + debug: 4.4.0 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + '@ungap/structured-clone@1.3.0': {} '@vitejs/plugin-vue@5.2.1(vite@6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(sass-embedded@1.83.4)(sass@1.83.4)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3))': @@ -15980,6 +15998,16 @@ snapshots: twikoo@1.6.41: {} + twoslash-protocol@0.2.12: {} + + twoslash@0.2.12(typescript@5.7.3): + dependencies: + '@typescript/vfs': 1.6.0(typescript@5.7.3) + twoslash-protocol: 0.2.12 + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + type-check@0.4.0: dependencies: prelude-ls: 1.2.1