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 的语法高亮器。通过 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 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>
  );
}

朴素样式(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, jsonJS~75 KB
标准tsx, ts, bash, json, css, yamlJS~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)

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

CodeHighlighted 属性

属性类型是否必需说明
codestring是要显示的源代码
langstring是

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

variant”default” | “plain”否代码块的视觉样式
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 初始化失败时的错误