update logic to handle config reloads

pull/4321/head
Divyansh Singh 11 months ago
parent 3673efa7ca
commit 06aa88e5dd

@ -28,7 +28,7 @@ import type {
import type { Logger } from 'vite' import type { Logger } from 'vite'
import { containerPlugin, type ContainerOptions } from './plugins/containers' import { containerPlugin, type ContainerOptions } from './plugins/containers'
import { gitHubAlertsPlugin } from './plugins/githubAlerts' import { gitHubAlertsPlugin } from './plugins/githubAlerts'
import { highlight } from './plugins/highlight' import { highlight as createHighlighter } from './plugins/highlight'
import { highlightLinePlugin } from './plugins/highlightLines' import { highlightLinePlugin } from './plugins/highlightLines'
import { imagePlugin, type Options as ImageOptions } from './plugins/image' import { imagePlugin, type Options as ImageOptions } from './plugins/image'
import { lineNumberPlugin } from './plugins/lineNumbers' import { lineNumberPlugin } from './plugins/lineNumbers'
@ -192,39 +192,35 @@ export interface MarkdownOptions extends Options {
export type MarkdownRenderer = MarkdownIt export type MarkdownRenderer = MarkdownIt
/** let md: MarkdownRenderer | undefined
* Keep a reference to the highlighter to avoid re-creating. let _disposeHighlighter: (() => void) | undefined
*
* This highlighter is used in the `createContentLoader` function so every time
* this function is called, the highlighter will be re-created. At the end,
* Shiki will slow down the build process because it must be a singleton.
*/
let highlighter:
| ((str: string, lang: string, attrs: string) => string)
| null
| undefined
export const createMarkdownRenderer = async ( export function disposeMdItInstance() {
if (md) {
md = undefined
_disposeHighlighter?.()
}
}
export async function createMarkdownRenderer(
srcDir: string, srcDir: string,
options: MarkdownOptions = {}, options: MarkdownOptions = {},
base = '/', base = '/',
logger: Pick<Logger, 'warn'> = console logger: Pick<Logger, 'warn'> = console
): Promise<MarkdownRenderer> => { ): Promise<MarkdownRenderer> {
if (md) return md
const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' } const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' }
const codeCopyButtonTitle = options.codeCopyButtonTitle || 'Copy Code' const codeCopyButtonTitle = options.codeCopyButtonTitle || 'Copy Code'
const hasSingleTheme = typeof theme === 'string' || 'name' in theme const hasSingleTheme = typeof theme === 'string' || 'name' in theme
highlighter = let [highlight, dispose] = options.highlight
highlighter || ? [options.highlight, () => {}]
options.highlight || : await createHighlighter(theme, options, logger)
(await highlight(theme, options, logger))
_disposeHighlighter = dispose
const md = MarkdownIt({ md = MarkdownIt({ html: true, linkify: true, highlight, ...options })
html: true,
linkify: true,
highlight: highlighter,
...options
})
md.linkify.set({ fuzzyLink: false }) md.linkify.set({ fuzzyLink: false })
md.use(restoreEntities) md.use(restoreEntities)

@ -23,7 +23,7 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
* 2. convert line numbers into line options: * 2. convert line numbers into line options:
* [{ line: number, classes: string[] }] * [{ line: number, classes: string[] }]
*/ */
const attrsToLines = (attrs: string): TransformerCompactLineOption[] => { function attrsToLines(attrs: string): TransformerCompactLineOption[] {
attrs = attrs.replace(/^(?:\[.*?\])?.*?([\d,-]+).*/, '$1').trim() attrs = attrs.replace(/^(?:\[.*?\])?.*?([\d,-]+).*/, '$1').trim()
const result: number[] = [] const result: number[] = []
if (!attrs) { if (!attrs) {
@ -51,7 +51,7 @@ export async function highlight(
theme: ThemeOptions, theme: ThemeOptions,
options: MarkdownOptions, options: MarkdownOptions,
logger: Pick<Logger, 'warn'> = console logger: Pick<Logger, 'warn'> = console
): Promise<(str: string, lang: string, attrs: string) => string> { ): Promise<[(str: string, lang: string, attrs: string) => string, () => void]> {
const { const {
defaultHighlightLang: defaultLang = '', defaultHighlightLang: defaultLang = '',
codeTransformers: userTransformers = [] codeTransformers: userTransformers = []
@ -95,93 +95,96 @@ export async function highlight(
const lineNoRE = /:(no-)?line-numbers(=\d*)?$/ const lineNoRE = /:(no-)?line-numbers(=\d*)?$/
const mustacheRE = /\{\{.*?\}\}/g const mustacheRE = /\{\{.*?\}\}/g
return (str: string, lang: string, attrs: string) => { return [
const vPre = vueRE.test(lang) ? '' : 'v-pre' (str: string, lang: string, attrs: string) => {
lang = const vPre = vueRE.test(lang) ? '' : 'v-pre'
lang lang =
.replace(lineNoStartRE, '') lang
.replace(lineNoRE, '') .replace(lineNoStartRE, '')
.replace(vueRE, '') .replace(lineNoRE, '')
.toLowerCase() || defaultLang .replace(vueRE, '')
.toLowerCase() || defaultLang
if (lang) { if (lang) {
const langLoaded = highlighter.getLoadedLanguages().includes(lang as any) const langLoaded = highlighter.getLoadedLanguages().includes(lang)
if (!langLoaded && !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 '${
defaultLang || 'txt' defaultLang || 'txt'
}' for syntax highlighting.` }' for syntax highlighting.`
)
) )
) lang = defaultLang
lang = defaultLang }
} }
}
const lineOptions = attrsToLines(attrs) const lineOptions = attrsToLines(attrs)
const mustaches = new Map<string, string>() const mustaches = new Map<string, string>()
const removeMustache = (s: string) => { const removeMustache = (s: string) => {
if (vPre) return s if (vPre) return s
return s.replace(mustacheRE, (match) => { return s.replace(mustacheRE, (match) => {
let marker = mustaches.get(match) let marker = mustaches.get(match)
if (!marker) { if (!marker) {
marker = nanoid() marker = nanoid()
mustaches.set(match, marker) mustaches.set(match, marker)
} }
return marker return marker
}) })
} }
const restoreMustache = (s: string) => { const restoreMustache = (s: string) => {
mustaches.forEach((marker, match) => { mustaches.forEach((marker, match) => {
s = s.replaceAll(marker, match) s = s.replaceAll(marker, match)
}) })
return s return s
} }
str = removeMustache(str).trimEnd() str = removeMustache(str).trimEnd()
const highlighted = highlighter.codeToHtml(str, { const highlighted = highlighter.codeToHtml(str, {
lang, lang,
transformers: [ transformers: [
...transformers, ...transformers,
transformerCompactLineOptions(lineOptions), transformerCompactLineOptions(lineOptions),
{ {
name: 'vitepress:v-pre', name: 'vitepress:v-pre',
pre(node) { pre(node) {
if (vPre) node.properties['v-pre'] = '' if (vPre) node.properties['v-pre'] = ''
} }
}, },
{ {
name: 'vitepress:empty-line', name: 'vitepress:empty-line',
code(hast) { code(hast) {
hast.children.forEach((span) => { hast.children.forEach((span) => {
if ( if (
span.type === 'element' && span.type === 'element' &&
span.tagName === 'span' && span.tagName === 'span' &&
Array.isArray(span.properties.class) && Array.isArray(span.properties.class) &&
span.properties.class.includes('line') && span.properties.class.includes('line') &&
span.children.length === 0 span.children.length === 0
) { ) {
span.children.push({ span.children.push({
type: 'element', type: 'element',
tagName: 'wbr', tagName: 'wbr',
properties: {}, properties: {},
children: [] children: []
}) })
} }
}) })
} }
}, },
...userTransformers ...userTransformers
], ],
meta: { __raw: attrs }, meta: { __raw: attrs },
...(typeof theme === 'object' && 'light' in theme && 'dark' in theme ...(typeof theme === 'object' && 'light' in theme && 'dark' in theme
? { themes: theme, defaultColor: false } ? { themes: theme, defaultColor: false }
: { theme }) : { theme })
}) })
return restoreMustache(highlighted) return restoreMustache(highlighted)
} },
highlighter.dispose
]
} }

@ -16,6 +16,7 @@ import {
resolveAliases resolveAliases
} from './alias' } from './alias'
import { resolvePages, resolveUserConfig, type SiteConfig } from './config' import { resolvePages, resolveUserConfig, type SiteConfig } from './config'
import { disposeMdItInstance } from './markdown/markdown'
import { import {
clearCache, clearCache,
createMarkdownToVueRenderFn, createMarkdownToVueRenderFn,
@ -388,6 +389,7 @@ export async function createVitePressPlugin(
return return
} }
disposeMdItInstance()
clearCache() clearCache()
await recreateServer?.() await recreateServer?.()
return return

Loading…
Cancel
Save