perf: reduce bundle size by using custom highlight.js subset
Replace highlight.js/lib/common (36 languages) with a curated subset of 16 languages relevant for coding-assistant chat UIs. Both ToolCall (direct hljs) and ChatMessage (rehype-highlight) now share the same custom bundle from src/lib/highlight.ts. Languages included: bash, css, diff, dockerfile, go, ini, javascript, json, markdown, python, rust, shell, sql, typescript, xml, yaml. Markdown chunk: 477KB → 336KB (-30%, -45KB gzipped)
This commit is contained in:
@@ -5,6 +5,7 @@ import ReactMarkdown from 'react-markdown';
|
|||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import remarkBreaks from 'remark-breaks';
|
import remarkBreaks from 'remark-breaks';
|
||||||
import rehypeHighlight from 'rehype-highlight';
|
import rehypeHighlight from 'rehype-highlight';
|
||||||
|
import { rehypeHighlightOptions } from '../lib/highlight';
|
||||||
import type { ChatMessage as ChatMessageType, MessageBlock } from '../types';
|
import type { ChatMessage as ChatMessageType, MessageBlock } from '../types';
|
||||||
import { ThinkingBlock } from './ThinkingBlock';
|
import { ThinkingBlock } from './ThinkingBlock';
|
||||||
import { ThinkingIndicator } from './ThinkingIndicator';
|
import { ThinkingIndicator } from './ThinkingIndicator';
|
||||||
@@ -169,7 +170,7 @@ const markdownComponents = { pre: CodeBlock, img: MarkdownImage, a: MarkdownLink
|
|||||||
function renderTextBlocks(blocks: MessageBlock[]) {
|
function renderTextBlocks(blocks: MessageBlock[]) {
|
||||||
return getTextBlocks(blocks).map((block, i) => (
|
return getTextBlocks(blocks).map((block, i) => (
|
||||||
<div key={`text-${i}`} className="markdown-body">
|
<div key={`text-${i}`} className="markdown-body">
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]} rehypePlugins={[rehypeHighlight]} components={markdownComponents}>
|
<ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]} rehypePlugins={[[rehypeHighlight, rehypeHighlightOptions]]} components={markdownComponents}>
|
||||||
{autoFormatText((block as Extract<MessageBlock, { type: 'text' }>).text)}
|
{autoFormatText((block as Extract<MessageBlock, { type: 'text' }>).text)}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
@@ -470,7 +471,7 @@ export function ChatMessageComponent({ message: rawMessage, onRetry, agentAvatar
|
|||||||
{/* User-visible text */}
|
{/* User-visible text */}
|
||||||
{message.blocks.length > 0 ? renderTextBlocks(message.blocks) : (
|
{message.blocks.length > 0 ? renderTextBlocks(message.blocks) : (
|
||||||
<div className="markdown-body">
|
<div className="markdown-body">
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]} rehypePlugins={[rehypeHighlight]} components={markdownComponents}>
|
<ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]} rehypePlugins={[[rehypeHighlight, rehypeHighlightOptions]]} components={markdownComponents}>
|
||||||
{autoFormatText(message.content)}
|
{autoFormatText(message.content)}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
|
import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
|
||||||
import { ChevronRight, ChevronDown, Check, Copy, WrapText, AlignLeft } from 'lucide-react';
|
import { ChevronRight, ChevronDown, Check, Copy, WrapText, AlignLeft } from 'lucide-react';
|
||||||
import hljs from 'highlight.js/lib/common';
|
import hljs from '../lib/highlight';
|
||||||
import { useT } from '../hooks/useLocale';
|
import { useT } from '../hooks/useLocale';
|
||||||
import { ImageBlock } from './ImageBlock';
|
import { ImageBlock } from './ImageBlock';
|
||||||
import { useToolCollapse } from '../hooks/useToolCollapse';
|
import { useToolCollapse } from '../hooks/useToolCollapse';
|
||||||
|
|||||||
80
src/lib/highlight.ts
Normal file
80
src/lib/highlight.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Custom highlight.js bundle with only the languages relevant for
|
||||||
|
* a coding-assistant chat UI. This replaces `highlight.js/lib/common`
|
||||||
|
* (36 languages) with a focused subset (~16), cutting bundle size significantly.
|
||||||
|
*/
|
||||||
|
import hljs from 'highlight.js/lib/core';
|
||||||
|
import type { LanguageFn } from 'highlight.js';
|
||||||
|
|
||||||
|
// Languages commonly seen in AI assistant conversations
|
||||||
|
import bash from 'highlight.js/lib/languages/bash';
|
||||||
|
import css from 'highlight.js/lib/languages/css';
|
||||||
|
import diff from 'highlight.js/lib/languages/diff';
|
||||||
|
import dockerfile from 'highlight.js/lib/languages/dockerfile';
|
||||||
|
import go from 'highlight.js/lib/languages/go';
|
||||||
|
import ini from 'highlight.js/lib/languages/ini';
|
||||||
|
import javascript from 'highlight.js/lib/languages/javascript';
|
||||||
|
import json from 'highlight.js/lib/languages/json';
|
||||||
|
import markdown from 'highlight.js/lib/languages/markdown';
|
||||||
|
import python from 'highlight.js/lib/languages/python';
|
||||||
|
import rust from 'highlight.js/lib/languages/rust';
|
||||||
|
import shell from 'highlight.js/lib/languages/shell';
|
||||||
|
import sql from 'highlight.js/lib/languages/sql';
|
||||||
|
import typescript from 'highlight.js/lib/languages/typescript';
|
||||||
|
import xml from 'highlight.js/lib/languages/xml';
|
||||||
|
import yaml from 'highlight.js/lib/languages/yaml';
|
||||||
|
|
||||||
|
hljs.registerLanguage('bash', bash);
|
||||||
|
hljs.registerLanguage('css', css);
|
||||||
|
hljs.registerLanguage('diff', diff);
|
||||||
|
hljs.registerLanguage('dockerfile', dockerfile);
|
||||||
|
hljs.registerLanguage('go', go);
|
||||||
|
hljs.registerLanguage('ini', ini);
|
||||||
|
hljs.registerLanguage('javascript', javascript);
|
||||||
|
hljs.registerLanguage('json', json);
|
||||||
|
hljs.registerLanguage('markdown', markdown);
|
||||||
|
hljs.registerLanguage('python', python);
|
||||||
|
hljs.registerLanguage('rust', rust);
|
||||||
|
hljs.registerLanguage('shell', shell);
|
||||||
|
hljs.registerLanguage('sql', sql);
|
||||||
|
hljs.registerLanguage('typescript', typescript);
|
||||||
|
hljs.registerLanguage('xml', xml);
|
||||||
|
hljs.registerLanguage('yaml', yaml);
|
||||||
|
|
||||||
|
// Aliases for hljs direct usage (ToolCall.tsx)
|
||||||
|
hljs.registerAliases(['sh', 'zsh'], { languageName: 'bash' });
|
||||||
|
hljs.registerAliases(['js', 'jsx'], { languageName: 'javascript' });
|
||||||
|
hljs.registerAliases(['ts', 'tsx'], { languageName: 'typescript' });
|
||||||
|
hljs.registerAliases(['py'], { languageName: 'python' });
|
||||||
|
hljs.registerAliases(['html'], { languageName: 'xml' });
|
||||||
|
hljs.registerAliases(['yml'], { languageName: 'yaml' });
|
||||||
|
hljs.registerAliases(['toml', 'cfg', 'conf'], { languageName: 'ini' });
|
||||||
|
hljs.registerAliases(['rs'], { languageName: 'rust' });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Language map for rehype-highlight (used in ChatMessage.tsx).
|
||||||
|
* rehype-highlight uses lowlight internally and accepts a `languages` record.
|
||||||
|
*/
|
||||||
|
export const rehypeHighlightLanguages: Record<string, LanguageFn> = {
|
||||||
|
bash, css, diff, dockerfile, go, ini, javascript, json,
|
||||||
|
markdown, python, rust, shell, sql, typescript, xml, yaml,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rehype-highlight options with our custom language subset.
|
||||||
|
*/
|
||||||
|
export const rehypeHighlightOptions = {
|
||||||
|
languages: rehypeHighlightLanguages,
|
||||||
|
aliases: {
|
||||||
|
bash: ['sh', 'zsh'],
|
||||||
|
javascript: ['js', 'jsx'],
|
||||||
|
typescript: ['ts', 'tsx'],
|
||||||
|
python: ['py'],
|
||||||
|
xml: ['html'],
|
||||||
|
yaml: ['yml'],
|
||||||
|
ini: ['toml', 'cfg', 'conf'],
|
||||||
|
rust: ['rs'],
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export default hljs;
|
||||||
Reference in New Issue
Block a user