- 閱讀時間 7 分鐘

使用 Rust 將 Docusaurus 轉換為 PDF

Rust
Markdown logo

我使用 Docusaurus 撰寫我所有的課程,這是一個我非常喜愛的工具:

  • 所有內容都以 Markdown/MDX 撰寫,因此我可以輕鬆移植到其他的工具
  • 很容易組織課程的章節,目錄會自動產生
  • 由於外掛程式系統,我將搜尋引擎整合到課程中(我甚至測試過 Algolia,效果相當不錯)
  • Docusaurus 基於 React,因此我可以建立元件,然後將它們整合到我的課程中:章節結尾測驗、整合程式碼編輯器…
  • 自 3.6 版本起,使用 RspackSWCLightning 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 工具鏈,編譯指令碼,安裝它並使其在我的系統上任何地方都可用:我可以將我的指令碼作為正常程式使用。

dcsrs-to-pdf-cli-example

dcsrs-to-pdf-sf-chapters