<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>林一谡のblog</title><description>记录技术、生活和一些想法</description><link>https://linyisu.github.io/</link><language>zh_CN</language><item><title>Expressive Code：让代码块成为文章的一部分</title><link>https://linyisu.github.io/posts/expressive-code-in-practice/</link><guid isPermaLink="true">https://linyisu.github.io/posts/expressive-code-in-practice/</guid><description>从语法高亮、编辑器窗口、终端窗口、行标记、折叠代码到 Astro 集成，系统介绍 Expressive Code 适合技术博客的写作方式。</description><pubDate>Thu, 21 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;写技术文章时，代码块很容易变成一整片“黑盒”：读者知道这是一段代码，但不一定知道应该看哪一行、为什么这一行重要、哪些内容是新增的、哪些只是为了让示例能跑起来的样板代码。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/&quot;&gt;Expressive Code&lt;/a&gt; 解决的就是这个问题。它不是简单地给代码上色，而是把代码块当成文档中的讲解单元来渲染：可以像 VS Code 一样显示文件标签，可以像终端一样展示命令，可以标记新增和删除的行，可以折叠无关代码，还能在长代码行上自动换行。&lt;/p&gt;
&lt;p&gt;这篇文章会从写博客的角度介绍 Expressive Code：它是什么、适合解决什么问题、在 Markdown 里怎么用，以及怎样把它用得克制而有效。&lt;/p&gt;
&lt;h2&gt;Expressive Code 是什么&lt;/h2&gt;
&lt;p&gt;Expressive Code 是一个用于在网页上展示代码的渲染工具。它可以被集成进 Astro、Starlight、Next.js，也可以作为 rehype 插件进入其他 Markdown / MDX 流程。&lt;/p&gt;
&lt;p&gt;它的几个核心特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 Shiki 做语法高亮，底层接近 VS Code 的语法着色效果。&lt;/li&gt;
&lt;li&gt;支持编辑器窗口和终端窗口，让代码块有更明确的上下文。&lt;/li&gt;
&lt;li&gt;支持行标记、文本标记和 diff 风格标记，适合解释“改了哪里”。&lt;/li&gt;
&lt;li&gt;支持自动换行、折叠代码、行号、复制按钮和多主题。&lt;/li&gt;
&lt;li&gt;不绑定 React、Vue 或 Svelte 这类前端框架，适合静态站点生成器。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对博客来说，它最大的价值不是“好看”，而是减少解释成本。你可以把读者的注意力直接引到关键代码上。&lt;/p&gt;
&lt;h2&gt;在 Astro 里安装&lt;/h2&gt;
&lt;p&gt;如果是一个普通 Astro 项目，官方推荐用 Astro CLI 添加集成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm astro add astro-expressive-code
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你的这个 Fuwari 博客已经接好了 &lt;code&gt;astro-expressive-code&lt;/code&gt;，并且额外启用了行号插件和折叠代码插件，所以后面的示例可以直接写进 Markdown 文章里。&lt;/p&gt;
&lt;p&gt;当前项目里的核心配置大概是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import expressiveCode from &quot;astro-expressive-code&quot;;
import { pluginCollapsibleSections } from &quot;@expressive-code/plugin-collapsible-sections&quot;;
import { pluginLineNumbers } from &quot;@expressive-code/plugin-line-numbers&quot;;

