Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
"lightningcss",
"sharp",
"workerd"
]
],
"overrides": {
"@pierre/diffs>shiki": "4.0.2",
"@pierre/diffs>@shikijs/transformers": "4.0.2"
},
"patchedDependencies": {
"@pierre/diffs@1.1.12": "patches/@pierre__diffs@1.1.12.patch"
}
}
}
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toggle": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@shikijs/langs": "4.0.2",
"@shikijs/rehype": "^4.0.2",
"@tailwindcss/typography": "^0.5.19",
"class-variance-authority": "^0.7.1",
Expand Down
78 changes: 31 additions & 47 deletions packages/ui/src/components/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,37 @@ import {
import rehypeRaw from "rehype-raw";
import remarkGfm from "remark-gfm";
import { remarkAlert } from "remark-github-blockquote-alert";
import type { BundledLanguage, Highlighter } from "shiki";
import { vercelDark, vercelLight } from "../lib/shiki-themes";
import {
createMarkdownHighlighter,
type MarkdownHighlighter,
type ShikiBundledLang,
shikiBundledLangSet,
} from "../lib/shiki-bundle";
import { cn } from "../lib/utils";

const PRELOADED_LANGS: BundledLanguage[] = [
"javascript",
"typescript",
"jsx",
"tsx",
"json",
"html",
"css",
"bash",
"shell",
"python",
"go",
"rust",
"yaml",
"markdown",
"diff",
"sql",
"graphql",
"ruby",
"java",
"c",
"cpp",
"swift",
"kotlin",
"dockerfile",
"toml",
];
const FENCE_LANG_ALIASES: Record<string, ShikiBundledLang> = {
shell: "shellscript",
sh: "shellscript",
zsh: "shellscript",
console: "shellscript",
js: "javascript",
ts: "typescript",
py: "python",
rs: "rust",
rb: "ruby",
kt: "kotlin",
cs: "csharp",
yml: "yaml",
gql: "graphql",
md: "markdown",
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Eagerly start loading the highlighter at module level (client-only to avoid
// bundling all shiki language grammars into the server bundle for CF Workers).
const highlighterPromise: Promise<Highlighter> =
const highlighterPromise: Promise<MarkdownHighlighter> =
typeof window !== "undefined"
? import("shiki").then((shiki) =>
shiki.createHighlighter({
themes: [vercelLight, vercelDark],
langs: PRELOADED_LANGS,
}),
)
: new Promise<Highlighter>(() => {}); // Never resolves on server → Suspense fallback
? createMarkdownHighlighter()
: new Promise<MarkdownHighlighter>(() => {}); // Never resolves on server → Suspense fallback

const htmlCache = new Map<string, Promise<string>>();

Expand Down Expand Up @@ -96,18 +84,14 @@ export function highlightCode(code: string, lang: string): Promise<string> {
const cached = htmlCache.get(key);
if (cached) return cached;

const promise = highlighterPromise.then(async (highlighter) => {
let effectiveLang = lang;
if (!highlighter.getLoadedLanguages().includes(lang)) {
try {
await highlighter.loadLanguage(lang as BundledLanguage);
} catch {
effectiveLang = "text";
}
}
const promise = highlighterPromise.then((highlighter) => {
const normalized =
FENCE_LANG_ALIASES[lang] ??
(shikiBundledLangSet.has(lang) ? (lang as ShikiBundledLang) : null);
const effectiveLang = normalized ?? "text";
return highlighter.codeToHtml(code, {
lang: effectiveLang,
themes: { light: "vercel-light", dark: "vercel-dark" },
themes: { light: "diffkit-light", dark: "diffkit-dark" },
defaultColor: false,
});
});
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/lib/diffs-themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import type { ThemeRegistrationRaw } from "shiki";
// addition/deletion colors, etc.).
//
// The `colors` object drives the diff chrome; `tokenColors` drives syntax
// highlighting. We reuse the same Vercel-inspired token palette from
// shiki-themes.ts but pair it with our own editor/UI chrome colors so the
// highlighting. We reuse the same token palette as shiki-themes.ts (diffkit
// light/dark) but pair it with our own editor/UI chrome colors so the
// diff viewer feels native to the app.
// ---------------------------------------------------------------------------

Expand Down
87 changes: 87 additions & 0 deletions packages/ui/src/lib/shiki-bundle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { createHighlighterCore } from "shiki/core";
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
import { diffkitDark, diffkitLight } from "./shiki-themes";

/**
* Fine-grained Shiki bundle: only these grammars are ever loaded (see shiki.style/guide/bundles).
* Unknown fence languages fall back to unstyled output via `text` in highlight callers.
*/
export const SHIKI_BUNDLED_LANGS = [
"javascript",
"typescript",
"jsx",
"tsx",
"json",
"html",
"css",
"bash",
"shellscript",
"python",
"go",
"rust",
"yaml",
"markdown",
"diff",
"sql",
"graphql",
"ruby",
"java",
"c",
"cpp",
"swift",
"kotlin",
"dockerfile",
"toml",
"vue",
"svelte",
"php",
"csharp",
] as const;

export type ShikiBundledLang = (typeof SHIKI_BUNDLED_LANGS)[number];

const LANG_IMPORTS = {
javascript: () => import("@shikijs/langs/javascript"),
typescript: () => import("@shikijs/langs/typescript"),
jsx: () => import("@shikijs/langs/jsx"),
tsx: () => import("@shikijs/langs/tsx"),
json: () => import("@shikijs/langs/json"),
html: () => import("@shikijs/langs/html"),
css: () => import("@shikijs/langs/css"),
bash: () => import("@shikijs/langs/bash"),
shellscript: () => import("@shikijs/langs/shellscript"),
python: () => import("@shikijs/langs/python"),
go: () => import("@shikijs/langs/go"),
rust: () => import("@shikijs/langs/rust"),
yaml: () => import("@shikijs/langs/yaml"),
markdown: () => import("@shikijs/langs/markdown"),
diff: () => import("@shikijs/langs/diff"),
sql: () => import("@shikijs/langs/sql"),
graphql: () => import("@shikijs/langs/graphql"),
ruby: () => import("@shikijs/langs/ruby"),
java: () => import("@shikijs/langs/java"),
c: () => import("@shikijs/langs/c"),
cpp: () => import("@shikijs/langs/cpp"),
swift: () => import("@shikijs/langs/swift"),
kotlin: () => import("@shikijs/langs/kotlin"),
dockerfile: () => import("@shikijs/langs/dockerfile"),
toml: () => import("@shikijs/langs/toml"),
vue: () => import("@shikijs/langs/vue"),
svelte: () => import("@shikijs/langs/svelte"),
php: () => import("@shikijs/langs/php"),
csharp: () => import("@shikijs/langs/csharp"),
} as const satisfies Record<ShikiBundledLang, () => Promise<unknown>>;

export const shikiBundledLangSet = new Set<string>(SHIKI_BUNDLED_LANGS);

export type MarkdownHighlighter = Awaited<
ReturnType<typeof createHighlighterCore>
>;

export function createMarkdownHighlighter(): Promise<MarkdownHighlighter> {
return createHighlighterCore({
themes: [diffkitLight, diffkitDark],
langs: SHIKI_BUNDLED_LANGS.map((id) => LANG_IMPORTS[id]),
engine: createJavaScriptRegexEngine(),
});
}
20 changes: 10 additions & 10 deletions packages/ui/src/lib/shiki-themes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ThemeRegistrationRaw } from "shiki";

const vercelLightTokens: ThemeRegistrationRaw["tokenColors"] = [
const diffkitLightTokens: ThemeRegistrationRaw["tokenColors"] = [
{
scope: ["comment", "punctuation.definition.comment"],
settings: { foreground: "#666666", fontStyle: "italic" },
Expand Down Expand Up @@ -132,7 +132,7 @@ const vercelLightTokens: ThemeRegistrationRaw["tokenColors"] = [
},
];

const vercelDarkTokens: ThemeRegistrationRaw["tokenColors"] = [
const diffkitDarkTokens: ThemeRegistrationRaw["tokenColors"] = [
{
scope: ["comment", "punctuation.definition.comment"],
settings: { foreground: "#a1a1a1", fontStyle: "italic" },
Expand Down Expand Up @@ -264,24 +264,24 @@ const vercelDarkTokens: ThemeRegistrationRaw["tokenColors"] = [
},
];

export const vercelLight: ThemeRegistrationRaw = {
name: "vercel-light",
export const diffkitLight: ThemeRegistrationRaw = {
name: "diffkit-light",
type: "light",
settings: vercelLightTokens as ThemeRegistrationRaw["settings"],
settings: diffkitLightTokens as ThemeRegistrationRaw["settings"],
colors: {
"editor.background": "#ffffff",
"editor.foreground": "#171717",
},
tokenColors: vercelLightTokens,
tokenColors: diffkitLightTokens,
};

export const vercelDark: ThemeRegistrationRaw = {
name: "vercel-dark",
export const diffkitDark: ThemeRegistrationRaw = {
name: "diffkit-dark",
type: "dark",
settings: vercelDarkTokens as ThemeRegistrationRaw["settings"],
settings: diffkitDarkTokens as ThemeRegistrationRaw["settings"],
colors: {
"editor.background": "#1a1a1a",
"editor.foreground": "#ededed",
},
tokenColors: vercelDarkTokens,
tokenColors: diffkitDarkTokens,
};
Loading
Loading