mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
feat: add plugin-shikiji
This commit is contained in:
parent
14e37ed52f
commit
1111e855cb
21
plugins/plugin-shikiji/LICENSE
Normal file
21
plugins/plugin-shikiji/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2021 - PRESENT by pengzhanbo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
67
plugins/plugin-shikiji/README.md
Normal file
67
plugins/plugin-shikiji/README.md
Normal file
@ -0,0 +1,67 @@
|
||||
# `@vuepress-plume/plugin-shikiji`
|
||||
|
||||
使用 [`shikiji`](https://shikiji.netlify.app/) 来为 Markdown 代码块启用代码高亮。
|
||||
|
||||
## Install
|
||||
```
|
||||
yarn add @vuepress-plume/plugin-shikiji
|
||||
```
|
||||
## Usage
|
||||
``` js
|
||||
// .vuepress/config.js
|
||||
const shikijiPlugin = require('@vuepress-plume/plugin-shikiji')
|
||||
module.exports = {
|
||||
//...
|
||||
plugins: [
|
||||
shikijiPlugin()
|
||||
]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```ts
|
||||
interface ShikijiOptions {
|
||||
/**
|
||||
* Custom theme for syntax highlighting.
|
||||
*
|
||||
* You can also pass an object with `light` and `dark` themes to support dual themes.
|
||||
*
|
||||
* @example { theme: 'github-dark' }
|
||||
* @example { theme: { light: 'github-light', dark: 'github-dark' } }
|
||||
*
|
||||
* You can use an existing theme.
|
||||
* @see https://github.com/antfu/shikiji/blob/main/docs/themes.md#all-themes
|
||||
* Or add your own theme.
|
||||
* @see https://github.com/antfu/shikiji/blob/main/docs/themes.md#load-custom-themes
|
||||
*/
|
||||
theme?: ThemeOptions
|
||||
/**
|
||||
* Languages for syntax highlighting.
|
||||
* @see https://github.com/antfu/shikiji/blob/main/docs/languages.md#all-themes
|
||||
*/
|
||||
languages?: LanguageInput[]
|
||||
/**
|
||||
* Custom language aliases.
|
||||
*
|
||||
* @example { 'my-lang': 'js' }
|
||||
* @see https://github.com/antfu/shikiji/tree/main#custom-language-aliases
|
||||
*/
|
||||
languageAlias?: Record<string, string>
|
||||
/**
|
||||
* Setup Shikiji instance
|
||||
*/
|
||||
shikijiSetup?: (shikiji: Highlighter) => void | Promise<void>
|
||||
/**
|
||||
* Fallback language when the specified language is not available.
|
||||
*/
|
||||
defaultHighlightLang?: string
|
||||
/**
|
||||
* Transformers applied to code blocks
|
||||
* @see https://github.com/antfu/shikiji#hast-transformers
|
||||
*/
|
||||
codeTransformers?: ShikijiTransformer[]
|
||||
}
|
||||
|
||||
```
|
||||
45
plugins/plugin-shikiji/package.json
Normal file
45
plugins/plugin-shikiji/package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@vuepress-plume/plugin-shikiji",
|
||||
"version": "1.0.0-beta.89",
|
||||
"description": "The Plugin for VuePres 2",
|
||||
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": "pengzhanbo <volodymyr@foxmail.com>",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./lib/node/index.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "lib/node/index.js",
|
||||
"types": "./lib/node/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm run clean && pnpm run copy && pnpm run ts",
|
||||
"clean": "rimraf lib *.tsbuildinfo",
|
||||
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
|
||||
"ts": "tsc -b tsconfig.build.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vuepress/core": "2.0.0-rc.0",
|
||||
"@vuepress/utils": "2.0.0-rc.0",
|
||||
"nanoid": "^5.0.4",
|
||||
"picocolors": "^1.0.0",
|
||||
"shikiji": "^0.9.11",
|
||||
"shikiji-transformers": "^0.9.11"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keyword": [
|
||||
"VuePress",
|
||||
"vuepress plugin",
|
||||
"shikiji",
|
||||
"vuepress-plugin-shikiji"
|
||||
]
|
||||
}
|
||||
150
plugins/plugin-shikiji/src/node/highlight.ts
Normal file
150
plugins/plugin-shikiji/src/node/highlight.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { logger } from '@vuepress/utils'
|
||||
import { customAlphabet } from 'nanoid'
|
||||
import c from 'picocolors'
|
||||
import type { ShikijiTransformer } from 'shikiji'
|
||||
import {
|
||||
addClassToHast,
|
||||
bundledLanguages,
|
||||
getHighlighter,
|
||||
isPlaintext as isPlainLang,
|
||||
isSpecialLang
|
||||
} from 'shikiji'
|
||||
import {
|
||||
transformerNotationDiff,
|
||||
transformerNotationErrorLevel,
|
||||
transformerNotationFocus,
|
||||
transformerNotationHighlight
|
||||
} from 'shikiji-transformers'
|
||||
import type { HighlighterOptions, ThemeOptions } from './types.js'
|
||||
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
|
||||
|
||||
export async function highlight(
|
||||
theme: ThemeOptions,
|
||||
options: HighlighterOptions,
|
||||
): Promise<(str: string, lang: string, attrs: string) => string> {
|
||||
const {
|
||||
defaultHighlightLang: defaultLang = '',
|
||||
codeTransformers: userTransformers = []
|
||||
} = options
|
||||
|
||||
const highlighter = await getHighlighter({
|
||||
themes:
|
||||
typeof theme === 'string' || 'name' in theme
|
||||
? [theme]
|
||||
: [theme.light, theme.dark],
|
||||
langs: [...Object.keys(bundledLanguages), ...(options.languages || [])],
|
||||
langAlias: options.languageAlias
|
||||
})
|
||||
|
||||
await options?.shikijiSetup?.(highlighter)
|
||||
|
||||
const transformers: ShikijiTransformer[] = [
|
||||
transformerNotationDiff(),
|
||||
transformerNotationFocus({
|
||||
classActiveLine: 'has-focus',
|
||||
classActivePre: 'has-focused-lines'
|
||||
}),
|
||||
transformerNotationHighlight(),
|
||||
transformerNotationErrorLevel(),
|
||||
{
|
||||
name: 'vuepress:add-class',
|
||||
pre(node) {
|
||||
addClassToHast(node, 'vp-code')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'vuepress:clean-up',
|
||||
pre(node) {
|
||||
delete node.properties.tabindex
|
||||
delete node.properties.style
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const vueRE = /-vue$/
|
||||
const lineNoStartRE = /=(\d*)/
|
||||
const lineNoRE = /:(no-)?line-numbers(=\d*)?$/
|
||||
const mustacheRE = /\{\{.*?\}\}/g
|
||||
|
||||
return (str: string, lang: string, attrs: string) => {
|
||||
const vPre = vueRE.test(lang) ? '' : 'v-pre'
|
||||
lang =
|
||||
lang
|
||||
.replace(lineNoStartRE, '')
|
||||
.replace(lineNoRE, '')
|
||||
.replace(vueRE, '')
|
||||
.toLowerCase() || defaultLang
|
||||
|
||||
if (lang) {
|
||||
const langLoaded = highlighter.getLoadedLanguages().includes(lang as any)
|
||||
if (!langLoaded && !isPlainLang(lang) && !isSpecialLang(lang)) {
|
||||
logger.warn(
|
||||
c.yellow(
|
||||
`\nThe language '${lang}' is not loaded, falling back to '${defaultLang || 'txt'
|
||||
}' for syntax highlighting.`
|
||||
)
|
||||
)
|
||||
lang = defaultLang
|
||||
}
|
||||
}
|
||||
|
||||
// const lineOptions = attrsToLines(attrs)
|
||||
|
||||
const mustaches = new Map<string, string>()
|
||||
|
||||
const removeMustache = (s: string) => {
|
||||
if (vPre) return s
|
||||
return s.replace(mustacheRE, (match) => {
|
||||
let marker = mustaches.get(match)
|
||||
if (!marker) {
|
||||
marker = nanoid()
|
||||
mustaches.set(match, marker)
|
||||
}
|
||||
return marker
|
||||
})
|
||||
}
|
||||
|
||||
const restoreMustache = (s: string) => {
|
||||
mustaches.forEach((marker, match) => {
|
||||
s = s.replaceAll(marker, match)
|
||||
})
|
||||
return s
|
||||
}
|
||||
|
||||
const fillEmptyHighlightedLine = (s: string) => {
|
||||
return s.replace(
|
||||
/(<span class="line highlighted">)(<\/span>)/g,
|
||||
'$1<wbr>$2'
|
||||
) + '\n'
|
||||
}
|
||||
|
||||
str = removeMustache(str).trimEnd()
|
||||
|
||||
const highlighted = highlighter.codeToHtml(str, {
|
||||
lang,
|
||||
transformers: [
|
||||
...transformers,
|
||||
// transformerCompactLineOptions(lineOptions),
|
||||
// {
|
||||
// name: 'vitepress:v-pre',
|
||||
// pre(node) {
|
||||
// if (vPre) node.properties['v-pre'] = ''
|
||||
// }
|
||||
// },
|
||||
...userTransformers
|
||||
],
|
||||
meta: {
|
||||
__raw: attrs
|
||||
},
|
||||
...(typeof theme === 'string' || 'name' in theme
|
||||
? { theme }
|
||||
: {
|
||||
themes: theme,
|
||||
defaultColor: false
|
||||
})
|
||||
})
|
||||
|
||||
return fillEmptyHighlightedLine(restoreMustache(highlighted))
|
||||
}
|
||||
}
|
||||
6
plugins/plugin-shikiji/src/node/index.ts
Normal file
6
plugins/plugin-shikiji/src/node/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { shikijiPlugin } from './shikijiPlugin.js'
|
||||
|
||||
export * from './shikijiPlugin.js'
|
||||
export * from './types.js'
|
||||
|
||||
export default shikijiPlugin
|
||||
19
plugins/plugin-shikiji/src/node/shikijiPlugin.ts
Normal file
19
plugins/plugin-shikiji/src/node/shikijiPlugin.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { Plugin } from '@vuepress/core'
|
||||
import { highlight } from './highlight.js'
|
||||
import type { HighlighterOptions } from './types'
|
||||
/**
|
||||
* Options of @vuepress/plugin-shiki
|
||||
*/
|
||||
export type ShikijiPluginOptions = HighlighterOptions
|
||||
|
||||
export const shikijiPlugin = (
|
||||
options: ShikijiPluginOptions = {}): Plugin => ({
|
||||
name: '@vuepress-plume/plugin-shikiji',
|
||||
|
||||
extendsMarkdown: async (md) => {
|
||||
const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' }
|
||||
const highlighter = await highlight(theme, options)
|
||||
|
||||
md.options.highlight = highlighter
|
||||
},
|
||||
})
|
||||
56
plugins/plugin-shikiji/src/node/types.ts
Normal file
56
plugins/plugin-shikiji/src/node/types.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import type {
|
||||
BuiltinTheme,
|
||||
Highlighter,
|
||||
LanguageInput,
|
||||
ShikijiTransformer,
|
||||
ThemeRegistration,
|
||||
} from 'shikiji'
|
||||
export type ThemeOptions =
|
||||
| ThemeRegistration
|
||||
| BuiltinTheme
|
||||
| {
|
||||
light: ThemeRegistration | BuiltinTheme
|
||||
dark: ThemeRegistration | BuiltinTheme
|
||||
}
|
||||
|
||||
export interface HighlighterOptions {
|
||||
/**
|
||||
* Custom theme for syntax highlighting.
|
||||
*
|
||||
* You can also pass an object with `light` and `dark` themes to support dual themes.
|
||||
*
|
||||
* @example { theme: 'github-dark' }
|
||||
* @example { theme: { light: 'github-light', dark: 'github-dark' } }
|
||||
*
|
||||
* You can use an existing theme.
|
||||
* @see https://github.com/antfu/shikiji/blob/main/docs/themes.md#all-themes
|
||||
* Or add your own theme.
|
||||
* @see https://github.com/antfu/shikiji/blob/main/docs/themes.md#load-custom-themes
|
||||
*/
|
||||
theme?: ThemeOptions
|
||||
/**
|
||||
* Languages for syntax highlighting.
|
||||
* @see https://github.com/antfu/shikiji/blob/main/docs/languages.md#all-themes
|
||||
*/
|
||||
languages?: LanguageInput[]
|
||||
/**
|
||||
* Custom language aliases.
|
||||
*
|
||||
* @example { 'my-lang': 'js' }
|
||||
* @see https://github.com/antfu/shikiji/tree/main#custom-language-aliases
|
||||
*/
|
||||
languageAlias?: Record<string, string>
|
||||
/**
|
||||
* Setup Shikiji instance
|
||||
*/
|
||||
shikijiSetup?: (shikiji: Highlighter) => void | Promise<void>
|
||||
/**
|
||||
* Fallback language when the specified language is not available.
|
||||
*/
|
||||
defaultHighlightLang?: string
|
||||
/**
|
||||
* Transformers applied to code blocks
|
||||
* @see https://github.com/antfu/shikiji#hast-transformers
|
||||
*/
|
||||
codeTransformers?: ShikijiTransformer[]
|
||||
}
|
||||
8
plugins/plugin-shikiji/tsconfig.build.json
Normal file
8
plugins/plugin-shikiji/tsconfig.build.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib"
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user