Shiki 与 KaTeX:暗夜里的代码与公式
把代码块交给 Shiki,把公式交给 KaTeX,让技术笔记里的每一行都拥有正确的颜色与呼吸。
关于这篇笔记的排版
你看到的提示框、代码高亮与公式,都不是在浏览器里临时拼出来的。正文保存在 SQLite,每次请求由 Astro SSR 渲染成 HTML,再交给浏览器。
技术笔记里最难排版的两样东西,是代码和公式。
代码需要语义高亮、行内外的区分、可复制的按钮;公式需要正确的字体、基线、上下标与求和符号。把这两件事都交给浏览器端的 JavaScript,会让一篇本可以很轻的文章背上沉重的包。
在这套 Astro SSR 架构里,它们都可以在服务端完成,并在浏览器抵达前变成干净的 HTML。
Shiki:在服务端把代码染上颜色
Astro 内置了 Shiki 作为语法高亮器。不需要安装额外的 remark 插件,只要在配置里选一个主题:
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
markdown: {
shikiConfig: {
theme: 'rose-pine',
wrap: true,
},
},
});
rose-pine 是 Rosé Pine 主题家族里最暗的一支:底色是带紫的墨色,关键字是玫瑰粉,字符串是低饱和的薄荷绿。它和这座影之庭院几乎像是为彼此准备的。
Shiki 的工作方式是在服务端生成带内联样式的 HTML:
<pre class="astro-code rose-pine" style="background-color:#191724">
<code>
<span style="color:#c4a7e7">const</span>
<span style="color:#e0def4"> garden </span>
<span style="color:#c4a7e7">=</span>
<span style="color:#9ccfd8">'bloom'</span>
</code>
</pre>
浏览器不需要加载任何高亮库。页面仍然由服务端动态渲染,但 HTML 可以压缩、缓存,这笔交易依然非常划算。
一行代码的三种状态
行内代码、代码块、以及代码块里的复制按钮,需要分开设计:
.prose code {
padding: 0.18em 0.4em;
border: 1px solid var(--line);
border-radius: 5px;
background: color-mix(in srgb, var(--surface-3) 55%, transparent);
color: var(--sakura-soft);
font-family: var(--font-mono);
font-size: 0.84em;
}
.prose pre {
position: relative;
padding: 22px;
border: 1px solid var(--line);
border-radius: 12px;
background: #0a0712 !important;
overflow-x: auto;
}
!important 在这里是必要的,因为 Shiki 会把背景色写成内联样式。把代码块统一压到比页面更深的墨色,相当于给代码挖出一块安静的凹槽。
复制按钮由一小段脚本注入:
document.querySelectorAll('.prose pre').forEach((pre) => {
const button = document.createElement('button');
button.className = 'code-copy';
button.type = 'button';
button.textContent = 'COPY';
button.addEventListener('click', async () => {
await navigator.clipboard.writeText(pre.innerText);
button.textContent = 'COPIED ✿';
button.classList.add('is-copied');
setTimeout(() => {
button.textContent = 'COPY';
button.classList.remove('is-copied');
}, 1800);
});
pre.appendChild(button);
});
按钮默认透明,悬停代码块时才浮现。它不应抢走代码的注意力,只需要在需要时被找到。
KaTeX:公式的排版应该像印刷品
Markdown 本身不理解数学。Astro 通过两个插件把这件事接上:
npm install remark-math rehype-katex katex
// astro.config.mjs
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
export default defineConfig({
markdown: {
remarkPlugins: [remarkMath],
rehypePlugins: [rehypeKatex],
},
});
还需要在全局样式里引入 KaTeX 的字体表:
import 'katex/dist/katex.min.css';
然后,行内公式写作 $E = mc^2$,独立公式用一对美元符号包住整段:
现在正文直接以 Markdown 形式保存。像 \frac{a}{b} 这样的公式可以自然出现,不再需要担心花括号被别的解析器抢走。
Markdown 与数学
KaTeX 只负责渲染公式;Markdown 只负责把内容组织成段落、列表和代码块。两层各司其职,公式里的花括号可以安心待着。
让公式融入夜色
KaTeX 默认的黑色文字在暗色背景上会显得突兀。最简单的做法是让它继承正文颜色:
.katex {
color: inherit;
font-size: 1.04em;
}
.katex-display {
overflow-x: auto;
padding-block: 0.5rem;
}
overflow-x: auto 很重要。移动端遇到超宽公式时,横向滚动比强行缩小或撑破布局都更体面。
代码与公式之间的节奏
技术文章真正的难点不是「能不能渲染」,而是阅读节奏。
我现在会刻意控制三件事:
- 代码块之前先用一句话说明「这段代码解决什么问题」。
- 长代码不超过 40 行;超过时拆成两个片段,中间用文字搭桥。
- 公式只保留能帮助理解的,不为炫技而推导。
一条私人的写作准则
如果一段代码或公式删掉之后,读者仍然能理解结论,那它大概不该出现在正文里,而应该放进折叠块或附录。
服务端渲染的美,是一种安静的美
Shiki 和 KaTeX 都选择了同一条路:把昂贵的工作留在服务端。
页面抵达浏览器时,代码已经是带颜色的 HTML,公式已经排好版。没有闪烁,没有布局偏移,也没有等待客户端脚本执行后才突然出现的公式。
这种美不太容易被察觉。它不像樱花粒子那样从屏幕上飘过,也不会在滚动时弹出来。它只是让每一行代码、每一个积分号,都安安静静地待在它该在的位置。
而一座庭院,本来就该是这样的。
MARGINALIA
留言0
路过的话,留下一枚脚印吧。
BE KIND, BLOOM SOFTLY.
还没有留言。做第一个在这里留下脚印的人吧。