refactor: migrate shikijs to shiki

This commit is contained in:
YunYouJun 2024-02-10 11:24:44 +08:00
parent cd475a9c63
commit 485ae82cef
12 changed files with 1192 additions and 798 deletions

View File

@ -37,6 +37,7 @@
"instantsearch", "instantsearch",
"jiti", "jiti",
"lightgallery", "lightgallery",
"shiki",
"twikoo", "twikoo",
"unconfig", "unconfig",
"unhead", "unhead",

View File

@ -22,7 +22,7 @@
"devDependencies": { "devDependencies": {
"@iconify-json/simple-icons": "^1.1.90", "@iconify-json/simple-icons": "^1.1.90",
"nodemon": "^3.0.3", "nodemon": "^3.0.3",
"vite": "^5.0.12", "vite": "^5.1.1",
"vitepress": "1.0.0-rc.41" "vitepress": "1.0.0-rc.42"
} }
} }

View File

@ -157,14 +157,14 @@ All icon names added to `config.unocss.safelist` will be ready for hot reloading
::: :::
::: zh-CN ::: zh-CN
基于 [Shikiji](https://shikiji.netlify.app/) 实现。 基于 [Shiki](https://shiki.style) 实现。
Valaxy 支持 `vue` 等语法高亮,拷贝代码,高亮其中某一行。 Valaxy 支持 `vue` 等语法高亮,拷贝代码,高亮其中某一行。
譬如: 譬如:
::: :::
::: en ::: en
Based on [Shikiji](https://shikiji.netlify.app/). Based on [Shiki](https://shiki.style).
Valaxy supports syntax highlighting for languages like `vue`, and also supports copying code and Valaxy supports syntax highlighting for languages like `vue`, and also supports copying code and
highlighting a particular line in the code block. highlighting a particular line in the code block.

View File

@ -56,18 +56,18 @@
"prepare": "husky install" "prepare": "husky install"
}, },
"dependencies": { "dependencies": {
"c12": "^1.6.1" "c12": "^1.7.0"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "2.6.4", "@antfu/eslint-config": "2.6.4",
"@iconify-json/logos": "^1.1.42", "@iconify-json/logos": "^1.1.42",
"@iconify-json/vscode-icons": "^1.1.33", "@iconify-json/vscode-icons": "^1.1.33",
"@microsoft/api-extractor": "^7.39.4", "@microsoft/api-extractor": "^7.40.1",
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",
"@types/markdown-it-attrs": "^4.1.3", "@types/markdown-it-attrs": "^4.1.3",
"@types/markdown-it-container": "^2.0.9", "@types/markdown-it-container": "^2.0.9",
"@types/markdown-it-emoji": "^2.0.4", "@types/markdown-it-emoji": "^2.0.4",
"@types/node": "^20.11.16", "@types/node": "^20.11.17",
"@types/prompts": "^2.4.9", "@types/prompts": "^2.4.9",
"@types/resolve": "^1.20.6", "@types/resolve": "^1.20.6",
"@valaxyjs/devtools": "workspace:*", "@valaxyjs/devtools": "workspace:*",
@ -78,7 +78,7 @@
"eslint-plugin-cypress": "^2.15.1", "eslint-plugin-cypress": "^2.15.1",
"https-localhost": "^4.7.1", "https-localhost": "^4.7.1",
"husky": "^9.0.10", "husky": "^9.0.10",
"lint-staged": "^15.2.1", "lint-staged": "^15.2.2",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
@ -86,7 +86,7 @@
"stylelint-config-recommended-vue": "^1.5.0", "stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard-scss": "^13.0.0", "stylelint-config-standard-scss": "^13.0.0",
"tsup": "^8.0.1", "tsup": "^8.0.1",
"tsx": "^4.7.0", "tsx": "^4.7.1",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"unbuild": "^2.0.0", "unbuild": "^2.0.0",
"valaxy": "workspace:*", "valaxy": "workspace:*",

View File

@ -34,6 +34,6 @@
"devDependencies": { "devDependencies": {
"typescript": "^5.3.3", "typescript": "^5.3.3",
"unbuild": "^2.0.0", "unbuild": "^2.0.0",
"vite": "^5.0.12" "vite": "^5.1.1"
} }
} }

