我使用 Docusaurus 撰寫我所有的課程,這是一個我非常喜愛的工具:
- 所有內容都以 Markdown/MDX 撰寫,因此我可以輕鬆移植到其他的工具
- 很容易組織課程的章節,目錄會自動產生
- 由於外掛程式系統,我將搜尋引擎整合到課程中(我甚至測試過 Algolia,效果相當不錯)
- Docusaurus 基於 React,因此我可以建立元件,然後將它們整合到我的課程中:章節結尾測驗、整合程式碼編輯器…
- 自 3.6 版本起,使用 Rspack、SWC 和 Lightning CSS,建置時間已大幅縮短,這真感覺很棒
有一天,在課堂上,一位學生問我是否可以提供 PDF 版本的課程。
研究
為了回應他的問題,我做的第一件事是搜尋線上工具。
我以為 NPM 上會有一個工具可以將 Docusaurus 網站轉換為 PDF 或一組 PDF 檔案,而我只需要使用 npx 執行它即可。
在測試了兩個工具並經歷了兩次失敗後,我決定自己撰寫一個。
需求
我的需求如下:
- 從命令列使用工具
- 傳遞 URL,跟一個選填 PDF 檔案的輸出資料來
關於指令碼:
- 對網頁發出要求
- 分析網頁內容
- 從網頁產生 PDF
- 為每個選單項目(編號)產生 PDF
工具
Rust
我決定使用 Rust 來開發這個工具。這是一個我非常喜歡的語言,我認為它相當適合這個情況。
為了要求網頁,我使用了 chromiumoxide 函式庫,它可以啟動無頭瀏覽器、導覽到網頁並檢查網頁上的元素,然後將網頁匯出為 PDF。
多執行緒
我為每個選單項目產生一個 PDF 檔案。
我本來依序地產生 PDF 檔案。為了提高速度,我決定為每個頁面建立一個執行緒,而並行執行它們。對於有數百個網頁的網站,這可能無法很好地運作(可能需要調整執行緒建立的方法),但在我的情況下還沒有達到這個程度:它運作,而且運作得相當快。
結果
指令碼的階段如下:
- 從 chromiumoxide 取得無頭瀏覽器,並建立一個我將用於導覽的頁面(從文件的主頁開始)
- 收集章節(標籤和 URL)
- 在輸出資料夾中將每個章節產生為 PDF
#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
let start = Instant::now();
let args = Args::parse();
let (mut browser, handle) = browser::get_browser_and_handle().await?;
let base_url = util::get_base_url(&args.initial_docs_url);
let page = browser::get_new_page(&browser, true).await?;
page.goto(&args.initial_docs_url).await?;
println!("Collecting chapters...");
let main_side_menu = page.find_element(".theme-doc-sidebar-menu").await?;
let chapters = docusaurus::collect_chapters(&main_side_menu, None).await?;
println!("Chapters found: {:?}", chapters.len());
println!("Generating PDF files in {}...", args.output_dir);
fs::create_dir_all(&args.output_dir)?;
pdf::generate_pdfs(&chapters, &browser, &base_url, &args.output_dir).await?;
println!("Done in {:.2?}", start.elapsed());
browser.close().await?;
handle.await;
Ok(())
}
整合到 NixOS
我使用 NixOS,因此我可以在線上儲存我的組態。
- 他們的宣告式組態系統允許我在同一個地方擁有所有內容並儲存它:如果我的筆電不見了,我可以快速在新筆電上重現我的系統
- 為了更新我的系統和我的程式,我執行一個建立新世代的命令:如果有任何問題,我可以透過在開機選單中選擇先前的世代輕鬆返回
- 在撰寫本文時,NixOS 上已有超過 12 萬個軟體包:當我想安裝新程式時,我將它新增到我的組態檔案並重建
- 每個 Nix 版本都是穩定的:更新是安全性或重大錯誤修正。如果我仍然想更新某些程式,而新版本尚未針對 Nix 版本進行驗證,我會為這些程式訂閱
unstable頻道 nix-shell允許我在新的臨時環境中啟動新的殼層。它對於測試工具很有用,或者如果需要,也可以擁有具有精確語言版本的臨時環境
我在 Nix 生態系統中還有很多要探索的內容,但在這種情況下,我能夠建立一個軟體包,然後將其載入到我的組態中:工具會被安裝為正常可執行檔。
在我的組態中(environment.systemPackages):
(callPackage ./dcsrs-to-pdf/default.nix {})
軟體包位於組態檔案旁邊:Nix 擷取 Rust 工具鏈,編譯指令碼,安裝它並使其在我的系統上任何地方都可用:我可以將我的指令碼作為正常程式使用。

