@@ -14,6 +14,7 @@ import { useToast } from '~/components/ToastProvider'
1414import { twMerge } from 'tailwind-merge'
1515import { useMarkdownHeadings } from '~/components/MarkdownHeadingContext'
1616import { renderMarkdown } from '~/utils/markdown'
17+ import { getNetlifyImageUrl } from '~/utils/netlifyImage'
1718import { Tabs } from '~/components/Tabs'
1819import { Copy } from 'lucide-react'
1920
@@ -82,7 +83,7 @@ const markdownComponents: Record<string, React.FC> = {
8283 return (
8384 < span
8485 className = { `border border-gray-500/20 bg-gray-500/10 rounded px-1 py-0.5${
85- className ?? ` ${ className } `
86+ className ? ` ${ className } ` : ''
8687 } `}
8788 { ...rest }
8889 />
@@ -91,30 +92,16 @@ const markdownComponents: Record<string, React.FC> = {
9192 iframe : ( props ) => (
9293 < iframe { ...props } className = "w-full" title = "Embedded Content" />
9394 ) ,
94- // eslint-disable-next-line @typescript-eslint/no-unused-vars
95- img : ( { children, alt, src, ...props } : HTMLProps < HTMLImageElement > ) => {
96- // Use Netlify Image CDN for local images
97- const optimizedSrc =
98- src &&
99- ! src . startsWith ( 'http' ) &&
100- ! src . startsWith ( 'data:' ) &&
101- ! src . endsWith ( '.svg' )
102- ? `/.netlify/images?url=${ encodeURIComponent ( src ) } &w=800&q=80`
103- : src
104-
105- return (
106- < img
107- { ...props }
108- src = { optimizedSrc }
109- alt = { alt ?? '' }
110- className = { `max-w-full h-auto rounded-lg shadow-md ${
111- props . className ?? ''
112- } `}
113- loading = "lazy"
114- decoding = "async"
115- />
116- )
117- } ,
95+ img : ( { alt, src, className, children : _ , ...props } : HTMLProps < HTMLImageElement > ) => (
96+ < img
97+ { ...props }
98+ src = { src ? getNetlifyImageUrl ( src ) : undefined }
99+ alt = { alt ?? '' }
100+ className = { `max-w-full h-auto rounded-lg shadow-md ${ className ?? '' } ` }
101+ loading = "lazy"
102+ decoding = "async"
103+ />
104+ ) ,
118105}
119106
120107export function extractPreAttributes ( html : string ) : {
@@ -194,13 +181,10 @@ export function CodeBlock({
194181 ] ( ( ) => {
195182 ; ( async ( ) => {
196183 const themes = [ 'github-light' , 'tokyo-night' ]
197-
198- // Normalize language name
199184 const normalizedLang = LANG_ALIASES [ lang ] || lang
200- const effectiveLang =
201- normalizedLang === 'mermaid' ? 'plaintext' : normalizedLang
185+ const effectiveLang = normalizedLang === 'mermaid' ? 'plaintext' : normalizedLang
202186
203- const highlighter = await getHighlighter ( lang , themes )
187+ const highlighter = await getHighlighter ( lang )
204188
205189 const htmls = await Promise . all (
206190 themes . map ( async ( theme ) => {
@@ -292,33 +276,6 @@ export function CodeBlock({
292276 )
293277}
294278
295- const cache = < T extends ( ...args : any [ ] ) => any > ( fn : T ) => {
296- const cache = new Map < string , any > ( )
297- return async ( ...args : Parameters < T > ) => {
298- const key = JSON . stringify ( args )
299- if ( cache . has ( key ) ) {
300- return cache . get ( key )
301- }
302- const value = await fn ( ...args )
303- cache . set ( key , value )
304- return value
305- }
306- }
307-
308- // Core languages to bundle (most commonly used in TanStack docs)
309- const CORE_LANGS = [
310- 'typescript' ,
311- 'javascript' ,
312- 'tsx' ,
313- 'jsx' ,
314- 'bash' ,
315- 'json' ,
316- 'html' ,
317- 'css' ,
318- 'markdown' ,
319- 'plaintext' ,
320- ] as const
321-
322279// Language aliases mapping
323280const LANG_ALIASES : Record < string , string > = {
324281 ts : 'typescript' ,
@@ -332,41 +289,32 @@ const LANG_ALIASES: Record<string, string> = {
332289 text : 'plaintext' ,
333290}
334291
335- // Lazy highlighter initialization
292+ // Lazy highlighter singleton
336293let highlighterPromise : Promise < HighlighterGeneric < any , any > > | null = null
337294
338- async function getShikiHighlighter ( ) {
295+ async function getHighlighter ( language : string ) {
339296 if ( ! highlighterPromise ) {
340297 highlighterPromise = createHighlighter ( {
341298 themes : [ 'github-light' , 'tokyo-night' ] ,
342- langs : CORE_LANGS as unknown as string [ ] ,
299+ langs : [ 'typescript' , 'javascript' , 'tsx' , 'jsx' , 'bash' , 'json' , 'html' , 'css' , 'markdown' , 'plaintext' ] ,
343300 } )
344301 }
345- return highlighterPromise
346- }
347-
348- const getHighlighter = cache ( async ( language : string , _themes : string [ ] ) => {
349- const highlighter = await getShikiHighlighter ( )
350302
351- // Normalize language name
303+ const highlighter = await highlighterPromise
352304 const normalizedLang = LANG_ALIASES [ language ] || language
353305 const langToLoad = normalizedLang === 'mermaid' ? 'plaintext' : normalizedLang
354306
355- const loadedLanguages = highlighter . getLoadedLanguages ( )
356-
357- // Only load language if not already loaded
358- if ( ! loadedLanguages . includes ( langToLoad as any ) ) {
307+ // Load language if not already loaded
308+ if ( ! highlighter . getLoadedLanguages ( ) . includes ( langToLoad as any ) ) {
359309 try {
360- // Load language using shiki's built-in loader (works with bundle/web)
361310 await highlighter . loadLanguage ( langToLoad as any )
362311 } catch {
363- // Fallback to plaintext if language not found
364312 console . warn ( `Shiki: Language "${ langToLoad } " not found, using plaintext` )
365313 }
366314 }
367315
368316 return highlighter
369- } )
317+ }
370318
371319const options : HTMLReactParserOptions = {
372320 replace : ( domNode ) => {
0 commit comments