@@ -542,8 +542,53 @@ const FileInfo = memo(
542542 } ,
543543) ;
544544
545+ // Create and manage a single highlighter instance at the module level
546+ let highlighterInstance : any = null ;
547+ let highlighterPromise : Promise < any > | null = null ;
548+
549+ const getSharedHighlighter = async ( ) => {
550+ if ( highlighterInstance ) {
551+ return highlighterInstance ;
552+ }
553+
554+ if ( highlighterPromise ) {
555+ return highlighterPromise ;
556+ }
557+
558+ highlighterPromise = getHighlighter ( {
559+ themes : [ 'github-dark' , 'github-light' ] ,
560+ langs : [
561+ 'typescript' ,
562+ 'javascript' ,
563+ 'json' ,
564+ 'html' ,
565+ 'css' ,
566+ 'jsx' ,
567+ 'tsx' ,
568+ 'python' ,
569+ 'php' ,
570+ 'java' ,
571+ 'c' ,
572+ 'cpp' ,
573+ 'csharp' ,
574+ 'go' ,
575+ 'ruby' ,
576+ 'rust' ,
577+ 'plaintext' ,
578+ ] ,
579+ } ) ;
580+
581+ highlighterInstance = await highlighterPromise ;
582+ highlighterPromise = null ;
583+
584+ // Clear the promise once resolved
585+ return highlighterInstance ;
586+ } ;
587+
545588const InlineDiffComparison = memo ( ( { beforeCode, afterCode, filename, language } : CodeComparisonProps ) => {
546589 const [ isFullscreen , setIsFullscreen ] = useState ( false ) ;
590+
591+ // Use state to hold the shared highlighter instance
547592 const [ highlighter , setHighlighter ] = useState < any > ( null ) ;
548593 const theme = useStore ( themeStore ) ;
549594
@@ -554,34 +599,32 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }
554599 const { unifiedBlocks, hasChanges, isBinary, error } = useProcessChanges ( beforeCode , afterCode ) ;
555600
556601 useEffect ( ( ) => {
557- getHighlighter ( {
558- themes : [ 'github-dark' , 'github-light' ] ,
559- langs : [
560- 'typescript' ,
561- 'javascript' ,
562- 'json' ,
563- 'html' ,
564- 'css' ,
565- 'jsx' ,
566- 'tsx' ,
567- 'python' ,
568- 'php' ,
569- 'java' ,
570- 'c' ,
571- 'cpp' ,
572- 'csharp' ,
573- 'go' ,
574- 'ruby' ,
575- 'rust' ,
576- 'plaintext' ,
577- ] ,
578- } ) . then ( setHighlighter ) ;
579- } , [ ] ) ;
602+ // Fetch the shared highlighter instance
603+ getSharedHighlighter ( ) . then ( setHighlighter ) ;
604+
605+ /*
606+ * No cleanup needed here for the highlighter instance itself,
607+ * as it's managed globally. Shiki instances don't typically
608+ * need disposal unless you are dynamically loading/unloading themes/languages.
609+ * If you were dynamically loading, you might need a more complex
610+ * shared instance manager with reference counting or similar.
611+ * For static themes/langs, a single instance is sufficient.
612+ */
613+ } , [ ] ) ; // Empty dependency array ensures this runs only once on mount
580614
581615 if ( isBinary || error ) {
582616 return renderContentWarning ( isBinary ? 'binary' : 'error' ) ;
583617 }
584618
619+ // Render a loading state or null while highlighter is not ready
620+ if ( ! highlighter ) {
621+ return (
622+ < div className = "h-full flex items-center justify-center" >
623+ < div className = "text-bolt-elements-textTertiary" > Loading diff...</ div >
624+ </ div >
625+ ) ;
626+ }
627+
585628 return (
586629 < FullscreenOverlay isFullscreen = { isFullscreen } >
587630 < div className = "w-full h-full flex flex-col" >
@@ -602,7 +645,7 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }
602645 lineNumber = { block . lineNumber }
603646 content = { block . content }
604647 type = { block . type }
605- highlighter = { highlighter }
648+ highlighter = { highlighter } // Pass the shared instance
606649 language = { language }
607650 block = { block }
608651 theme = { theme }
0 commit comments