我用 Rust 寫了一個 Static Site Generator 取代 Hugo

近來無事,用 Rust 從頭刻了一個 Static Site Generator——Jet

Static Site Generator 就是 Hugo、Hexo 這些可以將 Markdown 檔案轉成 HTML 的程式。因為很多網站(GitHub Pages、Netlify)可以讓你免費丟靜態的 HTML,所以不少人用 Hugo 或 Hexo 寫部落格,多數中文獨立部落格是用 SSG 生成的。

之前這個部落格就是用 Hugo 架的。那為什麼我要自己從頭造輪子?有現成的 Hugo、Hexo 不好嗎?

為什麼我放棄 Hugo

容易和其他網站撞主題

因為常見的 theme 就那些,所以多數人都是從最常見的 theme 挑選。也因此很容易遇到和別人的部落格撞主題的情況。舉之前使用的 theme Stack 爲例,我能在網路上找到超過三個使用 Stack 的部落格。雖然和別人撞主題不是啥大不了的事,但既然都開自己的部落格了,我認爲還是用個獨特的 theme 比較好。

那為什麼不從頭寫一個 theme 呢? Hugo 不是也可以自定義 theme 嗎?

Hugo theme 不好寫

儘管 Hugo 還算易用,但當我想自定義一些東西時,我總無從下手——尤其是 theme。

Hugo 的 template 檔案複雜,複雜到我不太想花時間去理解。一個 theme 底下有幾十個模板檔案是常態,而且那些別人寫好的主題也不好更改。與其花大量時間學習寫 theme,我還不如重頭刻一個 blog 更省時間。

完全掌握自己網站

雖然 Hugo 是 open source project,未來也不太可能出什麼大問題,但我還是不想用 Hugo。因為我無法理解 Hugo 背後的技術原理,也無法照著那些檔案復刻出一個 Hugo。如果某天我沒辦法用 Hugo,我就要重新想個方法來生成部落格。

那為什麼不一開始就自己寫一個呢?抱著這樣的想法,我開始寫 SSG。

程式語言選擇

一個好的語言就是成功的一半,選對的語言能省掉很多不必要的麻煩。

以下是一些我認爲必須滿足的條件。

該滿足的條件

有一個好的 package manager & ecosystem

可以編譯成 executable file

資料多

最終選擇——Rust

考慮到以上因素,我選擇了 Rust,而非其他我會的語言——Python、C++、TypeScript 或 Racket。

Python 第一、三點滿足,但 Python 編譯成單一 executable file 很麻煩。我知道有現成方案能解決,但那些方案坑也不少,還不如不用。

C++ 第二、三點滿足,但 C++ 沒有一個稱得上好用的 package manager (也有可能我沒研究過),而且我也不是很想寫太噁的語言

TypeScript 和 Python 原因一樣,我不確定如何編譯成單一 executable file。

Racket 算一個意外還不錯的選項,但它的 ecosystem 真的很差——雖然理論上寫得了,但我實在不想賭運氣。也許哪天我會用 Racket 重寫吧。

這下可好了——我熟悉的語言沒有一個符合條件。但經過一番調查後,我最後選擇了 Rust,一個我原本很抗拒的語言。縱使我不喜歡 Rust 的一些設計,Rust 完全符合我的需求——Cargo 可能是目前市面上數一數二好的 package manager,Rust 的整體生態也不錯;Rust 可以直接 run,也可以編譯成單一檔案(甚至可以一行指令直接安裝在系統上);Rust 社區的 docs 非常詳細,我隨時能找到需要的資料。

雖然我幾乎沒寫過 Rust,但我毅然決然開始學 Rust 來開發 SSG。

如何從頭寫一個 SSG?

Static Site Generator 原理

  1. 把 Markdown 檔案轉成 HTML。
  2. 把轉換完的 HTML 塞到模板檔案的 <body>,生成完整的 HTML。
  3. 建立輸出的資料夾,並把完整的 HTML 寫入檔案。

需要的 Library

開發過程

Prototype

專案架構

├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
└── src
    ├── commands.rs
    ├── generate.rs
    └── main.rs

使用的 library

成果

我大概花了三天寫出了 prototype,能做到:

在 local 預覽效果

Hugo 有提供 hugo server 可以在 local 預覽網站。我完成 prototype 後覺得這功能不錯,所以又花了點時間寫這功能。

爲了實現這功能,我需要一個 web framework(其實能跑 http server 就行)。問了一下 GPT 後,我選擇使用 axum

開發過程非常順利,一個早上就寫完功能了。axum 設計不複雜,和一般的 framework 相差無幾。

設定輸出資料夾

Hugo 有個很煩的問題:沒辦法指定最後輸出的資料夾。我用 Git 作版控時需要這個功能(因爲這樣對 Git 比較友好,不會出現同一個 project 下有兩個 Git repo 的狀況),所以這是另外一個我想實現的功能。

只要用 std::fs 就能完成功能了。

RSS

原本我沒打算加 RSS——畢竟我平常也不太用 RSS reader,但考量如果我要把這裏加到 中文独立博客列表 ,這部落格還是需要 RSS。

於是我又加了兩個 library rsstomlrss 用來處理 RSS,toml 則負責讀取 blog 的 config file。

寫的過程順便重構了一下程式。

目前實現的功能