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 的语法高亮组件。支持 200+ 种语言(基于 TextMate 语法)、浅色/深色双主题,并按需加载。通过独立的入口( @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"]}
>
{/* All CodeHighlighted components share the same Shiki instance */}
<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>
);
}完整功能
组合全部功能,带来完整的代码展示体验。
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 or 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 }} />;
}可复用高亮器
// For multiple highlights, reuse the highlighter
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(); // Clean up when done自定义 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 means highlighting failed — render plain text
if (html === null) {
return (
<pre>
<code>{code}</code>
</pre>
);
}
return <pre dangerouslySetInnerHTML={{ __html: html }} />;
}国际化
可在 provider 层级为所有代码块统一设置按钮文案,也可在单个组件上覆盖。
// Set labels at the provider level for all code blocks
<ShikiProvider
engine="javascript"
languages={["tsx", "bash"]}
labels={{ copy: "Copier", copied: "Copié!" }}
>
<App />
</ShikiProvider>
// Or override at the component level
<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 中移除。
// Before (deprecated)
import { Code, CodeBlock } from "@cloudflare/kumo";
<CodeBlock code="const x = 1;" lang="ts" />
// After
import { ShikiProvider, CodeHighlighted } from "@cloudflare/kumo/code";
// Once at app root
<ShikiProvider engine="javascript" languages={["tsx"]}>
<App />
</ShikiProvider>
// In components
<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 中) |
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 初始化失败时返回错误 |