diff options
author | 2023-09-07 22:28:02 +0800 | |
---|---|---|
committer | 2023-09-07 22:28:02 +0800 | |
commit | f3f62a5a20f4881bb04f65f192df8e1ccf7fb601 (patch) | |
tree | bcbae285c9ef23257c480861aa14622032a954d7 /packages/integrations/mdx/src | |
parent | 0fa483283e54c94f173838cd558dc0dbdd11e699 (diff) | |
download | astro-f3f62a5a20f4881bb04f65f192df8e1ccf7fb601.tar.gz astro-f3f62a5a20f4881bb04f65f192df8e1ccf7fb601.tar.zst astro-f3f62a5a20f4881bb04f65f192df8e1ccf7fb601.zip |
Refactor mdx remark plugins (#8430)
Diffstat (limited to 'packages/integrations/mdx/src')
-rw-r--r-- | packages/integrations/mdx/src/plugins.ts | 11 | ||||
-rw-r--r-- | packages/integrations/mdx/src/remark-prism.ts | 18 | ||||
-rw-r--r-- | packages/integrations/mdx/src/remark-shiki.ts | 94 |
3 files changed, 7 insertions, 116 deletions
diff --git a/packages/integrations/mdx/src/plugins.ts b/packages/integrations/mdx/src/plugins.ts index 5d7b9b58c..a3d9e4ff3 100644 --- a/packages/integrations/mdx/src/plugins.ts +++ b/packages/integrations/mdx/src/plugins.ts @@ -1,4 +1,9 @@ -import { rehypeHeadingIds, remarkCollectImages } from '@astrojs/markdown-remark'; +import { + rehypeHeadingIds, + remarkCollectImages, + remarkPrism, + remarkShiki, +} from '@astrojs/markdown-remark'; import { InvalidAstroDataError, safelyGetAstroData, @@ -16,8 +21,6 @@ import { rehypeInjectHeadingsExport } from './rehype-collect-headings.js'; import rehypeMetaString from './rehype-meta-string.js'; import { rehypeOptimizeStatic } from './rehype-optimize-static.js'; import { remarkImageToComponent } from './remark-images-to-component.js'; -import remarkPrism from './remark-prism.js'; -import remarkShiki from './remark-shiki.js'; import { jsToTreeNode } from './utils.js'; // Skip nonessential plugins during performance benchmark runs @@ -112,7 +115,7 @@ export async function getRemarkPlugins(mdxOptions: MdxOptions): Promise<Pluggabl if (!isPerformanceBenchmark) { // Apply syntax highlighters after user plugins to match `markdown/remark` behavior if (mdxOptions.syntaxHighlight === 'shiki') { - remarkPlugins.push([await remarkShiki(mdxOptions.shikiConfig)]); + remarkPlugins.push([remarkShiki, mdxOptions.shikiConfig]); } if (mdxOptions.syntaxHighlight === 'prism') { remarkPlugins.push(remarkPrism); diff --git a/packages/integrations/mdx/src/remark-prism.ts b/packages/integrations/mdx/src/remark-prism.ts deleted file mode 100644 index 7dc05f358..000000000 --- a/packages/integrations/mdx/src/remark-prism.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { runHighlighterWithAstro } from '@astrojs/prism/dist/highlighter'; -import { visit } from 'unist-util-visit'; - -/** */ -export default function remarkPrism() { - return (tree: any) => - visit(tree, 'code', (node: any) => { - let { lang, value } = node; - node.type = 'html'; - - let { html, classLanguage } = runHighlighterWithAstro(lang, value); - let classes = [classLanguage]; - node.value = `<pre class="${classes.join( - ' ' - )}"><code class="${classLanguage}">${html}</code></pre>`; - return node; - }); -} diff --git a/packages/integrations/mdx/src/remark-shiki.ts b/packages/integrations/mdx/src/remark-shiki.ts deleted file mode 100644 index a241aaa4e..000000000 --- a/packages/integrations/mdx/src/remark-shiki.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { ShikiConfig } from 'astro'; -import type * as shiki from 'shiki'; -import { getHighlighter } from 'shiki'; -import { visit } from 'unist-util-visit'; - -/** - * getHighlighter() is the most expensive step of Shiki. Instead of calling it on every page, - * cache it here as much as possible. Make sure that your highlighters can be cached, state-free. - * We make this async, so that multiple calls to parse markdown still share the same highlighter. - */ -const highlighterCacheAsync = new Map<string, Promise<shiki.Highlighter>>(); - -const remarkShiki = async ({ langs = [], theme = 'github-dark', wrap = false }: ShikiConfig) => { - const cacheID: string = typeof theme === 'string' ? theme : theme.name; - let highlighterAsync = highlighterCacheAsync.get(cacheID); - if (!highlighterAsync) { - highlighterAsync = getHighlighter({ theme }).then((hl) => { - hl.setColorReplacements({ - '#000001': 'var(--astro-code-color-text)', - '#000002': 'var(--astro-code-color-background)', - '#000004': 'var(--astro-code-token-constant)', - '#000005': 'var(--astro-code-token-string)', - '#000006': 'var(--astro-code-token-comment)', - '#000007': 'var(--astro-code-token-keyword)', - '#000008': 'var(--astro-code-token-parameter)', - '#000009': 'var(--astro-code-token-function)', - '#000010': 'var(--astro-code-token-string-expression)', - '#000011': 'var(--astro-code-token-punctuation)', - '#000012': 'var(--astro-code-token-link)', - }); - return hl; - }); - highlighterCacheAsync.set(cacheID, highlighterAsync); - } - const highlighter = await highlighterAsync; - - // NOTE: There may be a performance issue here for large sites that use `lang`. - // Since this will be called on every page load. Unclear how to fix this. - for (const lang of langs) { - await highlighter.loadLanguage(lang); - } - - return () => (tree: any) => { - visit(tree, 'code', (node) => { - let lang: string; - - if (typeof node.lang === 'string') { - const langExists = highlighter.getLoadedLanguages().includes(node.lang); - if (langExists) { - lang = node.lang; - } else { - console.warn(`The language "${node.lang}" doesn't exist, falling back to plaintext.`); - lang = 'plaintext'; - } - } else { - lang = 'plaintext'; - } - - let html = highlighter.codeToHtml(node.value, { lang }); - - // Q: Couldn't these regexes match on a user's inputted code blocks? - // A: Nope! All rendered HTML is properly escaped. - // Ex. If a user typed `<span class="line"` into a code block, - // It would become this before hitting our regexes: - // <span class="line" - - // Replace "shiki" class naming with "astro". - html = html.replace(/<pre class="(.*?)shiki(.*?)"/, `<pre class="$1astro-code$2"`); - // Add "user-select: none;" for "+"/"-" diff symbols - if (node.lang === 'diff') { - html = html.replace( - /<span class="line"><span style="(.*?)">([\+|\-])/g, - '<span class="line"><span style="$1"><span style="user-select: none;">$2</span>' - ); - } - // Handle code wrapping - // if wrap=null, do nothing. - if (wrap === false) { - html = html.replace(/style="(.*?)"/, 'style="$1; overflow-x: auto;"'); - } else if (wrap === true) { - html = html.replace( - /style="(.*?)"/, - 'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"' - ); - } - - node.type = 'html'; - node.value = html; - node.children = []; - }); - }; -}; - -export default remarkShiki; |