包包baobaolin.com
date
entry
021
topic
tooling
rev
1

build 設定裡不要放一份檔案清單

多頁靜態站的打包工具要你列出每一個 HTML 進入點。那份清單是人維護的,而漏改它不會報錯——只會讓那一頁安靜地不出現在輸出裡。

Vite(底下是 Rollup)的多頁設定,文件給的寫法是這樣:

build: {
  rollupOptions: {
    input: {
      main:  resolve(__dirname, "index.html"),
      about: resolve(__dirname, "about/index.html"),
      post1: resolve(__dirname, "posts/foo/index.html"),
      post2: resolve(__dirname, "posts/bar/index.html"),
      // 每寫一篇文章加一行
    }
  }
}

這在三頁的時候完全合理。到二十頁的時候,它變成一個每次新增內容都要同步更新的清單——而這種東西的失效方式都一樣:漏掉不會有錯誤訊息。build 成功,只是少了一頁。

更糟的是本機 vite dev 通常照樣看得到那頁(dev server 直接讀檔案系統,不看 input 清單),所以你在開發時完全正常,只有正式輸出裡沒有它。

讓檔案系統當事實來源

進入點的資訊本來就在目錄結構裡。與其抄一份到設定檔,不如 build 時直接去讀:

function collectHtmlInputs() {
  const inputs = { main: resolve(root, "index.html") };

  const notFound = resolve(root, "404.html");
  if (existsSync(notFound)) inputs["not-found"] = notFound;

  ["", "en"].forEach((locale) => {
    const base = locale ? resolve(root, locale) : root;
    const prefix = locale ? `${locale}-` : "";

    if (locale) {
      const home = resolve(base, "index.html");
      if (existsSync(home)) inputs[`${prefix}home`] = home;
    }

    const about = resolve(base, "about", "index.html");
    if (existsSync(about)) inputs[`${prefix}about`] = about;

    const postsRoot = resolve(base, "posts");
    if (existsSync(postsRoot)) {
      readdirSync(postsRoot, { withFileTypes: true })
        .filter((e) => e.isDirectory())
        .forEach((e) => {
          const html = resolve(postsRoot, e.name, "index.html");
          if (existsSync(html)) inputs[`${prefix}post-${e.name}`] = html;
        });
    }
  });

  return inputs;
}

新增一篇文章從此只要建一個目錄、放一個 index.html設定檔不用動。

三個實作細節

key 要唯一且穩定。中英兩份文章的 slug 相同,所以 key 要加語言前綴(post-fooen-post-foo)。撞名的話後寫的會蓋掉前一個,而 Rollup 不會抱怨——又是一個安靜的錯。

existsSync 而不是假設。掃到一個目錄不代表裡面一定有 index.html;少了這個判斷,一個空目錄就會讓整個 build 掛掉,錯誤訊息還是 Rollup 那種不容易對應回來源的形式。

語言用迴圈跑,不要複製貼上。上面那個 ["", "en"] 的迴圈,未來加第三種語言時只要改陣列。這跟雙語站的其他同步點是同一個考量:能被複製的結構遲早會兩邊不一致。

邊界:什麼時候不該掃

自動掃描的風險是掃到不該發布的東西——寫到一半的草稿、範本檔、備份。

解法是用目錄約定,不是用黑名單。草稿放在 drafts/(不在掃描範圍內),完成才移進 posts/。這比在掃描邏輯裡維護一串排除規則好——排除清單本身又是一份人維護的清單,等於把問題搬了個位置。

判斷方式很簡單:「要發布」這件事,應該由檔案的位置表達,而不是由設定裡的一行表達。

同樣的原則適用在靜態資產。這個站的 build 有一個小外掛,負責把 assets/ 整包複製到輸出目錄、並把英文版的 feed 放到 dist/en/feed.xml——那也是規則,不是清單。加一張圖不用改任何設定。

剩下的手工同步點

這個改動把「發一篇文章要動的地方」從八處減到七處,之後 sitemap 也改成自動產生,再減為六處。剩下的六處是中英文章、中英列表、兩份 feed。

理想上這些都該由同一個來源產生——把文章的中繼資料抽出來,列表、feed、sitemap 全部用它生成。我還沒做,因為現在的量還在手工加一段檢查腳本就能守住的範圍內。

但方向是明確的:每一個需要人記得同步的地方,都是一個未來會漏掉的點。能減就減,減不掉的就用檢查補上——這兩件事的順序不要顛倒,先把能自動化的自動化,剩下的才值得寫檢查。

如果只記得一件事

設定檔應該描述規則,不應該列舉檔案。 看到設定裡有一份會隨內容成長的清單,那就是一個等著出錯的地方——因為它的正確性靠的是有人記得,而記得從來不是一個可靠的機制

修訂紀錄

  1. sitemap 亦改為自動產生,剩餘同步點更新為六處
  2. 首次發布