View File

@ -58,6 +58,7 @@ export async function setupMarkdownPlugins(
) { ) {
const mdOptions = options?.config.markdown || {} const mdOptions = options?.config.markdown || {}
const theme = mdOptions.theme ?? defaultCodeTheme const theme = mdOptions.theme ?? defaultCodeTheme
const hasSingleTheme = typeof theme === 'string' || 'name' in theme
const siteConfig = options?.config.siteConfig || {} const siteConfig = options?.config.siteConfig || {}
if (mdOptions.preConfig) if (mdOptions.preConfig)
@ -70,8 +71,10 @@ export async function setupMarkdownPlugins(
.use(preWrapperPlugin, { theme, siteConfig }) .use(preWrapperPlugin, { theme, siteConfig })
.use(snippetPlugin, options?.userRoot) .use(snippetPlugin, options?.userRoot)
.use(containerPlugin, { .use(containerPlugin, {
hasSingleTheme,
}, {
...mdOptions.blocks, ...mdOptions.blocks,
theme, ...mdOptions?.container,
}) })
.use(cssI18nContainer, { .use(cssI18nContainer, {
languages: options?.config.siteConfig.languages, languages: options?.config.siteConfig.languages,
@ -123,7 +126,7 @@ export async function setupMarkdownPlugins(
} as FrontmatterPluginOptions) } as FrontmatterPluginOptions)
.use(headersPlugin, { .use(headersPlugin, {
slugify, slugify,
...mdOptions.headers, ...(typeof mdOptions.headers === 'boolean' ? undefined : mdOptions.headers),
} as HeadersPluginOptions) } as HeadersPluginOptions)
.use(sfcPlugin, { .use(sfcPlugin, {
...mdOptions.sfc, ...mdOptions.sfc,

View File

@ -8,15 +8,14 @@ import {
transformerNotationErrorLevel, transformerNotationErrorLevel,
transformerNotationFocus, transformerNotationFocus,
transformerNotationHighlight, transformerNotationHighlight,
} from 'shikiji-transformers' } from '@shikijs/transformers'
import type { ShikijiTransformer } from 'shikiji' import type { ShikiTransformer } from 'shiki'
import { import {
addClassToHast, addClassToHast,
bundledLanguages, bundledLanguages,
getHighlighter, getHighlighter,
isPlaintext as isPlainLang,
isSpecialLang, isSpecialLang,
} from 'shikiji' } from 'shiki'
import type { Logger } from 'vite' import type { Logger } from 'vite'
import type { MarkdownOptions, ThemeOptions } from '../types' import type { MarkdownOptions, ThemeOptions } from '../types'
@ -67,15 +66,15 @@ export async function highlight(
const highlighter = await getHighlighter({ const highlighter = await getHighlighter({
themes: themes:
(typeof theme === 'string' || 'name' in theme) typeof theme === 'object' && 'light' in theme && 'dark' in theme
? [theme] ? [theme.light, theme.dark]
: [theme.light, theme.dark], : [theme],
langs: [...Object.keys(bundledLanguages), ...(options.languages || [])], langs: [...Object.keys(bundledLanguages), ...(options.languages || [])],
langAlias: options.languageAlias, langAlias: options.languageAlias,
}) })
await options?.shikijiSetup?.(highlighter) await options?.shikiSetup?.(highlighter)
const transformers: ShikijiTransformer[] = [ const transformers: ShikiTransformer[] = [
transformerNotationDiff(), transformerNotationDiff(),
transformerNotationFocus({ transformerNotationFocus({
classActiveLine: 'has-focus', classActiveLine: 'has-focus',
@ -113,7 +112,7 @@ export async function highlight(
if (lang) { if (lang) {
const langLoaded = highlighter.getLoadedLanguages().includes(lang as any) const langLoaded = highlighter.getLoadedLanguages().includes(lang as any)
if (!langLoaded && !isPlainLang(lang) && !isSpecialLang(lang)) { if (!langLoaded && !isSpecialLang(lang)) {
logger.warn( logger.warn(
c.yellow( c.yellow(
`\nThe language '${lang}' is not loaded, falling back to '${ `\nThe language '${lang}' is not loaded, falling back to '${
@ -148,13 +147,6 @@ export async function highlight(
return s return s
} }
const fillEmptyHighlightedLine = (s: string) => {
return s.replace(
/(<span class="line highlighted">)(<\/span>)/g,
'$1<wbr>$2',
)
}
str = removeMustache(str).trimEnd() str = removeMustache(str).trimEnd()
const highlighted = highlighter.codeToHtml(str, { const highlighted = highlighter.codeToHtml(str, {
@ -169,19 +161,37 @@ export async function highlight(
node.properties['v-pre'] = '' node.properties['v-pre'] = ''
}, },
}, },
{
name: 'valaxy:empty-line',
code(hast) {
hast.children.forEach((span) => {
if (
span.type === 'element'
&& span.tagName === 'span'
&& Array.isArray(span.properties.class)
&& span.properties.class.includes('line')
&& span.children.length === 0
) {
span.children.push({
type: 'element',
tagName: 'wbr',
properties: {},
children: [],
})
}
})
},
},
...userTransformers, ...userTransformers,
], ],
meta: { meta: {
__raw: attrs, __raw: attrs,
}, },
...(typeof theme === 'string' || 'name' in theme ...(typeof theme === 'object' && 'light' in theme && 'dark' in theme
? { theme } ? { themes: theme, defaultColor: false }
: { : { theme }),
themes: theme,
defaultColor: false,
}),
}) })
return fillEmptyHighlightedLine(restoreMustache(highlighted)) return restoreMustache(highlighted)
} }
} }

View File

@ -111,11 +111,11 @@ const defaultBlocksOptions: ContainerOptions = {
}, },
} }
export function containerPlugin(md: MarkdownIt, options: ContainerOptions = {}) { export function containerPlugin(md: MarkdownIt, options: Options, containerOptions: ContainerOptions = {}) {
Object.keys(defaultBlocksOptions).forEach((optionKey) => { Object.keys(defaultBlocksOptions).forEach((optionKey) => {
const option: BlockItem = { const option: BlockItem = {
...defaultBlocksOptions[optionKey as keyof Blocks], ...defaultBlocksOptions[optionKey as keyof Blocks],
...(options[optionKey as keyof Blocks] || {}), ...(containerOptions[optionKey as keyof Blocks] || {}),
} }
md.use(...createContainer(optionKey, option)) md.use(...createContainer(optionKey, option))
@ -135,7 +135,7 @@ export function containerPlugin(md: MarkdownIt, options: ContainerOptions = {})
}) })
} }
function createCodeGroup(options: ContainerOptions): ContainerArgs { function createCodeGroup(options: Options): ContainerArgs {
return [ return [
container, container,
'code-group', 'code-group',
@ -177,9 +177,7 @@ function createCodeGroup(options: ContainerOptions): ContainerArgs {
} }
return `<div class="vp-code-group${getAdaptiveThemeMarker( return `<div class="vp-code-group${getAdaptiveThemeMarker(
{ options,
theme: options.theme!,
},
)}"><div class="tabs">${tabs}</div><div class="blocks">\n` )}"><div class="tabs">${tabs}</div><div class="blocks">\n`
} }
return `</div></div>\n` return `</div></div>\n`

View File

@ -4,6 +4,7 @@ import type { SiteConfig } from 'valaxy/types'
import type { ThemeOptions } from '../../types' import type { ThemeOptions } from '../../types'
export interface Options { export interface Options {
hasSingleTheme: boolean
theme: ThemeOptions theme: ThemeOptions
siteConfig?: SiteConfig siteConfig?: SiteConfig
} }
@ -19,16 +20,7 @@ export function extractLang(info: string) {
} }
export function getAdaptiveThemeMarker(options: Options) { export function getAdaptiveThemeMarker(options: Options) {
const { theme } = options return options.hasSingleTheme ? '' : ' vp-adaptive-theme'
const hasSingleTheme = typeof theme === 'string' || 'name' in theme
let marker = ''
if (hasSingleTheme) {
marker = ' va-adaptive-theme'
const themeName = typeof theme === 'string' ? theme : theme.name
const isDark = themeName.includes('dark') || themeName.includes('night')
marker = isDark ? ' dark' : ' light'
}
return marker
} }
export function extractTitle(info: string, html = false) { export function extractTitle(info: string, html = false) {

View File

@ -4,10 +4,10 @@ import type {
BuiltinTheme, BuiltinTheme,
Highlighter, Highlighter,
LanguageInput, LanguageInput,
ShikijiTransformer, ShikiTransformer,
ThemeRegistration ThemeRegistration
, ,
} from 'shikiji' } from 'shiki'
import type anchorPlugin from 'markdown-it-anchor' import type anchorPlugin from 'markdown-it-anchor'
import type { KatexOptions } from 'katex' import type { KatexOptions } from 'katex'
@ -26,7 +26,7 @@ import type { TocPluginOptions } from '@mdit-vue/plugin-toc'
import type { import type {
ComponentPluginOptions, ComponentPluginOptions,
} from '@mdit-vue/plugin-component' } from '@mdit-vue/plugin-component'
import type { Blocks } from './plugins/markdown-it/container' import type { Blocks, ContainerOptions } from './plugins/markdown-it/container'
export type ThemeOptions = export type ThemeOptions =
| ThemeRegistration | ThemeRegistration
@ -56,6 +56,8 @@ export interface MarkdownOptions {
allowedAttributes?: string[] allowedAttributes?: string[]
disable?: boolean disable?: boolean
} }
/* ==================== Syntax Highlighting ==================== */
/** /**
* Custom theme for syntax highlighting. * Custom theme for syntax highlighting.
* *
@ -65,21 +67,21 @@ export interface MarkdownOptions {
* @example { theme: { light: 'github-light', dark: 'github-dark' } } * @example { theme: { light: 'github-light', dark: 'github-dark' } }
* *
* You can use an existing theme. * You can use an existing theme.
* @see https://github.com/antfu/shikiji/blob/main/docs/themes.md#all-themes * @see https://shiki.style/themes
* Or add your own theme. * Or add your own theme.
* @see https://github.com/antfu/shikiji/blob/main/docs/themes.md#load-custom-themes * @see https://shiki.style/guide/load-theme
*/ */
theme?: ThemeOptions theme?: ThemeOptions
/** /**
* Languages for syntax highlighting. * Languages for syntax highlighting.
* @see https://github.com/antfu/shikiji/blob/main/docs/languages.md#all-themes * @see https://shiki.style/languages
*/ */
languages?: LanguageInput[] languages?: LanguageInput[]
/** /**
* Custom language aliases. * Custom language aliases.
* *
* @example { 'my-lang': 'js' } * @example { 'my-lang': 'js' }
* @see https://github.com/antfu/shikiji/tree/main#custom-language-aliases * @see https://shiki.style/guide/load-lang#custom-language-aliases
*/ */
languageAlias?: Record<string, string> languageAlias?: Record<string, string>
/** /**
@ -93,23 +95,51 @@ export interface MarkdownOptions {
defaultHighlightLang?: string defaultHighlightLang?: string
/** /**
* Transformers applied to code blocks * Transformers applied to code blocks
* @see https://github.com/antfu/shikiji#hast-transformers * @see https://shiki.style/guide/transformers
*/ */
codeTransformers?: ShikijiTransformer[] codeTransformers?: ShikiTransformer[]
/** /**
* Setup Shikiji instance * Setup Shiki instance
*/ */
shikijiSetup?: (shikiji: Highlighter) => void | Promise<void> shikiSetup?: (shiki: Highlighter) => void | Promise<void>
/* ==================== Markdown It Plugins ==================== */
// mdit-vue plugins // mdit-vue plugins
/**
* Options for `@mdit-vue/plugin-frontmatter`
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-frontmatter
*/
frontmatter?: FrontmatterPluginOptions frontmatter?: FrontmatterPluginOptions
headers?: HeadersPluginOptions /**
* Options for `@mdit-vue/plugin-headers`
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-headers
*/
headers?: HeadersPluginOptions | boolean
/**
* Options for `@mdit-vue/plugin-sfc`
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-sfc
*/
sfc?: SfcPluginOptions sfc?: SfcPluginOptions
/**
* Options for `@mdit-vue/plugin-toc`
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-toc
*/
toc?: TocPluginOptions toc?: TocPluginOptions
/**
* Options for `markdown-it-container`
* @see https://github.com/markdown-it/markdown-it-container
*/
container?: ContainerOptions
/**
* Custom block configurations based on `markdown-it-container`
*/
blocks?: Blocks
/** /**
* Options for `@mdit-vue/plugin-component` * Options for `@mdit-vue/plugin-component`
* @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-component * @see https://github.com/mdit-vue/mdit-vue/tree/main/packages/plugin-component
*/ */
component?: ComponentPluginOptions component?: ComponentPluginOptions
/** /**
* @see [markdown-it-image-figures](https://www.npmjs.com/package/markdown-it-image-figures) * @see [markdown-it-image-figures](https://www.npmjs.com/package/markdown-it-image-figures)
*/ */
@ -124,10 +154,6 @@ export interface MarkdownOptions {
* @see https://katex.org/docs/options.html * @see https://katex.org/docs/options.html
*/ */
katex?: KatexOptions katex?: KatexOptions
/**
* Custom block configurations
*/
blocks?: Blocks
externalLinks?: Record<string, string> externalLinks?: Record<string, string>
/* lazyload?: { /* lazyload?: {

View File

@ -65,7 +65,7 @@
"dependencies": { "dependencies": {
"@antfu/utils": "^0.7.7", "@antfu/utils": "^0.7.7",
"@ctrl/tinycolor": "^4.0.3", "@ctrl/tinycolor": "^4.0.3",
"@iconify-json/carbon": "^1.1.28", "@iconify-json/carbon": "^1.1.29",
"@iconify-json/ri": "^1.1.19", "@iconify-json/ri": "^1.1.19",
"@intlify/unplugin-vue-i18n": "^2.0.0", "@intlify/unplugin-vue-i18n": "^2.0.0",
"@types/body-scroll-lock": "^3.1.2", "@types/body-scroll-lock": "^3.1.2",
@ -74,7 +74,7 @@
"@unhead/schema-org": "^1.8.10", "@unhead/schema-org": "^1.8.10",
"@unhead/vue": "^1.8.10", "@unhead/vue": "^1.8.10",
"@valaxyjs/devtools": "workspace:*", "@valaxyjs/devtools": "workspace:*",
"@vitejs/plugin-vue": "^5.0.3", "@vitejs/plugin-vue": "^5.0.4",
"@vue/devtools-api": "^7.0.14", "@vue/devtools-api": "^7.0.14",
"@vueuse/core": "^10.7.2", "@vueuse/core": "^10.7.2",
"@vueuse/integrations": "^10.7.2", "@vueuse/integrations": "^10.7.2",
@ -113,20 +113,19 @@
"pinia": "^2.1.7", "pinia": "^2.1.7",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"sass": "^1.70.0", "sass": "^1.70.0",
"shikiji": "0.9.7", "shiki": "^1.1.1",
"shikiji-transformers": "0.9.7",
"star-markdown-css": "^0.4.2", "star-markdown-css": "^0.4.2",
"unconfig": "^0.3.11", "unconfig": "^0.3.11",
"unocss": "^0.58.4", "unocss": "^0.58.5",
"unplugin-vue-components": "^0.26.0", "unplugin-vue-components": "^0.26.0",
"unplugin-vue-router": "^0.7.0", "unplugin-vue-router": "^0.7.0",
"vanilla-lazyload": "^17.8.5", "vanilla-lazyload": "^17.8.8",
"vite": "^5.0.12", "vite": "^5.1.1",
"vite-plugin-vue-devtools": "^7.0.14", "vite-plugin-vue-devtools": "^7.0.14",
"vite-plugin-vue-layouts": "0.11.0", "vite-plugin-vue-layouts": "0.11.0",
"vite-ssg": "0.23.6", "vite-ssg": "0.23.6",
"vite-ssg-sitemap": "0.6.1", "vite-ssg-sitemap": "0.6.1",
"vue": "^3.4.15", "vue": "^3.4.18",
"vue-i18n": "^9.9.1", "vue-i18n": "^9.9.1",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"yargs": "^17.7.2" "yargs": "^17.7.2"

File diff suppressed because it is too large Load Diff