11import Markdoc from '@markdoc/markdoc' ;
2+ import { createShikiHighlighter } from '@astrojs/markdown-remark' ;
23import type { ShikiConfig } from 'astro' ;
34import { unescapeHTML } from 'astro/runtime/server/index.js' ;
4- import { bundledLanguages , getHighlighter , type Highlighter } from 'shikiji' ;
55import type { AstroMarkdocConfig } from '../config.js' ;
66
7- const ASTRO_COLOR_REPLACEMENTS : Record < string , string > = {
8- '#000001' : 'var(--astro-code-color-text)' ,
9- '#000002' : 'var(--astro-code-color-background)' ,
10- '#000004' : 'var(--astro-code-token-constant)' ,
11- '#000005' : 'var(--astro-code-token-string)' ,
12- '#000006' : 'var(--astro-code-token-comment)' ,
13- '#000007' : 'var(--astro-code-token-keyword)' ,
14- '#000008' : 'var(--astro-code-token-parameter)' ,
15- '#000009' : 'var(--astro-code-token-function)' ,
16- '#000010' : 'var(--astro-code-token-string-expression)' ,
17- '#000011' : 'var(--astro-code-token-punctuation)' ,
18- '#000012' : 'var(--astro-code-token-link)' ,
19- } ;
20- const COLOR_REPLACEMENT_REGEX = new RegExp (
21- `(${ Object . keys ( ASTRO_COLOR_REPLACEMENTS ) . join ( '|' ) } )` ,
22- 'g'
23- ) ;
24-
25- const PRE_SELECTOR = / < p r e c l a s s = " ( .* ?) s h i k i ( .* ?) " / ;
26- const LINE_SELECTOR = / < s p a n c l a s s = " l i n e " > < s p a n s t y l e = " ( .* ?) " > ( [ \+ | \- ] ) / g;
27- const INLINE_STYLE_SELECTOR = / s t y l e = " ( .* ?) " / ;
28- const INLINE_STYLE_SELECTOR_GLOBAL = / s t y l e = " ( .* ?) " / g;
29-
30- /**
31- * Note: cache only needed for dev server reloads, internal test suites, and manual calls to `Markdoc.transform` by the user.
32- * Otherwise, `shiki()` is only called once per build, NOT once per page, so a cache isn't needed!
33- */
34- const highlighterCache = new Map < string , Highlighter > ( ) ;
35-
36- export default async function shiki ( {
37- langs = [ ] ,
38- theme = 'github-dark' ,
39- wrap = false ,
40- } : ShikiConfig = { } ) : Promise < AstroMarkdocConfig > {
41- const cacheId = typeof theme === 'string' ? theme : theme . name || '' ;
42- let highlighter = highlighterCache . get ( cacheId ) ! ;
43- if ( ! highlighter ) {
44- highlighter = await getHighlighter ( {
45- langs : langs . length ? langs : Object . keys ( bundledLanguages ) ,
46- themes : [ theme ] ,
47- } ) ;
48- highlighterCache . set ( cacheId , highlighter ) ;
49- }
7+ export default async function shiki ( config ?: ShikiConfig ) : Promise < AstroMarkdocConfig > {
8+ const highlighter = await createShikiHighlighter ( config ) ;
509
5110 return {
5211 nodes : {
5312 fence : {
5413 attributes : Markdoc . nodes . fence . attributes ! ,
5514 transform ( { attributes } ) {
56- let lang : string ;
57-
58- if ( typeof attributes . language === 'string' ) {
59- const langExists = highlighter
60- . getLoadedLanguages ( )
61- . includes ( attributes . language as any ) ;
62- if ( langExists ) {
63- lang = attributes . language ;
64- } else {
65- console . warn (
66- `[Shiki highlighter] The language "${ attributes . language } " doesn't exist, falling back to plaintext.`
67- ) ;
68- lang = 'plaintext' ;
69- }
70- } else {
71- lang = 'plaintext' ;
72- }
73-
74- let html = highlighter . codeToHtml ( attributes . content , { lang, theme } ) ;
75-
76- // Q: Could these regexes match on a user's inputted code blocks?
77- // A: Nope! All rendered HTML is properly escaped.
78- // Ex. If a user typed `<span class="line"` into a code block,
79- // It would become this before hitting our regexes:
80- // <span class="line"
81-
82- html = html . replace ( PRE_SELECTOR , `<pre class="$1astro-code$2"` ) ;
83- // Add "user-select: none;" for "+"/"-" diff symbols
84- if ( attributes . language === 'diff' ) {
85- html = html . replace (
86- LINE_SELECTOR ,
87- '<span class="line"><span style="$1"><span style="user-select: none;">$2</span>'
88- ) ;
89- }
90-
91- if ( wrap === false ) {
92- html = html . replace ( INLINE_STYLE_SELECTOR , 'style="$1; overflow-x: auto;"' ) ;
93- } else if ( wrap === true ) {
94- html = html . replace (
95- INLINE_STYLE_SELECTOR ,
96- 'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"'
97- ) ;
98- }
99-
100- // theme.id for shiki -> shikiji compat
101- const themeName = typeof theme === 'string' ? theme : theme . name ;
102- if ( themeName === 'css-variables' ) {
103- html = html . replace ( INLINE_STYLE_SELECTOR_GLOBAL , ( m ) => replaceCssVariables ( m ) ) ;
104- }
15+ const lang = typeof attributes . language === 'string' ? attributes . language : 'plaintext' ;
16+ const html = highlighter . highlight ( attributes . content , lang ) ;
10517
10618 // Use `unescapeHTML` to return `HTMLString` for Astro renderer to inline as HTML
10719 return unescapeHTML ( html ) as any ;
@@ -110,10 +22,3 @@ export default async function shiki({
11022 } ,
11123 } ;
11224}
113-
114- /**
115- * shiki -> shikiji compat as we need to manually replace it
116- */
117- function replaceCssVariables ( str : string ) {
118- return str . replace ( COLOR_REPLACEMENT_REGEX , ( match ) => ASTRO_COLOR_REPLACEMENTS [ match ] || match ) ;
119- }
0 commit comments