fix: simplify lang extraction logic; use markdown-it plugins in type-safe manner; bump deps

pull/4910/merge
Divyansh Singh 3 weeks ago
parent eddf0ca257
commit 4e548f5424

@ -15,7 +15,7 @@
"open-cli": "^8.0.0",
"postcss-rtlcss": "^5.7.1",
"vitepress": "workspace:*",
"vitepress-plugin-group-icons": "^1.6.3",
"vitepress-plugin-llms": "^1.7.3"
"vitepress-plugin-group-icons": "^1.6.5",
"vitepress-plugin-llms": "^1.9.1"
}
}

@ -95,28 +95,28 @@
"*": "prettier --experimental-cli --ignore-unknown --write"
},
"dependencies": {
"@docsearch/css": "^4.0.0-beta.8",
"@docsearch/js": "^4.0.0-beta.8",
"@iconify-json/simple-icons": "^1.2.49",
"@shikijs/core": "^3.12.0",
"@shikijs/transformers": "^3.12.0",
"@shikijs/types": "^3.12.0",
"@docsearch/css": "^4.3.2",
"@docsearch/js": "^4.3.2",
"@iconify-json/simple-icons": "^1.2.58",
"@shikijs/core": "^3.15.0",
"@shikijs/transformers": "^3.15.0",
"@shikijs/types": "^3.15.0",
"@types/markdown-it": "^14.1.2",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/devtools-api": "^8.0.1",
"@vue/shared": "^3.5.20",
"@vueuse/core": "^13.8.0",
"@vueuse/integrations": "^13.8.0",
"focus-trap": "^7.6.5",
"@vue/devtools-api": "^8.0.3",
"@vue/shared": "^3.5.24",
"@vueuse/core": "^14.0.0",
"@vueuse/integrations": "^14.0.0",
"focus-trap": "^7.6.6",
"mark.js": "8.11.1",
"minisearch": "^7.1.2",
"shiki": "^3.12.0",
"vite": "^7.1.3",
"vue": "^3.5.20"
"minisearch": "^7.2.0",
"shiki": "^3.15.0",
"vite": "^7.2.2",
"vue": "^3.5.24"
},
"devDependencies": {
"@clack/prompts": "^1.0.0-alpha.4",
"@iconify/utils": "^3.0.1",
"@clack/prompts": "^1.0.0-alpha.6",
"@iconify/utils": "^3.0.2",
"@mdit-vue/plugin-component": "^3.0.2",
"@mdit-vue/plugin-frontmatter": "^3.0.2",
"@mdit-vue/plugin-headers": "^3.0.2",
@ -125,11 +125,11 @@
"@mdit-vue/plugin-toc": "^3.0.2",
"@mdit-vue/shared": "^3.0.2",
"@polka/compression": "^1.0.0-next.28",
"@rollup/plugin-alias": "^5.1.1",
"@rollup/plugin-commonjs": "^28.0.6",
"@rollup/plugin-alias": "^6.0.0",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-replace": "^6.0.2",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-replace": "^6.0.3",
"@types/cross-spawn": "^6.0.6",
"@types/debug": "^4.1.12",
"@types/fs-extra": "^11.0.4",
@ -139,62 +139,62 @@
"@types/markdown-it-container": "^2.0.10",
"@types/markdown-it-emoji": "^3.0.1",
"@types/minimist": "^1.2.5",
"@types/node": "^24.3.0",
"@types/node": "^24.10.1",
"@types/picomatch": "^4.0.2",
"@types/prompts": "^2.4.9",
"chokidar": "^4.0.3",
"conventional-changelog-cli": "^5.0.0",
"cross-spawn": "^7.0.6",
"debug": "^4.4.1",
"esbuild": "^0.25.9",
"debug": "^4.4.3",
"esbuild": "^0.25.0",
"execa": "^9.6.0",
"fs-extra": "^11.3.1",
"fs-extra": "^11.3.2",
"get-port": "^7.1.0",
"gray-matter": "^4.0.3",
"lint-staged": "^16.1.5",
"lint-staged": "^16.2.6",
"lodash.template": "^4.5.0",
"lru-cache": "^11.1.0",
"lru-cache": "^11.2.2",
"markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.2.0",
"markdown-it-async": "^2.2.0",
"markdown-it-attrs": "^4.3.1",
"markdown-it-cjk-friendly": "^1.2.0",
"markdown-it-cjk-friendly": "^1.3.2",
"markdown-it-container": "^4.0.0",
"markdown-it-emoji": "^3.0.0",
"markdown-it-mathjax3": "^4.3.2",
"minimist": "^1.2.8",
"nanoid": "^5.1.5",
"ora": "^8.2.0",
"oxc-minify": "^0.82.3",
"p-map": "^7.0.3",
"nanoid": "^5.1.6",
"ora": "^9.0.0",
"oxc-minify": "^0.97.0",
"p-map": "^7.0.4",
"package-directory": "^8.1.0",
"path-to-regexp": "^6.3.0",
"picocolors": "^1.1.1",
"picomatch": "^4.0.3",
"playwright-chromium": "^1.55.0",
"playwright-chromium": "^1.56.1",
"polka": "^1.0.0-next.28",
"postcss": "^8.5.6",
"postcss-selector-parser": "^7.1.0",
"prettier": "^3.6.2",
"prompts": "^2.4.2",
"punycode": "^2.3.1",
"rimraf": "^6.0.1",
"rollup": "^4.49.0",
"rimraf": "^6.1.0",
"rollup": "^4.53.2",
"rollup-plugin-dts": "6.1.1",
"rollup-plugin-esbuild": "^6.2.1",
"semver": "^7.7.2",
"semver": "^7.7.3",
"simple-git-hooks": "^2.13.1",
"sirv": "^3.0.1",
"sitemap": "^8.0.0",
"tinyglobby": "^0.2.14",
"typescript": "^5.9.2",
"sirv": "^3.0.2",
"sitemap": "^9.0.0",
"tinyglobby": "^0.2.15",
"typescript": "^5.9.3",
"vitest": "4.0.0-beta.4",
"vue-tsc": "^3.0.6",
"wait-on": "^8.0.4"
"vue-tsc": "^3.1.3",
"wait-on": "^9.0.3"
},
"peerDependencies": {
"markdown-it-mathjax3": "^4",
"oxc-minify": "^0.82.3",
"oxc-minify": "*",
"postcss": "^8"
},
"peerDependenciesMeta": {
@ -208,5 +208,5 @@
"optional": true
}
},
"packageManager": "pnpm@10.14.0"
"packageManager": "pnpm@10.22.0"
}

