Skip to content

Commit

Permalink
update logic to handle config reloads
Browse files Browse the repository at this point in the history
  • Loading branch information
brc-dd committed Oct 29, 2024
1 parent 3673efa commit 06aa88e
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 106 deletions.
44 changes: 20 additions & 24 deletions src/node/markdown/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import type {
import type { Logger } from 'vite'
import { containerPlugin, type ContainerOptions } from './plugins/containers'
import { gitHubAlertsPlugin } from './plugins/githubAlerts'
import { highlight } from './plugins/highlight'
import { highlight as createHighlighter } from './plugins/highlight'
import { highlightLinePlugin } from './plugins/highlightLines'
import { imagePlugin, type Options as ImageOptions } from './plugins/image'
import { lineNumberPlugin } from './plugins/lineNumbers'
Expand Down Expand Up @@ -192,39 +192,35 @@ export interface MarkdownOptions extends Options {

export type MarkdownRenderer = MarkdownIt

/**
* Keep a reference to the highlighter to avoid re-creating.
*
* 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
let md: MarkdownRenderer | undefined
let _disposeHighlighter: (() => void) | undefined

export const createMarkdownRenderer = async (
export function disposeMdItInstance() {
if (md) {
md = undefined
_disposeHighlighter?.()
}
}

export async function createMarkdownRenderer(
srcDir: string,
options: MarkdownOptions = {},
base = '/',
logger: Pick<Logger, 'warn'> = console
): Promise<MarkdownRenderer> => {
): Promise<MarkdownRenderer> {
if (md) return md

const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' }
const codeCopyButtonTitle = options.codeCopyButtonTitle || 'Copy Code'
const hasSingleTheme = typeof theme === 'string' || 'name' in theme

highlighter =
highlighter ||
options.highlight ||
(await highlight(theme, options, logger))
let [highlight, dispose] = options.highlight
? [options.highlight, () => {}]
: await createHighlighter(theme, options, logger)

_disposeHighlighter = dispose

const md = MarkdownIt({
html: true,
linkify: true,
highlight: highlighter,
...options
})
md = MarkdownIt({ html: true, linkify: true, highlight, ...options })

md.linkify.set({ fuzzyLink: false })
md.use(restoreEntities)
Expand Down
167 changes: 85 additions & 82 deletions src/node/markdown/plugins/highlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
* 2. convert line numbers into line options:
* [{ line: number, classes: string[] }]
*/
const attrsToLines = (attrs: string): TransformerCompactLineOption[] => {
function attrsToLines(attrs: string): TransformerCompactLineOption[] {
attrs = attrs.replace(/^(?:\[.*?\])?.*?([\d,-]+).*/, '$1').trim()
const result: number[] = []
if (!attrs) {
Expand Down Expand Up @@ -51,7 +51,7 @@ export async function highlight(
theme: ThemeOptions,
options: MarkdownOptions,
logger: Pick<Logger, 'warn'> = console
): Promise<(str: string, lang: string, attrs: string) => string> {
): Promise<[(str: string, lang: string, attrs: string) => string, () => void]> {
const {
defaultHighlightLang: defaultLang = '',
codeTransformers: userTransformers = []
Expand Down Expand Up @@ -95,93 +95,96 @@ export async function highlight(
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
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 && !isSpecialLang(lang)) {
logger.warn(
c.yellow(
`\nThe language '${lang}' is not loaded, falling back to '${
defaultLang || 'txt'
}' for syntax highlighting.`
if (lang) {
const langLoaded = highlighter.getLoadedLanguages().includes(lang)
if (!langLoaded && !isSpecialLang(lang)) {
logger.warn(
c.yellow(
`\nThe language '${lang}' is not loaded, falling back to '${
defaultLang || 'txt'
}' for syntax highlighting.`
)
)
)
lang = defaultLang
lang = defaultLang
}
}
}

const lineOptions = attrsToLines(attrs)
const mustaches = new Map<string, string>()
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 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 restoreMustache = (s: string) => {
mustaches.forEach((marker, match) => {
s = s.replaceAll(marker, match)
})
return s
}

str = removeMustache(str).trimEnd()
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'] = ''
}
},
{
name: 'vitepress: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
],
meta: { __raw: attrs },
...(typeof theme === 'object' && 'light' in theme && 'dark' in theme
? { themes: theme, defaultColor: false }
: { theme })
})
const highlighted = highlighter.codeToHtml(str, {
lang,
transformers: [
...transformers,
transformerCompactLineOptions(lineOptions),
{
name: 'vitepress:v-pre',
pre(node) {
if (vPre) node.properties['v-pre'] = ''
}
},
{
name: 'vitepress: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
],
meta: { __raw: attrs },
...(typeof theme === 'object' && 'light' in theme && 'dark' in theme
? { themes: theme, defaultColor: false }
: { theme })
})

return restoreMustache(highlighted)
}
return restoreMustache(highlighted)
},
highlighter.dispose
]
}
2 changes: 2 additions & 0 deletions src/node/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
resolveAliases
} from './alias'
import { resolvePages, resolveUserConfig, type SiteConfig } from './config'
import { disposeMdItInstance } from './markdown/markdown'
import {
clearCache,
createMarkdownToVueRenderFn,
Expand Down Expand Up @@ -388,6 +389,7 @@ export async function createVitePressPlugin(
return
}

disposeMdItInstance()
clearCache()
await recreateServer?.()
return
Expand Down

0 comments on commit 06aa88e

Please sign in to comment.