﻿---
title: Optimize Shiki's HTML Output with styleToClass Transformer
date: 2025-11-30
tags:
  - CSS
  - Shiki
  - Performance
  - Catppuccin
excerpt: 使用Shiki styleToClass转换器优化HTML输出
---

<script data-swup-reload-script type="module" src="/js/components/tab.js"></script>

Shiki提供比 `highlight.js` 更准确的代码高亮效果；以 `{js} console.log("hello")` 为例，Shiki 高亮器按语义将其拆分为 6 个 `{html} <span>` 元素，并为其添加**内联样式**（_e.g.  `color`，`{css} font-style` and CSS 变量[^1]_），以实现颜色[^2]以及字体[^3]的**多主题适配**

<x-tabs>

<x-tab title="Markdown" active>

```js
const code = await codeToHtml('console.log("hello")', {
  lang: "javascript",
  themes: { light: "min-light", dark: "nord" },
});
```

</x-tab>

<x-tab title="HTML Output">

```html
<code>
  <span class="line">
    <span style="color:#1976D2;--shiki-dark:#D8DEE9">console</span>
    <span style="color:#6F42C1;--shiki-dark:#ECEFF4">.</span>
    <span style="color:#6F42C1;--shiki-dark:#88C0D0">log</span>
    <span style="color:#24292EFF;--shiki-dark:#D8DEE9FF">(</span>
    <span style="color:#22863A;--shiki-dark:#ECEFF4">"</span>
    <span style="color:#22863A;--shiki-dark:#A3BE8C">hello</span>
    <span style="color:#22863A;--shiki-dark:#ECEFF4">"</span>
    <span style="color:#24292EFF;--shiki-dark:#D8DEE9FF">)</span>
  </span>
</code>
```

</x-tab>

<x-tab title="StyleToClass">

```html
<code>
  <span class="line">
    <span class="_sk_p7ztx5">console</span>
    <span class="_sk_8gyisu">.</span>
    <span class="_sk_18gpmt">log</span>
    <span class="_sk_23aifj space">(</span>
    <span class="_sk_23aifj">"</span>
    <span class="_sk_8gyisu">hello</span>
    <span class="_sk_18gpmt">""</span>
    <span class="_sk_p7ztx5 space">)</span>
  </span>
</code>
```

</x-tab>

</x-tabs>

上面的示例输出中，HTML 体积尚可接受，一是代码量很小，此外是仅应用了 `min-light` 和 `nord` 两种主题，且这两个主题仅在实例代码中嵌入了 `color` 属性以及 `--shiki-dark` 变量；

当这些条件不满足时，输出的 HTML 体积会大幅增加。例如个人博客中包含一些长代码片段，同时应用了 `catppuccin-latte`, `catppuccin-mocha`, `tokyo-night`, `nord`, `tokyo-night` 5 种主题，输出的 HTML 就略显臃肿；下文将记录如何使用 `styleToClass` Transformer 来优化 Shiki 高亮器的输出。

## Solutions

操作很简单，引入 `@shikijs/transformers` 包中的 `styleToClass` Transformer，并在调用 `codeToHtml` 时传入该 Transformer 即可

```ts
import { writeFileSync } from "fs";
import { transformerStyleToClass } from "@shikijs/transformers"; // [!code highlight]
import { codeToHtml } from "shiki";

const toClass = transformerStyleToClass({
  // [!code highlight]
  classPrefix: "__shiki_", // [!code highlight]
}); // [!code highlight]

const code = `console.log('hello')`;
const html = await codeToHtml(code, {
  lang: "ts",
  themes: { dark: "vitesse-dark", light: "vitesse-light" },
  transformers: [toClass], // [!code highlight]
});

const css = toClass.getCSS();

// 将生成的CSS写入~/Downloads/shiki_style_to_class.css文件
writeFileSync(
  join(
    process.env.HOME || process.env.USERPROFILE || ".",
    "Downloads/shiki_style_to_class.css"
  ),
  cssContent,
  "utf-8"
); // [!code highlight]
```

要注意的一点是，当与 `@shikijs/colorized-brackets`一起使用时，`styleToClass` Transformer 应该放在该 `colorizedBrackets` 之后；否则 `colorizedBrackets` 的样式依然会被内联到 HTML 中，而不是转换为类名。同理，其他会生成内联样式的 Transformer 也应放在 `styleToClass`之前。

> [!TIP]- transformerStyleToClass
>
> 工作原理摘抄自[官方文档](https://shiki.style/packages/transformers#transformerstyletoclass)，内容如下
> Convert Shiki's inline styles to unique classes.
>
> Class names are generated based on the hash value of the style object with the prefix/suffix you provide. You can put this transformer in multiple highlights passes and then get the CSS at the end to reuse the exact same styles. As Shiki doesn't handle CSS, it's on your integration to decide how to extract and apply/bundle the CSS.

CSS 内容写入文件后，在 Blog 或网站中引入该 CSS 文件即可；对于 Hexo 博客，可使用[Efterklang/hexo-shiki-highlight 插件](https://github.com/Efterklang/hexo-shiki-highlight#style_to_class-transformer)

## 效果

以如下 CSS Codeblock 为例，测试启用/禁用 `styleToClass` Transformer 前后，生成 `{html} <code>`体积对比，启用的主题为前面提到的 5 种主题

````md
```css
code span {
  font-style: var(--shiki-light-font-style);
}
:where([data-theme="tokyo_night"]) {
  code span {
    font-style: var(--shiki-tokyo-font-style);
    color: var(--shiki-tokyo);
  }
}
:where([data-theme="mocha"], [data-theme="macchiato"]) {
  code span {
    font-style: var(--shiki-dark-font-style);
    color: var(--shiki-dark);
  }
}
```
````

| Transformer         | HTML 体积 |
| ------------------- | --------- |
| 启用 `styleToClass` | `5.2KB`   |
| 禁用 `styleToClass` | `11.0KB`  |

测试了几组代码，基本可以压缩 45%+的HTML体积（~~同时引入了20Kb+ 的CSS~~）。使用 External CSS 代替 Inline CSS 的主要优势还是在可复用性，可维护性和可读性上，此外就是 Inline CSS 不支持 `hover` 等伪类样式，而 External CSS 则可以轻松实现。

对于体积压缩而言，除非代码量很大/应用了多种主题，否则体积压缩效果可能不明显 👉 👈

[^1]: **Custom properties** (sometimes referred to as **CSS variables** or **cascading variables**) are entities defined by CSS authors that represent specific values to be reused throughout a document. They are set using the [`font-weight`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@property) at-rule or by [custom property syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/--*) (e.g., **`text-decoration`**). Custom properties are accessed using the CSS [`--shiki-{theme-name}-font-weight`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/var) function (e.g., **`--shiki-{theme-name}-text-decolor);`**). [Using CSS custom properties (variables) - CSS | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties)
[^2]: Shiki 高亮器生成的代码片段中，包含默认主题的颜色样式，例如 `color`，`background-color`等，对于非默认主题，这些样式通过 `--shiki-{theme}`，`--shiki-{theme}-bg` 等 CSS 变量进行存储，其中 `{theme}` 为 `codeToHtml` 方法中 `themes` 字段的键名[深浅色模式 | Shiki 中文文档](https://shiki.zhcndoc.com/guide/dual-themes)
[^3]: 与颜色样式同理， `font-weight`，`text-decoration` for default theme，`--shiki-{theme}-font-weight` , `--shiki-{theme}-text-decoration` for other themes