🧩 插件应用

自研小插件:给 Markdown 加目录、锚点与代码复制

写作时不该为了目录而手写 HTML。思路是:Markdown 照常写,渲染之后统一做后处理。

第一步:渲染 HTML#

ts
import { marked } from 'marked';

const html = marked.parse(markdown, { async: false }) as string;

第二步:给 h2/h3 注入锚点并顺手建目录#

ts
const toc: TocItem[] = [];
const counter = new Map<string, number>();

const withIds = html.replace(/<h([23])(\s[^>]*)?>([\s\S]*?)<\/h\1>/g, (m, level, attrs, inner) => {
  const plain = inner.replace(/<[^>]+>/g, '').trim();
  const base = headingId(plain);
  const used = counter.get(base) ?? 0;
  counter.set(base, used + 1);
  const id = used === 0 ? base : `${base}-${used + 1}`;
  toc.push({ id, text: plain, level: Number(level) });
  return `<h${level} id="${id}">${inner}<a class="heading-anchor" href="#${id}">#</a></h${level}>`;
});

中文标题直接进 id 也没问题,浏览器会自动做百分号编码。重复标题必须去重,否则第二个同名小节的锚点会跳错位置。

第三步:代码高亮 + 复制按钮#

ts
const highlighted = hljs.getLanguage(lang)
  ? hljs.highlight(raw, { language: lang }).value
  : hljs.highlightAuto(raw).value;

return `<figure class="code-block" data-lang="${lang}">
  <figcaption><span>${lang}</span><button data-copy>复制</button></figcaption>
  <pre><code class="hljs">${highlighted}</code></pre>
</figure>`;

复制按钮不用在渲染时绑定事件,交给事件委托即可:

ts
root.addEventListener('click', (event) => {
  const btn = (event.target as HTMLElement).closest('[data-copy]');
  if (!btn) return;
  const code = btn.closest('.code-block')?.querySelector('code');
  navigator.clipboard.writeText(code?.textContent ?? '');
});

这样服务端渲染的 HTML 不需要任何改造,客户端只加一层增强。

效果与边界#

  • ✅ 目录、锚点、高亮、复制、灯箱,全部零写作成本;
  • ✅ 与静态渲染兼容,不依赖客户端 JS 也能读到正文;
  • ⚠️ 后处理用的是正则,别在标题里写嵌套的复杂 HTML,否则锚点文本会带上标签残留。

更严格的做法是自定义 marked 渲染器,但正则后处理的好处是:升级 marked 大版本时基本不受影响。

付费下载 · ¥39.00

付款后立即开通下载链接(30 天有效、可下载 5 次)。在微信中打开本页可直接支付,在电脑上则显示扫码支付。

客服微信二维码微信扫码加客服

评论

正在加载评论…

评论保存在本站 data/comments.json,不经过第三方。