From 6d9e697e0b1fd0cb5a20b67a2eca261105da5155 Mon Sep 17 00:00:00 2001 From: Ryo_gk <62658104+ryo-gk@users.noreply.github.com> Date: Wed, 25 May 2022 19:53:39 +0900 Subject: [PATCH] use shiki instead of prismjs for syntax highlight --- docs/guide/api.md | 2 +- docs/guide/markdown-extensions.md | 4 +- package.json | 2 +- pnpm-lock.yaml | 25 +++- .../styles/components/vp-doc.css | 128 ++++-------------- src/client/theme-default/styles/vars.css | 2 +- src/node/build/bundle.ts | 8 +- src/node/markdown/markdown.ts | 8 +- src/node/markdown/plugins/highlight.ts | 55 ++------ src/node/markdown/plugins/lineNumbers.ts | 2 +- src/node/markdownToVue.ts | 4 +- src/node/plugin.ts | 8 +- src/node/shims.d.ts | 10 -- 13 files changed, 79 insertions(+), 179 deletions(-) diff --git a/docs/guide/api.md b/docs/guide/api.md index ba427e5a..a2d2f5f5 100644 --- a/docs/guide/api.md +++ b/docs/guide/api.md @@ -85,7 +85,7 @@ Because VitePress applications are server-rendered in Node.js when generating st If you are using or demoing components that are not SSR-friendly (for example, contain custom directives), you can wrap them inside the `ClientOnly` component. -```html +```vue-html diff --git a/docs/guide/markdown-extensions.md b/docs/guide/markdown-extensions.md index dbeefe11..a566881c 100644 --- a/docs/guide/markdown-extensions.md +++ b/docs/guide/markdown-extensions.md @@ -198,7 +198,7 @@ console.log('Hello, VitePress!') ## Syntax Highlighting in Code Blocks -VitePress uses [Prism](https://prismjs.com) to highlight language syntax in Markdown code blocks, using coloured text. Prism supports a wide variety of programming languages. All you need to do is append a valid language alias to the beginning backticks for the code block: +VitePress uses [Shiki](https://shiki.matsu.io/) to highlight language syntax in Markdown code blocks, using coloured text. Shiki supports a wide variety of programming languages. All you need to do is append a valid language alias to the beginning backticks for the code block: **Input** @@ -240,7 +240,7 @@ export default { ``` -A [list of valid languages](https://prismjs.com/#languages-list) is available on Prism’s site. +A [list of valid languages](https://github.com/shikijs/shiki/blob/main/docs/languages.md) is available on Shiki’s repository. ## Line Highlighting in Code Blocks diff --git a/package.json b/package.json index 51adb045..baa25af5 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "@vitejs/plugin-vue": "^2.3.2", "@vueuse/core": "^8.5.0", "body-scroll-lock": "^4.0.0-beta.0", - "prismjs": "^1.25.0", + "shiki": "^0.10.1", "vite": "^2.9.7", "vue": "3.2.33" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 555a2b16..27df0624 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,11 +56,11 @@ importers: ora: ^5.4.0 polka: ^0.5.2 prettier: ^2.3.0 - prismjs: ^1.25.0 rimraf: ^3.0.2 rollup: ^2.56.3 rollup-plugin-esbuild: ^4.8.2 semver: ^7.3.5 + shiki: ^0.10.1 sirv: ^1.0.12 typescript: ^4.6.4 vite: ^2.9.7 @@ -73,7 +73,7 @@ importers: '@vitejs/plugin-vue': 2.3.3_vite@2.9.9+vue@3.2.33 '@vueuse/core': 8.5.0_vue@3.2.33 body-scroll-lock: 4.0.0-beta.0 - prismjs: 1.25.0 + shiki: 0.10.1 vite: 2.9.9 vue: 3.2.33 devDependencies: @@ -2557,7 +2557,6 @@ packages: /jsonc-parser/3.0.0: resolution: {integrity: sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==} - dev: true /jsonfile/4.0.0: resolution: {integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=} @@ -3191,10 +3190,6 @@ packages: hasBin: true dev: true - /prismjs/1.25.0: - resolution: {integrity: sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg==} - dev: false - /process-nextick-args/2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true @@ -3464,6 +3459,14 @@ packages: resolution: {integrity: sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==} dev: true + /shiki/0.10.1: + resolution: {integrity: sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==} + dependencies: + jsonc-parser: 3.0.0 + vscode-oniguruma: 1.6.2 + vscode-textmate: 5.2.0 + dev: false + /side-channel/1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: @@ -3909,6 +3912,14 @@ packages: - stylus dev: true + /vscode-oniguruma/1.6.2: + resolution: {integrity: sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==} + dev: false + + /vscode-textmate/5.2.0: + resolution: {integrity: sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==} + dev: false + /vue-demi/0.12.5_vue@3.2.33: resolution: {integrity: sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==} engines: {node: '>=12'} diff --git a/src/client/theme-default/styles/components/vp-doc.css b/src/client/theme-default/styles/components/vp-doc.css index c0c76615..68a8e20b 100644 --- a/src/client/theme-default/styles/components/vp-doc.css +++ b/src/client/theme-default/styles/components/vp-doc.css @@ -303,7 +303,7 @@ position: relative; z-index: 1; margin: 0; - padding: 16px 24px; + padding: 14px 24px; background: transparent; overflow-x: auto; } @@ -372,100 +372,32 @@ transition: color 0.5s; } -.vp-doc [class~='language-vue']:before { content: 'vue'; } -.vp-doc [class~='language-html']:before { content: 'html'; } -.vp-doc [class~='language-vue-html']:before { content: 'template'; } -.vp-doc [class~='language-css']:before { content: 'css'; } -.vp-doc [class~='language-js']:before { content: 'js'; } -.vp-doc [class~='language-jsx']:before { content: 'jsx'; } -.vp-doc [class~='language-ts']:before { content: 'ts'; } -.vp-doc [class~='language-tsx']:before { content: 'tsx'; } -.vp-doc [class~='language-json']:before { content: 'json'; } -.vp-doc [class~='language-yaml']:before { content: 'yaml'; } -.vp-doc [class~='language-yml']:before { content: 'yaml'; } -.vp-doc [class~='language-sh']:before { content: 'sh'; } -.vp-doc [class~='language-bash']:before { content: 'sh'; } - -/** - * Code: Highlight - * - * prism.js tomorrow night eighties theme. - * https://github.com/chriskempson/tomorrow-theme - * - * @author Rose Pritchard - * -------------------------------------------------------------------------- */ - -.token.comment, -.token.block-comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: #999; -} - -.token.punctuation { - color: #ccc; -} - -.token.tag, -.token.attr-name, -.token.namespace, -.token.deleted { - color: #e2777a; -} - -.token.function-name { - color: #6196cc; -} - -.token.boolean, -.token.number, -.token.function { - color: #f08d49; -} - -.token.property, -.token.class-name, -.token.constant, -.token.symbol { - color: #f8c555; -} - -.token.selector, -.token.important, -.token.atrule, -.token.keyword, -.token.builtin { - color: #cc99cd; -} - -.token.string, -.token.char, -.token.attr-value, -.token.regex, -.token.variable { - color: #7ec699; -} - -.token.operator, -.token.entity, -.token.url { - color: #67cdcc; -} - -.token.important, -.token.bold { - font-weight: bold; -} - -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - -.token.inserted { - color: green; -} +.vp-doc [class~='language-vue']:before { content: 'vue'; } +.vp-doc [class~='language-html']:before { content: 'html'; } +.vp-doc [class~='language-vue-html']:before { content: 'template'; } +.vp-doc [class~='language-md']:before { content: 'md' } +.vp-doc [class~='language-markdown']:before { content: 'md'; } +.vp-doc [class~='language-css']:before { content: 'css'; } +.vp-doc [class~='language-sass']:before { content: 'sass'; } +.vp-doc [class~='language-scss']:before { content: 'scss'; } +.vp-doc [class~='language-less']:before { content: 'less'; } +.vp-doc [class~='language-stylus']:before { content: 'styl'; } +.vp-doc [class~='language-js']:before { content: 'js'; } +.vp-doc [class~='language-javascript']:before { content: 'js'; } +.vp-doc [class~='language-jsx']:before { content: 'jsx'; } +.vp-doc [class~='language-ts']:before { content: 'ts'; } +.vp-doc [class~='language-typescript']:before { content: 'ts'; } +.vp-doc [class~='language-tsx']:before { content: 'tsx'; } +.vp-doc [class~='language-json']:before { content: 'json'; } +.vp-doc [class~='language-rb']:before { content: 'rb'; } +.vp-doc [class~='language-ruby']:before { content: 'rb'; } +.vp-doc [class~='language-py']:before { content: 'py'; } +.vp-doc [class~='language-python']:before { content: 'py'; } +.vp-doc [class~='language-php']:before { content: 'php'; } +.vp-doc [class~='language-go']:before { content: 'go'; } +.vp-doc [class~='language-rust']:before { content: 'rust'; } +.vp-doc [class~='language-java']:before { content: 'java'; } +.vp-doc [class~='language-c']:before { content: 'c'; } +.vp-doc [class~='language-yaml']:before { content: 'yaml'; } +.vp-doc [class~='language-sh']:before { content: 'sh'; } +.vp-doc [class~='language-bash']:before { content: 'sh'; } diff --git a/src/client/theme-default/styles/vars.css b/src/client/theme-default/styles/vars.css index 17f2fa5b..a4cb5186 100644 --- a/src/client/theme-default/styles/vars.css +++ b/src/client/theme-default/styles/vars.css @@ -182,7 +182,7 @@ * -------------------------------------------------------------------------- */ :root { - --vp-code-line-height: 24px; + --vp-code-line-height: 1.5; --vp-code-font-size: 14px; --vp-code-block-color: var(--vp-c-text-dark-1); diff --git a/src/node/build/bundle.ts b/src/node/build/bundle.ts index 7a5a46f1..26d608fb 100644 --- a/src/node/build/bundle.ts +++ b/src/node/build/bundle.ts @@ -41,11 +41,11 @@ export async function bundle( // resolve options to pass to vite const { rollupOptions } = options - const resolveViteConfig = (ssr: boolean): ViteUserConfig => ({ + const resolveViteConfig = async (ssr: boolean): Promise => ({ root: srcDir, base: config.site.base, logLevel: 'warn', - plugins: createVitePressPlugin( + plugins: await createVitePressPlugin( root, config, ssr, @@ -108,8 +108,8 @@ export async function bundle( spinner.start('building client + server bundles...') try { ;[clientResult, serverResult] = await (Promise.all([ - config.mpa ? null : build(resolveViteConfig(false)), - build(resolveViteConfig(true)) + config.mpa ? null : build(await resolveViteConfig(false)), + build(await resolveViteConfig(true)) ]) as Promise<[RollupOutput, RollupOutput]>) } catch (e) { spinner.stopAndPersist({ diff --git a/src/node/markdown/markdown.ts b/src/node/markdown/markdown.ts index 46cbff35..b4f2d226 100644 --- a/src/node/markdown/markdown.ts +++ b/src/node/markdown/markdown.ts @@ -1,4 +1,5 @@ import MarkdownIt from 'markdown-it' +import { Theme } from 'shiki' import { parseHeader } from '../utils/parseHeader' import { highlight } from './plugins/highlight' import { slugify } from './plugins/slugify' @@ -29,6 +30,7 @@ export interface MarkdownOptions extends MarkdownIt.Options { rightDelimiter?: string allowedAttributes?: string[] } + theme?: Theme // https://github.com/Oktavilla/markdown-it-table-of-contents toc?: any externalLinks?: Record @@ -48,15 +50,15 @@ export interface MarkdownRenderer extends MarkdownIt { export type { Header } -export const createMarkdownRenderer = ( +export const createMarkdownRenderer = async ( srcDir: string, options: MarkdownOptions = {}, base: string -): MarkdownRenderer => { +): Promise => { const md = MarkdownIt({ html: true, linkify: true, - highlight, + highlight: await highlight(options.theme), ...options }) as MarkdownRenderer diff --git a/src/node/markdown/plugins/highlight.ts b/src/node/markdown/plugins/highlight.ts index aab5f996..279e8eb9 100644 --- a/src/node/markdown/plugins/highlight.ts +++ b/src/node/markdown/plugins/highlight.ts @@ -1,52 +1,15 @@ -import chalk from 'chalk' import escapeHtml from 'escape-html' -import prism from 'prismjs' -// prism is listed as actual dep so it's ok to require -const loadLanguages = require('prismjs/components/index') +export const highlight = async (theme = 'material-palenight') => { + const highlighter = await require('shiki').getHighlighter({ + theme + }) -// required to make embedded highlighting work... -loadLanguages(['markup', 'css', 'javascript']) - -function wrap(code: string, lang: string): string { - if (lang === 'text') { - code = escapeHtml(code) - } - return `
${code}
` -} - -export const highlight = (str: string, lang: string) => { - if (!lang) { - return wrap(str, 'text') - } - lang = lang.toLowerCase() - const rawLang = lang - if (lang === 'vue' || lang === 'html') { - lang = 'markup' - } - if (lang === 'md') { - lang = 'markdown' - } - if (lang === 'ts') { - lang = 'typescript' - } - if (lang === 'py') { - lang = 'python' - } - if (!prism.languages[lang]) { - try { - loadLanguages([lang]) - } catch (e) { - console.warn( - chalk.yellow( - `[vitepress] Syntax highlight for language "${lang}" is not supported.` - ) - ) + return (str: string, lang: string) => { + if (!lang || lang === 'text') { + return `
${escapeHtml(str)}
` } + + return highlighter.codeToHtml(str, lang).replace(/^/, '
')
   }
-  if (prism.languages[lang]) {
-    const code = prism.highlight(str, prism.languages[lang], lang)
-    return wrap(code, rawLang)
-  }
-  return wrap(str, 'text')
 }
diff --git a/src/node/markdown/plugins/lineNumbers.ts b/src/node/markdown/plugins/lineNumbers.ts
index 94a8f5ec..739b0222 100644
--- a/src/node/markdown/plugins/lineNumbers.ts
+++ b/src/node/markdown/plugins/lineNumbers.ts
@@ -21,7 +21,7 @@ export const lineNumberPlugin = (md: MarkdownIt) => {
 
     const finalCode = rawCode
       .replace(/<\/div>$/, `${lineNumbersWrapperCode}`)
-      .replace(/"(language-\w+)"/, '"$1 line-numbers-mode"')
+      .replace(/"(language-\w*)"/, '"$1 line-numbers-mode"')
 
     return finalCode
   }
diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts
index f3bb2542..96639f8d 100644
--- a/src/node/markdownToVue.ts
+++ b/src/node/markdownToVue.ts
@@ -21,7 +21,7 @@ export interface MarkdownCompileResult {
   includes: string[]
 }
 
-export function createMarkdownToVueRenderFn(
+export async function createMarkdownToVueRenderFn(
   srcDir: string,
   options: MarkdownOptions = {},
   pages: string[],
@@ -30,7 +30,7 @@ export function createMarkdownToVueRenderFn(
   base: string,
   includeLastUpdatedData = false
 ) {
-  const md = createMarkdownRenderer(srcDir, options, base)
+  const md = await createMarkdownRenderer(srcDir, options, base)
   pages = pages.map((p) => slash(p.replace(/\.md$/, '')))
 
   const userDefineRegex = userDefines
diff --git a/src/node/plugin.ts b/src/node/plugin.ts
index 02ba4661..d30d32b0 100644
--- a/src/node/plugin.ts
+++ b/src/node/plugin.ts
@@ -7,6 +7,8 @@ import { slash } from './utils/slash'
 import { OutputAsset, OutputChunk } from 'rollup'
 import { staticDataPlugin } from './staticDataPlugin'
 
+type Awaited = T extends Promise ? P : never
+
 const hashRE = /\.(\w+)\.js$/
 const staticInjectMarkerRE =
   /\b(const _hoisted_\d+ = \/\*(?:#|@)__PURE__\*\/\s*createStaticVNode)\("(.*)", (\d+)\)/g
@@ -46,7 +48,7 @@ export function createVitePressPlugin(
     pages
   } = siteConfig
 
-  let markdownToVue: ReturnType
+  let markdownToVue: Awaited>
 
   // lazy require plugin-vue to respect NODE_ENV in @vue/compiler-x
   const vuePlugin = require('@vitejs/plugin-vue')({
@@ -70,9 +72,9 @@ export function createVitePressPlugin(
   const vitePressPlugin: Plugin = {
     name: 'vitepress',
 
-    configResolved(resolvedConfig) {
+    async configResolved(resolvedConfig) {
       config = resolvedConfig
-      markdownToVue = createMarkdownToVueRenderFn(
+      markdownToVue = await createMarkdownToVueRenderFn(
         srcDir,
         markdown,
         pages,
diff --git a/src/node/shims.d.ts b/src/node/shims.d.ts
index 097e3cc8..af90ae3e 100644
--- a/src/node/shims.d.ts
+++ b/src/node/shims.d.ts
@@ -23,16 +23,6 @@ declare module 'escape-html' {
   export default def
 }
 
-declare module 'prismjs' {
-  const def: any
-  export default def
-}
-
-declare module 'prismjs/components/index' {
-  const def: any
-  export default def
-}
-
 declare module 'diacritics' {
   export const remove: (str: string) => string
 }