CodeHighlighted
@cloudflare/kumo

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 dev
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>
  );
}

/** 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();
}
CSS Variable--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/kumo
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 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, jsonJS~75 KB
标准配置tsx, ts, bash, json, css, yamlJS~95 KB
完整配置15+ 种语言WASM~250 KB

不引入 @cloudflare/kumo/code 的应用额外体积为 0 KB。

从 Code/CodeBlock 迁移

旧版 CodeCodeBlock 组件已废弃。 它们将在 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)

languagesstring[]要支持的语言(如 [“tsx”, “bash”])
labels{ copy?: string, copied?: string }复制按钮的本地化文案
childrenReactNode应用内容

CodeHighlighted 属性

属性类型必填说明
codestring要展示的源代码
langstring

语言标识(必须在 provider 的 languages 中)

showLineNumbersboolean显示行号
highlightLinesnumber[]要强调的行(从 1 开始索引)
showCopyButtonboolean显示复制到剪贴板的按钮
labels{ copy?: string, copied?: string }覆盖此实例的 provider 文案
classNamestring额外的 CSS 类

useShikiHighlighter 返回值

属性类型说明
highlight(code, lang, options?) => string | null返回高亮后的 HTML;未就绪时返回 null
isLoadingbooleanShiki 加载期间为 true
isReadyboolean当 highlight() 可安全调用时为 true
errorError | nullShiki 初始化失败时返回错误