v1.10 新增: 基于 Shiki 的语法高亮,支持懒加载。
从 @cloudflare/kumo/code 导入即可使用。
const greeting = "Hello, World!";
console.log(greeting);import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** Basic syntax highlighting demo */
export function CodeHighlightedBasicDemo() {
return (
<DemoProvider>
<CodeHighlighted
code={`const greeting = "Hello, World!";
console.log(greeting);`}
lang="typescript"
/>
</DemoProvider>
);
}概览
一个基于 Shiki 的语法高亮器。通过 TextMate 语法支持 200 多种语言,
具备浅色/深色双主题与懒加载能力。从独立入口点(@cloudflare/kumo/code)
导出,避免把 Shiki 打进不需要它的应用的产物里。
安装
CodeHighlighted 从独立入口点导出,避免把 Shiki 打进不需要它的应用的产物里。
import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";注意: 请不要从主入口 @cloudflare/kumo 导入。
那样即使你不用 Shiki,它也会被拉进你的产物里。
基本用法
用 ShikiProvider 包裹你的应用,一次性配置 Shiki。
其内部的所有 CodeHighlighted 组件共享同一个 Shiki 实例。
import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
export function App() {
return (
<ShikiProvider
engine="javascript"
languages={["tsx", "typescript", "bash", "json"]}
>
{/* 所有 CodeHighlighted 组件共享同一个 Shiki 实例 */}
<CodeHighlighted code="const x = 1;" lang="typescript" />
</ShikiProvider>
);
}示例
语言
CodeHighlighted 通过 Shiki 支持 200 多种语言。只加载你需要的语言即可。
TypeScript
interface User {
id: string;
name: string;
email: string;
}
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** TypeScript with interface */
export function CodeHighlightedTypeScriptDemo() {
return (
<DemoProvider>
<CodeHighlighted
code={`interface User {
id: string;
name: string;
email: string;
}
async function fetchUser(id: string): Promise<User> {
const response = await fetch(\`/api/users/\${id}\`);
return response.json();
}`}
lang="typescript"
/>
</DemoProvider>
);
}React / TSX
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** React/TSX code example */
export function CodeHighlightedReactDemo() {
return (
<DemoProvider>
<CodeHighlighted
code={`import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}`}
lang="tsx"
/>
</DemoProvider>
);
}Bash / Shell
# Install Kumo
npm install @cloudflare/kumo
# Or with pnpm
pnpm add @cloudflare/kumo
# Start development server
pnpm devimport { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** Bash/shell commands */
export function CodeHighlightedBashDemo() {
return (
<DemoProvider>
<CodeHighlighted
code={`# Install Kumo
npm install @cloudflare/kumo
# Or with pnpm
pnpm add @cloudflare/kumo
# Start development server
pnpm dev`}
lang="bash"
/>
</DemoProvider>
);
}JSON
{
"name": "@cloudflare/kumo",
"version": "1.9.0",
"dependencies": {
"react": "^19.0.0",
"shiki": "^4.0.0"
}
}import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** JSON configuration */
export function CodeHighlightedJsonDemo() {
return (
<DemoProvider>
<CodeHighlighted
code={`{
"name": "@cloudflare/kumo",
"version": "1.9.0",
"dependencies": {
"react": "^19.0.0",
"shiki": "^4.0.0"
}
}`}
lang="json"
/>
</DemoProvider>
);
}CSS
.button {
background: var(--color-brand);
border-radius: 0.5rem;
padding: 0.5rem 1rem;
&:hover {
background: var(--color-brand-hover);
}
}import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** CSS code example */
export function CodeHighlightedCssDemo() {
return (
<DemoProvider>
<CodeHighlighted
code={`.button {
background: var(--color-brand);
border-radius: 0.5rem;
padding: 0.5rem 1rem;
&:hover {
background: var(--color-brand-hover);
}
}`}
lang="css"
/>
</DemoProvider>
);
}高亮指定行
用 highlightLines 强调指定行(从 1 开始计数)。
function processData(items: string[]) {
// Filter out empty items
const filtered = items.filter(Boolean);
// Transform to uppercase (highlighted)
const transformed = filtered.map(item => item.toUpperCase());
// Return sorted result
return transformed.toSorted();
}import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** With highlighted lines */
export function CodeHighlightedHighlightLinesDemo() {
return (
<DemoProvider>
<CodeHighlighted
code={`function processData(items: string[]) {
// Filter out empty items
const filtered = items.filter(Boolean);
// Transform to uppercase (highlighted)
const transformed = filtered.map(item => item.toUpperCase());
// Return sorted result
return transformed.toSorted();
}`}
lang="typescript"
highlightLines={[5, 6]}
/>
</DemoProvider>
);
}自定义高亮颜色
通过 --kumo-code-highlight-bg CSS 变量自定义高亮颜色。
function greet(name: string) {
// This line is highlighted
console.log(`Hello, ${name}!`);
return name.toUpperCase();
}--kumo-code-highlight-bg: hsla(220, 80%, 50%, 0.1)行号
用 showLineNumbers 显示行号。
import { useState, useEffect } from "react";
export function useWindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
function handleResize() {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
handleResize();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return size;
}import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** With line numbers */
export function CodeHighlightedLineNumbersDemo() {
return (
<DemoProvider>
<CodeHighlighted
code={`import { useState, useEffect } from "react";
export function useWindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
function handleResize() {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
handleResize();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return size;
}`}
lang="typescript"
showLineNumbers
/>
</DemoProvider>
);
}复制按钮
用 showCopyButton 添加复制到剪贴板的按钮。
npm install @cloudflare/kumoimport { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** With copy button */
export function CodeHighlightedCopyButtonDemo() {
return (
<DemoProvider>
<CodeHighlighted
code={`npm install @cloudflare/kumo`}
lang="bash"
showCopyButton
/>
</DemoProvider>
);
}朴素样式(Plain)
在把高亮代码嵌入其他容器时,用 variant="plain" 去掉外层表面、
边框和圆角。
type EmbeddedOptions = {
enabled: boolean;
target: string;
};
const options = {
enabled: true,
target: "#preview",
} satisfies EmbeddedOptions;
console.log(options);type EmbeddedOptions = {
enabled: boolean;
target: string;
};
const options = {
enabled: true,
target: "#preview",
} satisfies EmbeddedOptions;
console.log(options);import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** Frameless code block for embedding in another surface */
export function CodeHighlightedPlainDemo() {
return (
<DemoProvider>
<div className="grid divide-x divide-kumo-line rounded border border-kumo-line md:grid-cols-2">
<div className="p-2">
<CodeHighlighted
showLineNumbers
code={`type EmbeddedOptions = {
enabled: boolean;
target: string;
};
const options = {
enabled: true,
target: "#preview",
} satisfies EmbeddedOptions;
console.log(options);`}
lang="typescript"
variant="plain"
/>
</div>
<div className="p-2">
<CodeHighlighted
showCopyButton
code={`type EmbeddedOptions = {
enabled: boolean;
target: string;
};
const options = {
enabled: true,
target: "#preview",
} satisfies EmbeddedOptions;
console.log(options);`}
lang="typescript"
variant="plain"
highlightLines={[6, 7, 8, 9]}
/>
</div>
</div>
</DemoProvider>
);
}完整功能
组合所有功能,获得完整的代码展示体验。
import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
export function CodeExample({ code, language }: Props) {
return (
<ShikiProvider
engine="javascript"
languages={["tsx", "typescript", "bash", "json"]}
>
<CodeHighlighted
code={code}
lang={language}
showCopyButton
/>
</ShikiProvider>
);
}import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** Full featured example */
export function CodeHighlightedFullFeaturedDemo() {
return (
<DemoProvider>
<CodeHighlighted
code={`import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
export function CodeExample({ code, language }: Props) {
return (
<ShikiProvider
engine="javascript"
languages={["tsx", "typescript", "bash", "json"]}
>
<CodeHighlighted
code={code}
lang={language}
showCopyButton
/>
</ShikiProvider>
);
}`}
lang="tsx"
showCopyButton
highlightLines={[6, 7, 8, 9]}
/>
</DemoProvider>
);
}共享 Provider
多个代码块可以共享同一个 ShikiProvider。
Shiki 只加载一次,之后被所有代码块复用。
const config = { theme: "dark" };npm run build{ "success": true }import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
import { ReactNode } from "react";
/**
* Wrapper component that provides Shiki context for all demos.
* This loads Shiki once and shares it across all CodeHighlighted instances.
*/
function DemoProvider({ children }: { children: ReactNode }) {
return (
<ShikiProvider
engine="javascript"
languages={[
"tsx",
"typescript",
"javascript",
"bash",
"json",
"css",
"html",
]}
>
{children}
</ShikiProvider>
);
}
/** Multiple code blocks sharing a provider */
export function CodeHighlightedSharedProviderDemo() {
return (
<DemoProvider>
<div className="space-y-4">
<CodeHighlighted
code={`const config = { theme: "dark" };`}
lang="typescript"
/>
<CodeHighlighted code={`npm run build`} lang="bash" />
<CodeHighlighted code={`{ "success": true }`} lang="json" />
</div>
</DemoProvider>
);
}主题
CodeHighlighted 使用硬编码的主题,以保证所有 Kumo 应用的样式一致:
浅色模式:
github-light深色模式:
vesper
不支持自定义主题。这保证了应用中所有代码块的视觉一致性。
服务端用法
对于 SSR 框架(Next.js RSC、Astro、Remix),使用服务端工具在构建时进行高亮。
单次高亮
// Next.js RSC 或 Astro
import { highlightCode } from "@cloudflare/kumo/code/server";
export default async function Page() {
const html = await highlightCode(`const x = 1;`, "typescript");
return <pre dangerouslySetInnerHTML={{ __html: html }} />;
}可复用的高亮器
// 多次高亮时,复用同一个高亮器
import { createServerHighlighter } from "@cloudflare/kumo/code/server";
const highlighter = await createServerHighlighter({
languages: ["tsx", "bash", "json"],
});
const html1 = highlighter.highlight(code1, "tsx");
const html2 = highlighter.highlight(code2, "bash");
highlighter.dispose(); // 用完后清理自定义 Hook
用 useShikiHighlighter 实现自定义逻辑。
import { useShikiHighlighter } from "@cloudflare/kumo/code";
function CustomCodeBlock({ code, lang }) {
const { highlight, isLoading, isReady, error } = useShikiHighlighter();
if (error) {
return <div className="text-red-500">Failed to load highlighter</div>;
}
if (isLoading) {
return (
<pre className="animate-pulse">
<code>{code}</code>
</pre>
);
}
const html = highlight(code, lang);
// null 表示高亮失败 —— 渲染纯文本
if (html === null) {
return (
<pre>
<code>{code}</code>
</pre>
);
}
return <pre dangerouslySetInnerHTML={{ __html: html }} />;
}国际化
可以在 Provider 层为所有代码块定制按钮文案,也可以按组件单独覆盖。
// 在 Provider 层为所有代码块设置文案
<ShikiProvider
engine="javascript"
languages={["tsx", "bash"]}
labels={{ copy: "Copier", copied: "Copié!" }}
>
<App />
</ShikiProvider>
// 或在组件层覆盖
<CodeHighlighted
code={code}
lang="tsx"
showCopyButton
labels={{ copy: "Copy code", copied: "Done!" }}
/>框架集成
Next.js App Router
// app/providers.tsx
"use client";
import { ShikiProvider } from "@cloudflare/kumo/code";
export function Providers({ children }) {
return (
<ShikiProvider engine="javascript" languages={["tsx", "bash", "json"]}>
{children}
</ShikiProvider>
);
}
// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Astro(静态)
对于静态站点,使用服务端高亮,客户端可以做到零 JavaScript。
---
// src/components/CodeBlock.astro
import { highlightCode } from "@cloudflare/kumo/code/server";
const { code, lang } = Astro.props;
const html = await highlightCode(code, lang);
---
<div class="code-block" set:html={html} />打包体积
Shiki 在首次渲染时懒加载。体积取决于你的配置:
| 场景 | 语言 | 引擎 | 懒加载体积 |
|---|---|---|---|
| 最小 | tsx, json | JS | ~75 KB |
| 标准 | tsx, ts, bash, json, css, yaml | JS | ~95 KB |
| 完整 | 15+ 种语言 | WASM | ~250 KB |
没有从 @cloudflare/kumo/code 导入的团队,体积开销为 0 KB。
从 Code/CodeBlock 迁移
旧的 Code 和 CodeBlock 组件已弃用,
将在 v2.0 中移除。
// 之前(已弃用)
import { Code, CodeBlock } from "@cloudflare/kumo";
<CodeBlock code="const x = 1;" lang="ts" />
// 之后
import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
// 在应用根部配置一次
<ShikiProvider engine="javascript" languages={["tsx"]}>
<App />
</ShikiProvider>
// 在组件中使用
<CodeHighlighted code="const x = 1;" lang="tsx" />API 参考
ShikiProvider 属性
| 属性 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
engine | ”javascript” | “wasm” | 是 | JS 体积更小(~50KB),WASM 更精确(~180KB) |
languages | string[] | 是 | 要支持的语言(如 [“tsx”, “bash”]) |
labels | { copy?: string, copied?: string } | 否 | 复制按钮的本地化文案 |
children | ReactNode | 是 | 应用内容 |
CodeHighlighted 属性
| 属性 | 类型 | 是否必需 | 说明 |
|---|---|---|---|
code | string | 是 | 要显示的源代码 |
lang | string | 是 | 语言标识(必须在 Provider 的 languages 中) |
variant | ”default” | “plain” | 否 | 代码块的视觉样式 |
showLineNumbers | boolean | 否 | 显示行号 |
highlightLines | number[] | 否 | 要强调的行(从 1 开始计数) |
showCopyButton | boolean | 否 | 显示复制到剪贴板按钮 |
labels | { copy?: string, copied?: string } | 否 | 覆盖此实例的 Provider 文案 |
className | string | 否 | 额外的 CSS 类名 |
useShikiHighlighter 返回值
| 属性 | 类型 | 说明 |
|---|---|---|
highlight | (code, lang, options?) => string | null | 返回高亮后的 HTML,未就绪时返回 null |
isLoading | boolean | Shiki 加载期间为 true |
isReady | boolean | 可以安全调用 highlight() 时为 true |
error | Error | null | Shiki 初始化失败时的错误 |