返回博客

rehype-pretty-code 双主题配置完全正确,代码却全灰——选择器打错了

给技术博客接入代码高亮,选了 rehype-pretty-code + 双主题方案(亮色/暗色各一套)。配置看起来无懈可击:theme: { light: "github-light", dark: "github-dark" }keepBackground: falseglobals.css 里写好了 token 变色规则。构建一切正常,打开页面——代码块是一整片灰色,没有任何高亮。

行内 `code` 正常,只有围栏代码块全军覆没。

现象

打开 DevTools 检查 DOM,发现 token span 上确实有内联样式:

<span style="--shiki-light:#D73A49;--shiki-dark:#F97583">import</span>

变量值完全正确,Shiki 已经算好了每个 token 的颜色。但每个 token 最终呈现的颜色都一样,就是 Prose 的默认前景色——灰。

再看 CSS:

/* 原来写的 */
.shiki, .shiki span {
  color: var(--shiki-light);
}
html.dark .shiki span {
  color: var(--shiki-dark);
}

浏览器里选中这两条规则,匹配元素数:0

根因

.shiki 类名是裸 Shiki(直接用 shiki 包调用 codeToHtml())输出的,rehype-pretty-code 不加这个类。

rehype-pretty-code 的真实 DOM 结构是:

figure[data-rehype-pretty-code-figure]
  └ pre[data-theme="github-light github-dark"]
      └ code                          ← 没有 .shiki 类
          └ span[data-line]
              └ span[style="--shiki-light:#..;--shiki-dark:#.."]

precode 上没有任何 class,只有 data-* 属性。选择器 .shiki span 一个元素也选不中,token 上的 CSS 变量永远不会被读取,每个 token 老老实实继承父元素的颜色——全灰。

坑:.shiki 类是裸 Shiki 的产物,rehype-pretty-code 不输出它

把高亮 CSS 写成 .shiki span { color: var(--shiki-light) } 是从 Shiki 官方文档复制过来的常见误操作。rehype-pretty-code 包装了 Shiki,但刻意用 data-rehype-pretty-code-figure 等属性作为锚点,不保留 .shiki 类名。DevTools 里看到 token 有 --shiki-light 变量却不生效,八成就是这个原因。

正解

把选择器换成 rehype-pretty-code 实际输出的 data-* 属性,并用你的主题切换属性(这里用 html[data-theme])来切换变量:

/* 重置 figure 默认的 margin-left: 40px */
[data-rehype-pretty-code-figure] {
  margin: 0;
}
 
/* 亮色:读 --shiki-light */
[data-rehype-pretty-code-figure] code span {
  color: var(--shiki-light);
}
 
/* 暗色:读 --shiki-dark */
html[data-theme="dark"] [data-rehype-pretty-code-figure] code span {
  color: var(--shiki-dark);
}
 
/* 可选:高亮行背景 */
[data-rehype-pretty-code-figure] [data-highlighted-line] {
  background-color: var(--color-surface-hover);
}

pre 的背景色继续由你的 Prose/pre 样式控制(正好对应 keepBackground: false 的语义)。

正解:锚到 data-rehype-pretty-code-figure,而非 .shiki

选择器固定用 [data-rehype-pretty-code-figure] code span,主题切换通过 html[data-theme="dark"] 前缀区分亮暗——这是文档推荐的、无需额外依赖的双主题写法。

验证

修完之后在控制台抓两个语义不同的 token,颜色必须不同:

// 关键字(keyword red)
getComputedStyle(importSpan).color  // → "rgb(215, 58, 73)"
 
// 字符串(string blue)
getComputedStyle(stringSpan).color  // → "rgb(3, 47, 98)"

如果两个值不同,高亮就真正工作了。

附:Turbopack 下的额外坑

用 Next.js + Turbopack 开发时,rehype-pretty-code 必须以字符串元组形式传入,不能直接 import 函数再传:

// next.config.js — Turbopack 兼容写法
rehypePlugins: [["rehype-pretty-code", { theme: { light: "github-light", dark: "github-dark" } }]]
 
// 以下写法在 Turbopack 下会报错(函数无法序列化)
import rehypePrettyCode from "rehype-pretty-code";
rehypePlugins: [[rehypePrettyCode, { ... }]]

一句话外卖

token 变量存在但颜色全灰,先查 CSS 选择器是否真的命中了元素——rehype-pretty-code 输出的是 data-* 属性,不是 .shiki 类。