export default defineConfig({
  integrations: [
    expressiveCode({
      themes: [&quot;github-dark&quot;, &quot;github-dark&quot;],
      plugins: [
        pluginCollapsibleSections(),
        pluginLineNumbers(),
      ],
      defaultProps: {
        wrap: true,
      },
    }),
  ],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段配置说明了三件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码块使用 &lt;code&gt;github-dark&lt;/code&gt; 主题。&lt;/li&gt;
&lt;li&gt;启用了可折叠区块和行号能力。&lt;/li&gt;
&lt;li&gt;默认开启 &lt;code&gt;wrap&lt;/code&gt;，长代码行会自动换行，更适合移动端阅读。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;最基础：语言标识和语法高亮&lt;/h2&gt;
&lt;p&gt;最简单的用法就是给 Markdown 代码块加语言名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;```ts
const site = {
  title: &quot;林一谡のblog&quot;,
  lang: &quot;zh_CN&quot;,
};
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;渲染出来就是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const site = {
  title: &quot;林一谡のblog&quot;,
  lang: &quot;zh_CN&quot;,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expressive Code 默认用 Shiki 进行高亮，常见的 JavaScript、TypeScript、HTML、CSS、Astro、Markdown、MDX、JSON、YAML 都能直接识别。写博客时，最容易忽略的反而是语言名本身：不要偷懒写成普通三引号，能写 &lt;code&gt;ts&lt;/code&gt; 就写 &lt;code&gt;ts&lt;/code&gt;，能写 &lt;code&gt;astro&lt;/code&gt; 就写 &lt;code&gt;astro&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;编辑器窗口：告诉读者这是哪个文件&lt;/h2&gt;
&lt;p&gt;技术文章经常要展示“把这段代码放到哪里”。如果只给一段代码，读者还要从上下文猜文件名。Expressive Code 支持在代码块 meta 里加 &lt;code&gt;title&lt;/code&gt;，渲染时会变成类似编辑器标签的标题。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
const name = &quot;linyisu&quot;;
---

&amp;lt;main&amp;gt;
  &amp;lt;h1&amp;gt;Hello, {name}&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;这个代码块带有文件名标题。&amp;lt;/p&amp;gt;
&amp;lt;/main&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这比在正文里反复写“打开 &lt;code&gt;src/pages/hello.astro&lt;/code&gt;，然后加入下面的代码”更自然。读者先看到文件名，再看代码，认知负担会低很多。&lt;/p&gt;
&lt;p&gt;除了 &lt;code&gt;title&lt;/code&gt;，Expressive Code 也支持从代码前几行的文件名注释中提取标题。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* src/styles/card.css */
.card {
  border-radius: 8px;
  background: var(--card-bg);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种写法适合你从真实文件里复制代码时保留路径说明。&lt;/p&gt;
&lt;h2&gt;终端窗口：命令和源码分开表达&lt;/h2&gt;
&lt;p&gt;命令行不是源码。它通常代表“要在终端执行的操作”。Expressive Code 会把 &lt;code&gt;bash&lt;/code&gt;、&lt;code&gt;sh&lt;/code&gt;、&lt;code&gt;shellsession&lt;/code&gt;、&lt;code&gt;powershell&lt;/code&gt; 这类语言识别成终端窗口。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm install
pnpm build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在博客里，我建议把终端命令和配置代码分开写：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;命令用 &lt;code&gt;shellsession&lt;/code&gt;、&lt;code&gt;bash&lt;/code&gt; 或 &lt;code&gt;powershell&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;文件内容用 &lt;code&gt;ts&lt;/code&gt;、&lt;code&gt;js&lt;/code&gt;、&lt;code&gt;astro&lt;/code&gt;、&lt;code&gt;css&lt;/code&gt;、&lt;code&gt;json&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;输出日志只截关键部分，不要整段复制几百行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样读者一眼就知道：这是要执行的命令，不是要粘贴进文件的代码。&lt;/p&gt;
&lt;h2&gt;行标记：把重点放到关键行&lt;/h2&gt;
&lt;p&gt;技术文章最怕一句“重点看这里”，然后下面贴了 40 行代码。Expressive Code 可以用 &lt;code&gt;{}&lt;/code&gt; 标记行号或行范围。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;```ts title=&quot;src/config.ts&quot; {3,6-8}
export const siteConfig = {
  title: &quot;林一谡のblog&quot;,
  lang: &quot;zh_CN&quot;,
  themeColor: {
    hue: 290,
    fixed: false,
  },
};
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;渲染效果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const siteConfig = {
  title: &quot;林一谡のblog&quot;,
  lang: &quot;zh_CN&quot;,
  themeColor: {
    hue: 290,
    fixed: false,
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;行号从 1 开始，可以写单行，也可以写范围：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{4}
{4,8,12}
{4-8}
{1,4,7-8}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写教程时，行标记比正文解释更直接。正文负责解释“为什么”，高亮负责回答“哪里”。&lt;/p&gt;
&lt;h2&gt;新增和删除：用 &lt;code&gt;ins&lt;/code&gt; / &lt;code&gt;del&lt;/code&gt; 解释修改&lt;/h2&gt;
&lt;p&gt;如果你在写“修改前后”的教程，普通高亮不够，因为它没有语义。Expressive Code 支持 &lt;code&gt;ins&lt;/code&gt; 和 &lt;code&gt;del&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const theme = {
  hue: 250,
  hue: 290,
  fixed: false,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码表达的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第 2 行是删除内容。&lt;/li&gt;
&lt;li&gt;第 3 到 4 行是新增内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你也可以用 diff 风格写法，它更接近 GitHub 上看 patch 的体验：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  const theme = {
-   hue: 250,
+   hue: 290,
    fixed: false,
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意这里用了 &lt;code&gt;diff lang=&quot;ts&quot;&lt;/code&gt;：外层用 diff 语义表达增删，内层仍然按 TypeScript 高亮。写版本迁移、配置修改、bug 修复时，这种写法非常清楚。&lt;/p&gt;
&lt;h2&gt;文本标记：高亮一个词，而不是整行&lt;/h2&gt;
&lt;p&gt;有时候你不需要高亮整行，只需要指出某个参数或方法名。Expressive Code 支持在 meta 中写字符串或正则表达式，匹配代码块内部的文本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type SearchParams = {
  query: string;
  limit: number;
  offset: number;
};

function search(params: SearchParams) {
  return fetch(`/api/search?q=${params.query}`);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;字符串适合高亮固定词，正则适合高亮一组相关名称。我的建议是：文本标记只用于真正需要对比的词，不要把一段代码标得太花，否则读者会失去视觉锚点。&lt;/p&gt;
&lt;h2&gt;折叠代码：隐藏样板，保留重点&lt;/h2&gt;
&lt;p&gt;复杂示例经常需要 import、类型定义、初始化代码。如果全部展示，真正要讲的几行会被淹没。这个项目已经启用了 &lt;code&gt;@expressive-code/plugin-collapsible-sections&lt;/code&gt;，可以用 &lt;code&gt;collapse={X-Y}&lt;/code&gt; 折叠不重要的行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createServer } from &quot;node:http&quot;;
import { readFile } from &quot;node:fs/promises&quot;;
import path from &quot;node:path&quot;;

type RenderResult = {
  html: string;
};

async function renderPost(slug: string): Promise&amp;lt;RenderResult&amp;gt; {
  const file = path.join(&quot;src/content/posts&quot;, `${slug}.md`);
  const markdown = await readFile(file, &quot;utf-8&quot;);

  return {
    html: markdown.toUpperCase(),
  };
}

const server = createServer(async (_req, res) =&amp;gt; {
  const result = await renderPost(&quot;hello&quot;);
  res.end(result.html);
});

server.listen(3000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;折叠代码的关键是“让示例仍然完整”。读者默认看到重点，但如果想理解上下文，也能展开被折叠的部分。&lt;/p&gt;
&lt;h2&gt;自动换行：让代码块适配移动端&lt;/h2&gt;
&lt;p&gt;很多技术博客在桌面端看起来很好，到了手机上就变成横向滚动。Expressive Code 支持 &lt;code&gt;wrap&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const url = &quot;https://example.com/api/search?query=expressive-code&amp;amp;category=markdown&amp;amp;sort=updated-desc&amp;amp;includeDrafts=false&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个博客的配置已经把 &lt;code&gt;wrap&lt;/code&gt; 设成默认值，所以大部分代码块不需要单独写。你仍然可以在特殊场景关闭它：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;```ts wrap=false
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我一般只在两种情况下关闭换行：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;展示终端输出时，原始对齐比适配宽度更重要。&lt;/li&gt;
&lt;li&gt;展示表格、ASCII 图、固定宽度内容时，换行会破坏结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;行号：适合讨论长代码&lt;/h2&gt;
&lt;p&gt;行号不是每个代码块都必须要有，但它在两种文章里特别有用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分析源码或配置文件。&lt;/li&gt;
&lt;li&gt;在正文中引用“第几行”的行为。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个博客已经接入了行号插件。需要从某个数字开始时，可以用 &lt;code&gt;startLineNumber&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const siteConfig = {
  title: &quot;林一谡のblog&quot;,
  subtitle: &quot;记录技术、生活和一些想法&quot;,
  lang: &quot;zh_CN&quot;,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意：&lt;code&gt;startLineNumber&lt;/code&gt; 只是视觉上的起始数字。高亮行时，&lt;code&gt;{}&lt;/code&gt; 里仍然按代码块内部的实际行数计算。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;&amp;lt;Code&amp;gt;&lt;/code&gt; 组件：当代码来自变量或文件&lt;/h2&gt;
&lt;p&gt;在普通 Markdown 里，代码块已经够用。但如果你写的是 MDX 或 Astro 页面，Expressive Code 还提供 &lt;code&gt;&amp;lt;Code&amp;gt;&lt;/code&gt; 组件。它可以从变量、接口返回值或真实文件中读取代码，再渲染成同样风格的代码块。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
import { Code } from &quot;astro-expressive-code/components&quot;;

const code = `console.log(&quot;Hello from dynamic code&quot;);`;
---

&amp;lt;Code code={code} lang=&quot;js&quot; title=&quot;dynamic.js&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;官方文档还展示了配合 Vite 的 &lt;code&gt;?raw&lt;/code&gt; 导入真实文件的方式。这个能力很适合写项目文档：示例代码来自仓库里的真实文件，文件改了，文档里的代码也跟着变。&lt;/p&gt;
&lt;h2&gt;写作建议&lt;/h2&gt;
&lt;p&gt;Expressive Code 的功能很多，但技术写作不应该为了展示功能而展示功能。我自己的使用规则会是这样：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个代码块都写语言名。&lt;/li&gt;
&lt;li&gt;涉及文件修改时，尽量加 &lt;code&gt;title&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;只高亮正在讲的行，不要把半个代码块都标出来。&lt;/li&gt;
&lt;li&gt;写改动时优先用 &lt;code&gt;ins&lt;/code&gt; / &lt;code&gt;del&lt;/code&gt; 或 &lt;code&gt;diff lang=&quot;...&quot;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;长代码先折叠样板，再解释关键逻辑。&lt;/li&gt;
&lt;li&gt;终端命令和源码分开，不要混在一个代码块里。&lt;/li&gt;
&lt;li&gt;能短就短，代码块不是越完整越好，而是越能支撑上下文越好。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;适合哪些文章&lt;/h2&gt;
&lt;p&gt;Expressive Code 特别适合这些内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;框架配置教程，比如 Astro、Vite、Next.js、Tailwind。&lt;/li&gt;
&lt;li&gt;代码迁移记录，比如从旧 API 切到新 API。&lt;/li&gt;
&lt;li&gt;Debug 复盘，比如展示错误配置和修复后的配置。&lt;/li&gt;
&lt;li&gt;源码阅读，比如一边贴代码一边解释关键分支。&lt;/li&gt;
&lt;li&gt;命令行工具介绍，比如展示安装、构建、发布流程。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它不太适合的场景也很明确：如果一段代码只有两三行，而且没有文件名、差异、重点行，那普通代码块就足够了。工具应该服务文章，而不是抢走文章的注意力。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;Expressive Code 让我最喜欢的一点是，它把“代码块”变成了“可讲解的代码块”。语法高亮只是基础，真正有价值的是标题、终端框、行标记、diff、折叠和换行这些辅助叙事的能力。&lt;/p&gt;
&lt;p&gt;如果你写技术博客，尤其经常写教程、配置说明、源码分析，它会明显改善读者体验。读者不需要在一大段代码里猜重点，你可以直接把重点标出来，让代码和正文互相配合。&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://expressive-code.com/&quot;&gt;Expressive Code 官方网站&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://expressive-code.com/installation/&quot;&gt;Installing Expressive Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://expressive-code.com/key-features/syntax-highlighting/&quot;&gt;Syntax Highlighting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://expressive-code.com/key-features/frames/&quot;&gt;Editor &amp;amp; Terminal Frames&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://expressive-code.com/key-features/text-markers/&quot;&gt;Text &amp;amp; Line Markers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://expressive-code.com/key-features/word-wrap/&quot;&gt;Word Wrap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://expressive-code.com/plugins/collapsible-sections/&quot;&gt;Collapsible Sections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://expressive-code.com/plugins/line-numbers/&quot;&gt;Line Numbers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://expressive-code.com/key-features/code-component/&quot;&gt;Code Component&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://expressive-code.com/reference/configuration/&quot;&gt;Configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item></channel></rss>