Markdown 目錄(TOC)完整教學:手動建立與自動產生方法

寫長文件的時候,有個目錄(Table of Contents,簡稱 TOC)能讓讀者快速跳轉到感興趣的章節。Markdown 本身沒有專門產生目錄的語法,但透過幾種方法完全可以實現,而且有些方法比想像中簡單。

Markdown 目錄的基本原理

Markdown 目錄的本質就是一組內部連結(也叫錨點連結),每個連結指向頁面內的某個標題。渲染器會把你的標題自動轉換成 HTML 錨點,你只需要用連結語法指向它就行了。

基本格式是這樣的:

## 目錄

- [簡介](#簡介)
- [安裝](#安裝)
- [使用方法](#使用方法)
- [常見問題](#常見問題)

## 簡介

這裡是簡介的內容...

## 安裝

安裝步驟...

關鍵在於理解標題怎麼變成錨點——不同平台的規則不一樣,搞清楚這個,手動寫目錄就不難了。

不同平台的錨點產生規則

這是很多人容易忽略的地方:同一個標題,在 GitHub、GitLab、Bitbucket 上產生的錨點可能完全不同。我之前幫一個專案寫 README 的時候,手動寫了目錄在本地的 GitLab 上顯示正常,推到 GitHub 後目錄全部失效了,排查了好一陣才發現是錨點規則不一樣。

GitHub 錨點規則

GitHub 的規則最常用,也比較直觀:

規則範例
全部轉小寫## Hello World#hello-world
空格變短橫線## Getting Started#getting-started
去掉標點符號## What's New?#whats-new
連續短橫線合併## Cost ($) Analysis#cost--analysis
重複標題加編號第二個 ## Example#example-1

GitLab 錨點規則

GitLab 的規則和 GitHub 大體相似,但有個關鍵區別——GitLab 支援用 [[_TOC_]] 標籤直接自動產生目錄,不需要手動寫:

## 文件標題

[[_TOC_]]

## 第一節
內容...

## 第二節
內容...

[[_TOC_]] 會根據頁面的所有標題自動產生一個可點擊的目錄。這個功能從 GitLab 14.x 開始支援,寫 wiki 和 README 都能用。

Bitbucket 錨點規則

Bitbucket 比較特殊,它會給所有錨點加一個 markdown-header- 前綴:

- [簡介](#markdown-header-簡介)
- [安裝步驟](#markdown-header-安裝步驟)

如果你的文件只在 Bitbucket 上用,記得加上這個前綴。

中文標題的處理

中文標題在 GitHub 和 GitLab 上一般直接使用 URL 編碼後的形式。不過實測下來,直接在連結裡寫中文也能跳轉:

## 目錄

- [快速開始](#快速開始)
- [設定說明](#設定說明)

## 快速開始

## 設定說明

大部分現代 Markdown 渲染器都支援中文錨點直接跳轉。但如果你發現某些平台不支援,可以用 HTML 的 <a> 標籤手動加錨點:

<a name="快速開始"></a>

## 快速開始

然後在目錄裡連結到這個錨點:

- [快速開始](#快速開始)

手動建立 Markdown 目錄

如果你文件不長,或者標題很少變動,手動寫目錄是最直接的方式。

基本步驟

  1. 先把文件的所有標題寫好
  2. 在文件開頭(或你想放目錄的位置)新增一個「目錄」章節
  3. 用列表 + 連結語法,逐個指向各個標題

## 目錄

- [功能介紹](#功能介紹)
- [安裝](#安裝)
  - [npm 安裝](#npm-安裝)
  - [手動安裝](#手動安裝)
- [使用](#使用)
- [授權條款](#授權條款)

## 功能介紹

...

## 安裝

...

### npm 安裝

...

### 手動安裝

...

## 使用

...

## 授權條款

...

巢狀的標題用縮排來表示層級關係,這樣目錄結構和文件結構就能對上。

說實話,手動寫目錄的問題不在於寫,而在於維護——每次改了標題,都得記得去目錄裡同步更新。所以對於經常變動的長文件,我更建議用自動產生工具。

使用 VS Code 擴充功能自動產生目錄

如果你用 VS Code 寫 Markdown(很多人都是),裝個擴充功能就能一鍵產生目錄,而且還能在儲存時自動更新。

Markdown All in One

這是最推薦的 VS Code Markdown 擴充功能,由 Yu Zhang 開發,在 VS Code Marketplace 上安裝量超過 600 萬。

安裝和使用:

  1. 在 VS Code 擴充功能市集搜尋 Markdown All in One 並安裝
  2. 開啟你的 .md 檔案,把游標放在想插入目錄的位置
  3. Ctrl+Shift+P 開啟命令面板,輸入 Markdown: Create Table of Contents
  4. 目錄就自動產生了

產生出來的目錄就是普通的 Markdown 列表,格式和手動寫的一模一樣,所以放到 GitHub、GitLab 上都能正常顯示。

自動更新功能:

預設情況下,每次儲存檔案時擴充功能會自動更新目錄。如果你不想自動更新(比如目錄位置有自訂內容),可以在 VS Code 設定裡關掉:

{
  "markdown.extension.toc.updateOnSave": false
}

我用了這個擴充功能快兩年了,總體很順手。有一點要注意:如果你文件裡有 HTML 標籤混著 Markdown 標題,擴充功能有時候識別會出偏差,這種情況需要手動檢查一下產生的目錄。

Auto Markdown TOC

另一個選擇是 Auto Markdown TOC 擴充功能(作者 huntertran),功能類似但更輕量。安裝後右鍵選單裡會出現 "Markdown TOC" 選項,操作也很直觀。

命令列工具自動產生 TOC

如果你需要在 CI/CD 環境裡批次處理,或者不想依賴特定編輯器,命令列工具是更好的選擇。

doctoc — 最流行的 TOC 產生工具

doctoc 是 Node.js 寫的工具,GitHub 上超過 4000 star,是最廣泛使用的 Markdown TOC 產生器。

# 安裝
npm install -g doctoc

# 為目前目錄下所有 Markdown 檔案產生 TOC
doctoc .

# 只處理單一檔案
doctoc README.md

doctoc 會在檔案中插入特殊的註解標記,之後再次執行 doctoc 時會自動更新已有的 TOC,不會重複插入:

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**

- [簡介](#簡介)
- [安裝](#安裝)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

這個標記機制很聰明——你不該手動編輯 TOC 內容,改了標題直接重新跑一遍 doctoc 就行。

markdown-toc — 靈活的 Node.js 工具

另一個 Node.js 工具,由 jonschlinkert 開發,被 NASA、Prisma、Mocha、Prettier 等知名專案使用。

# 安裝
npm install -g markdown-toc

# 產生 TOC 並輸出到終端機
markdown-toc README.md

# 儲存到檔案
markdown-toc README.md > TOC.md

和 doctoc 不同,markdown-toc 預設輸出到標準輸出,你可以靈活決定怎麼使用它——貼到檔案裡也好,管線到其他工具也好。

gh-md-toc — 專為 GitHub 最佳化的 Shell 工具

如果你不想裝 Node.js,這個純 Shell 指令碼就夠用了。

# 下載(Linux)
wget https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc
chmod a+x gh-md-toc

# 下載(macOS)
curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc
chmod a+x gh-md-toc

# 使用
./gh-md-toc README.md

它還能處理遠端 GitHub 上的檔案,甚至 GitHub Wiki 頁面:

./gh-md-toc https://github.com/user/repo/blob/main/README.md

自動插入模式下,在檔案裡放上標記:

<!--ts-->
<!--te-->

然後執行 ./gh-md-toc --insert README.md,TOC 就會被插入到這兩個標記之間。之後每次改了標題,再跑一次指令就行。

md-toc — Python 使用者的工具

如果你更習慣 Python 生態系,md-toc 是一個功能完善的選擇:

pip install md-toc
md_toc --in-place github --header-levels 6 README.md

它支援 GitHub 和 GitLab 兩種錨點產生模式,還能控制顯示的標題層級。

線上 TOC 產生工具

不想裝任何工具的話,線上產生器最方便:

  • BitDownToc(bitdowntoc.derlin.ch):支援 GitHub、GitLab、dev.to、hashnode 等多種平台 profile,貼上 Markdown 內容就能產生對應平台的 TOC。開源免費。

工具對比:選哪個?

工具類型支援平台自動更新依賴
手動編寫全部
Markdown All in OneVS Code 擴充功能全部儲存時自動更新VS Code
Auto Markdown TOCVS Code 擴充功能全部手動觸發VS Code
doctocCLIGitHub/GitLab重新執行即更新Node.js
markdown-tocCLIGitHub輸出到 stdoutNode.js
gh-md-tocShell 指令碼GitHub標記間自動更新curl/wget
md-tocCLIGitHub/GitLab標記間自動更新Python
BitDownToc線上工具多平台手動複製瀏覽器
GitLab [[_TOC_]]平台原生僅 GitLab自動

選工具的建議:

  • 寫 GitHub README,用 VS Code 擴充功能或者 doctoc 就夠了
  • 只用 GitLab,直接用 [[_TOC_]],最省事
  • 需要在 CI 裡自動化,用命令列工具配合指令碼
  • 偶爾用一次,線上工具最方便

用 GitHub Actions 自動維護 TOC

如果你有一個文件儲存庫,不想每次改標題後手動跑工具更新 TOC,可以用 GitHub Actions 實現全自動化。

下面這個設定會在你推送 Markdown 檔案時自動更新 TOC:

name: Update TOC

on:
  push:
    branches: [main]
    paths: ['**/*.md']

jobs:
  toc:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v4
      - run: |
          curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc
          chmod a+x gh-md-toc
          ./gh-md-toc --insert --no-backup README.md
          rm gh-md-toc
      - uses: stefanzweifel/git-auto-commit-action@v4
        with:
          commit_message: "Auto update markdown TOC"

這樣每次你 push 程式碼,Actions 會自動檢查 Markdown 檔案的 TOC 是否需要更新,如果需要就自動 commit 新版本。

重複標題和特殊字元的處理

實際寫文件時,經常會遇到一些邊緣情況。

重複標題

如果一個文件裡有兩個相同的標題,GitHub 會自動給第二個加上編號:

## 範例

這是第一個範例...

## 範例

這是第二個範例...

對應的錨點分別是 #範例#範例-1。在目錄裡連結時要注意這點:

- [範例(第一個)](#範例)
- [範例(第二個)](#範例-1)

特殊字元

標題裡有 $()? 等特殊字元時,GitHub 會把它們去掉:

## 版本 2.0 (重大更新)

錨點是 #版本-20-重大更新,注意點號被保留了,但括號沒了。

重複標題在不同平台的表現

平台重複標題處理
GitHub第二個加 -1,第三個加 -2,以此類推
GitLab同樣加編號後綴
Bitbucket同樣加編號後綴,但前綴是 markdown-header-

如果不確定某個標題產生的錨點是什麼,GitHub 上有個小技巧:把滑鼠懸停在標題上,會出現一個連結圖示,點一下就能看到實際的錨點了。

常見問題

GitHub README 支援 [TOC] 標籤嗎?

不支援。GitHub 不認識 [TOC] 這個標記(不像一些 Markdown 編輯器那樣)。在 GitHub 上你只能用上面介紹的方法來建立目錄——要麼手動寫,要麼用工具產生。

為什麼我的目錄連結跳轉不到對應標題?

最常見的原因是錨點格式不對。檢查一下:

  1. 錨點是不是全小寫了
  2. 空格是不是換成了短橫線
  3. 有沒有多餘的標點符號沒去掉
  4. 是不是 Bitbucket 環境忘了加 markdown-header- 前綴

怎麼讓目錄只顯示特定層級的標題?

用 VS Code 的 Markdown All in One 擴充功能,可以在設定裡控制:

{
  "markdown.extension.toc.levels": "2..4"
}

這表示只顯示 H2 到 H4 的標題。用命令列工具的話,doctoc 用 --maxlevel 參數,md-toc 用 --header-levels 參數。

GitHub 自動產生的目錄和手寫的有什麼區別?

2021 年 4 月起,GitHub 在 Markdown 檔案的頭部自動顯示一個「大綱」(Outline)選單,列出所有標題。這個功能是 GitHub 前端自動產生的,不需要你在檔案裡寫任何東西。但它只出現在 GitHub 網頁上,不影響你的 Markdown 檔案內容。

所以說,如果你在 GitHub 上寫文件,檔案裡手動或工具產生的 TOC 是給所有場景用的(包括 clone 到本機看),GitHub 內建的 Outline 是只在網頁上多一層導航。

參考來源