@ -0,0 +1,23 @@
diff --git a/index.d.ts b/index.d.ts
index 41d4a858c6ece5a61a2088733cf8b333b45603d8..2cb19bcd8fc76ae82ebe87af742916e17f49334b 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,3 +1,15 @@
-import MarkdownIt = require("markdown-it");
-declare function attrs(md: MarkdownIt): void;
-export = attrs;
+import type MarkdownIt from 'markdown-it'
+
+export interface MarkdownItAttrsOptions {
+ /** left delimiter, default is `{`(left curly bracket) */
+ leftDelimiter?: string
+ /** right delimiter, default is `}`(right curly bracket) */
+ rightDelimiter?: string
+ /** rule of allowed attribute, empty means no limit */
+ allowedAttributes?: (string | RegExp)[]
+}
+
+export default function attrsPlugin(
+ md: MarkdownIt,
+ options?: MarkdownItAttrsOptions
+): void

File diff suppressed because it is too large Load Diff

@ -2,6 +2,8 @@ packages:
- docs
- __tests__/*
autoInstallPeers: false
onlyBuiltDependencies:
- esbuild
- playwright-chromium
@ -12,8 +14,8 @@ overrides:
vite: npm:rolldown-vite@latest
patchedDependencies:
'@types/markdown-it-attrs': patches/@types__markdown-it-attrs.patch
'@types/mdurl@2.0.0': patches/@types__mdurl@2.0.0.patch
markdown-it-anchor@9.2.0: patches/markdown-it-anchor@9.2.0.patch
autoInstallPeers: false
shellEmulator: true

@ -20,8 +20,8 @@ import type {
ThemeRegistrationAny
} from '@shikijs/types'
import anchorPlugin from 'markdown-it-anchor'
import { MarkdownItAsync, type Options } from 'markdown-it-async'
import attrsPlugin from 'markdown-it-attrs'
import { MarkdownItAsync, type MarkdownItAsyncOptions } from 'markdown-it-async'
import attrsPlugin, { type MarkdownItAttrsOptions } from 'markdown-it-attrs'
import mditCjkFriendly from 'markdown-it-cjk-friendly'
import { full as emojiPlugin } from 'markdown-it-emoji'
import type { BuiltinLanguage, BuiltinTheme, Highlighter } from 'shiki'
@ -48,7 +48,7 @@ export type ThemeOptions =
dark: ThemeRegistrationAny | BuiltinTheme
}
export interface MarkdownOptions extends Options {
export interface MarkdownOptions extends MarkdownItAsyncOptions {
/* ==================== General Options ==================== */
/**
@ -153,12 +153,7 @@ export interface MarkdownOptions extends Options {
* Options for `markdown-it-attrs`
* @see https://github.com/arve0/markdown-it-attrs
*/
attrs?: {
leftDelimiter?: string
rightDelimiter?: string
allowedAttributes?: Array<string | RegExp>
disable?: boolean
}
attrs?: MarkdownItAttrsOptions & { disable?: boolean }
/**
* Options for `markdown-it-emoji`
* @see https://github.com/markdown-it/markdown-it-emoji
@ -230,7 +225,10 @@ export interface MarkdownOptions extends Options {
export type MarkdownRenderer = MarkdownItAsync
let md: MarkdownRenderer | undefined
// highlight is marked as any to avoid type conflicts with plugins expecting
// regular markdown-it which has sync highlight function. Such plugins will fail
// if they access highlight directly but currently none of the ones we use do that.
let md: (MarkdownRenderer & { options: { highlight?: any } }) | undefined
let _disposeHighlighter: (() => void) | undefined
export function disposeMdItInstance() {
@ -263,7 +261,7 @@ export async function createMarkdownRenderer(
md = new MarkdownItAsync({ html: true, linkify: true, highlight, ...options })
md.linkify.set({ fuzzyLink: false })
md.use(restoreEntities)
restoreEntities(md)
if (options.preConfig) {
await options.preConfig(md)
@ -272,22 +270,22 @@ export async function createMarkdownRenderer(
const slugify = options.anchor?.slugify ?? defaultSlugify
// custom plugins
md.use(componentPlugin, { ...options.component })
.use(highlightLinePlugin)
.use(preWrapperPlugin, {
codeCopyButtonTitle,
languageLabel: options.languageLabel
})
.use(snippetPlugin, srcDir)
.use(containerPlugin, options.container)
.use(imagePlugin, options.image)
.use(
linkPlugin,
{ target: '_blank', rel: 'noreferrer', ...options.externalLinks },
base,
slugify
)
.use(lineNumberPlugin, options.lineNumbers)
componentPlugin(md, options.component)
highlightLinePlugin(md)
preWrapperPlugin(md, {
codeCopyButtonTitle,
languageLabel: options.languageLabel
})
snippetPlugin(md, srcDir)
containerPlugin(md, options.container)
imagePlugin(md, options.image)
linkPlugin(
md,
{ target: '_blank', rel: 'noreferrer', ...options.externalLinks },
base,
slugify
)
lineNumberPlugin(md, options.lineNumbers)
const tableOpen = md.renderer.rules.table_open
md.renderer.rules.table_open = function (tokens, idx, options, env, self) {
@ -299,17 +297,17 @@ export async function createMarkdownRenderer(
}
if (options.gfmAlerts !== false) {
md.use(gitHubAlertsPlugin, options.container)
gitHubAlertsPlugin(md, options.container)
}
// third party plugins
if (!options.attrs?.disable) {
md.use(attrsPlugin, options.attrs)
attrsPlugin(md, options.attrs)
}
md.use(emojiPlugin, { ...options.emoji })
emojiPlugin(md, options.emoji)
// mdit-vue plugins
md.use(anchorPlugin, {
anchorPlugin(md, {
slugify,
getTokensText: (tokens) => {
return tokens
@ -343,35 +341,33 @@ export async function createMarkdownRenderer(
state.tokens[idx + 1].children?.push(...linkTokens)
},
...options.anchor
} as anchorPlugin.AnchorOptions).use(frontmatterPlugin, {
...options.frontmatter
} as FrontmatterPluginOptions)
})
frontmatterPlugin(md, options.frontmatter)
if (options.headers) {
md.use(headersPlugin, {
headersPlugin(md, {
level: [2, 3, 4, 5, 6],
slugify,
...(typeof options.headers === 'boolean' ? undefined : options.headers)
} as HeadersPluginOptions)
})
}
md.use(sfcPlugin, {
...options.sfc
} as SfcPluginOptions)
.use(titlePlugin)
.use(tocPlugin, {
slugify,
...options.toc,
format: (s) => {
const title = s.replaceAll('&amp;', '&') // encoded twice because of restoreEntities
return options.toc?.format?.(title) ?? title
}
} as TocPluginOptions)
sfcPlugin(md, options.sfc)
titlePlugin(md)
tocPlugin(md, {
slugify,
...options.toc,
format: (s) => {
const title = s.replaceAll('&amp;', '&') // encoded twice because of restoreEntities
return options.toc?.format?.(title) ?? title
}
})
if (options.math) {
try {
const mathPlugin = await import('markdown-it-mathjax3')
md.use(mathPlugin.default ?? mathPlugin, {
;(mathPlugin.default ?? mathPlugin)(md, {
...(typeof options.math === 'boolean' ? {} : options.math)
})
const origMathInline = md.renderer.rules.math_inline!
@ -388,13 +384,13 @@ export async function createMarkdownRenderer(
}
} catch (error) {
throw new Error(
'You need to install `markdown-it-mathjax3` to use math support.'
'You need to install `markdown-it-mathjax3@^4` to use math support.'
)
}
}
if (options.cjkFriendlyEmphasis !== false && options.cjkFriendly !== false) {
md.use(mditCjkFriendly)
mditCjkFriendly(md)
}
// apply user config

@ -127,20 +127,16 @@ export async function highlight(
}
]
const vueRE = /-vue(?=:|$)/
const lineNoStartRE = /=(\d*)/
const lineNoRE = /:(no-)?line-numbers(=\d*)?$/
const mustacheRE = /\{\{.*?\}\}/g
// keep in sync with ./preWrapper.ts#extractLang
const langRE = /^[a-zA-Z0-9-_]+/
const vueRE = /-vue$/
return [
async (str: string, lang: string, attrs: string) => {
const vPre = vueRE.test(lang) ? '' : 'v-pre'
lang =
lang
.replace(lineNoStartRE, '')
.replace(lineNoRE, '')
.replace(vueRE, '')
.toLowerCase() || defaultLang
lang = langRE.exec(lang)?.[0].toLowerCase() || defaultLang
const vPre = !vueRE.test(lang)
if (!vPre) lang = lang.slice(0, -4)
try {
// https://github.com/shikijs/shiki/issues/952
@ -164,7 +160,7 @@ export async function highlight(
const removeMustache = (s: string) => {
if (vPre) return s
return s.replace(mustacheRE, (match) => {
return s.replace(/\{\{.*?\}\}/g, (match) => {
let marker = mustaches.get(match)
if (!marker) {
marker = nanoid()

@ -12,14 +12,14 @@ export const lineNumberPlugin = (md: MarkdownItAsync, enable = false) => {
const info = tokens[idx].info
if (
(!enable && !/:line-numbers($| |=)/.test(info)) ||
(enable && /:no-line-numbers($| )/.test(info))
(!enable && !/:line-numbers\b/.test(info)) ||
(enable && /:no-line-numbers\b/.test(info))
) {
return rawCode
}
let startLineNumber = 1
const matchStartLineNumber = info.match(/=(\d*)/)
const matchStartLineNumber = info.match(/=(\d+)/)
if (matchStartLineNumber && matchStartLineNumber[1]) {
startLineNumber = parseInt(matchStartLineNumber[1])
}

@ -44,12 +44,12 @@ export function extractTitle(info: string, html = false) {
return info.match(/\[(.*)\]/)?.[1] || extractLang(info) || 'txt'
}
function extractLang(info: string) {
return info
.trim()
.replace(/=(\d*)/, '')
.replace(/:(no-)?line-numbers({| |$|=\d*).*/, '')
.replace(/(-vue|{| ).*$/, '')
.replace(/^vue-html$/, 'template')
.replace(/^ansi$/, '')
function extractLang(info: string): string {
return (
/^[a-zA-Z0-9-_]+/
.exec(info)?.[0]
.replace(/-vue$/, '') // remove -vue suffix
.replace(/^vue-html$/, 'template')
.replace(/^ansi$/, '') || ''
)
}

Loading…
Cancel
Save