A site generator based on a collection of racket/scribble programs.
下面著手描述這個專案的核心想法
地址唯一性 [tr-0003]
每張卡片應有唯一的地址
生成 embed 與 index HTML [tr-0001]
embed 之間需要按引用關係構造,所以用 topological sort 排序(從 metadata.json 中復原依賴關係)後按順序編譯
通過分成兩階段生成,可以讓 index 引入 embed 的內容
raco tr next 與使用方式 [tr-0002]
讓 raco tr next xxx 生成新的地址,就可以用 code $(raco tr next xxx).scrbl 這樣的方式開啟新檔案
數學支援 [tr-0004]
用 Katex 支援公式,用 LaTeX 支援複雜的圖
LaTeX 工作的獨立標籤 [tr-0006]
每個 address yyy 對 LaTeX 會創造新的相關的 _tmp/yyy/tex3212.tex 之類的檔案,tex3212 是用 gensym 產生。
如此一來 *.tex 檔案可知道自己是哪個 address 產出,如果 source 沒有更新便不編譯。
RSS 與 searching [tr-0005]
利用預先生成的 addr.metadata.json 進一步生成 RSS 與索引資料
tr/card [tr-0008]
Code changes: https://github.com/dannypsnl/tr/commit/c1000033203c4216cdccff53bac7f2288a76b867
在 metadata 裡面加入 locals 這個概念,在這個陣列裡面儲存 title 跟 taxon 資訊。遞迴時先標好地址,把資訊存到相應的地址中,就得到平坦的結構。於是就不一定需要開新的檔案來寫新的 card 了。
Commit ddffdf3 之後新增了很重要的選項:#:open,寫成 @tr/card[#:open #f]{...},確保使用者可以選擇預設不打開這張卡片。
tr 的快取機制 [tr-G7G0]
如果原始檔案沒有變化,我們當然希望盡可能不要重新建構它,原因大致上都是因為建構是需要時間的,比如解譯一次 scribble 檔案然後生成 html 需要不少時間、typst 產生圖片需要不少時間等等。從很早期 tr 就有用很單純的方法判斷原始碼有沒有變化過:metadata 的變更時間
從 PR #53 開始 tr 就改從 content-addressed build store 機制判斷了。這套機制的想法是這樣:卡片 render 的結果會依賴一些輸入,用這些輸入計算出一個 sha1 signature,這就是 store 的 key
每次建構之前會用這個 key 判斷 store 中有沒有快取存在,如果有命中就把快取好的產物複製到輸出目錄,不然就表示需要真的建構一次(不管是因為快取被清理掉了還是因為原始碼更新了),並且把內容儲存進 store
signature 在 transclude 的拓樸序下排列(被 transcluded 的卡片會先被處理):
卡片自己的 source code:原始
.scrbl檔案的 bytes,加上它每個@include進來的檔案的 bytes。所以即使.scrbl沒動,Agda html 之類的外部檔案一有變化時這張卡也會失效。不需要另外用 Makefile 之類的工具清理快取卡片自己算出來的 metadata(已經帶有傳播過來的
backlinks/context/related)。每個transclude卡片的完整 signature。所以改 transclude 卡片會讓每個嵌入它的 parent 卡片重新簽名;但反過來,改 parent 卡片不會影響 transclude 卡片
每個被引用的 neighborhood 的
title跟taxon(不管是來自 context/references/backlinks/related/authors),這是一張卡片的 neighborhoods 唯一會 render 的欄位。所以改一張卡片的標題,會重建那些在 Related 區塊 mention 它的卡片;但改卡片的內文則不會影響 neighborhoods一個render-config-tag:設定檔裡的
head欄位會影響render的內容。兩個render出來bytes會不同的output那他們的target就會有不同的signature,不會複製到對方的輸出
store 下來的內容放在 _tmp/cache/store/<sig>/,儲存:render 好的 embed.html 跟 out/(編譯好的 latex / typst svg)。index.html 不快取,因為這個計算很快
deploy可以從同一份
_tmp建立兩棵樹:先執行dev建構再執行release建構的話,只要內容跟config有影響內容的欄位相同,signature就相同,於是第二個target只需要從store複製,不用重複建構把卡片revert回去還是會有快取命中,因為較早的signature對應的entry還在硬碟上(這在外部工具產出的一些東西不在signature計算中時確實偶爾會是問題,但不嚴重)
store 雖然保證了不同建構 target 時的正確性,但不知道某個特定 target 是由哪個 signature 產生,所以每個 output 目錄會留一個戳記 <output>/<addr>/.sig,記下它的 index.html 是哪個 signature 產生的。當戳記吻合、index.html 存在、且每個被快取的 output 檔都還在時,per-target 的工作(svg 加 index.html)就可以被跳過,只把 embed 刷新回 _tmp 供 transclude 它的父卡讀取。被刪掉或被竄改的 output 檔,即使戳記吻合,下一次 build 也會從 store 重新產生
private cards [tr-490U]
我突然意識到我雖然引入了這個功能(commit 4c9a9ed)但是還沒有記錄。這個功能的用途是讓 content/private 中的cards在release build中會被排除,如果這時候其他cards引用到任何一個private cards建構就不會成功
Config as code [tr-XH91]
在site.rkt中編寫設定檔,這讓一些複雜欄位可以用racket完成,以本站為例設定檔是
#lang racket/base (require scribble/html) (provide site) (define site (hash 'assets '("assets" "slide-assets" "images") 'description "……" 'domain "dannypsnl.me/" 'mode "dev" 'title "Lîm Tsú-thuàn" 'head (list (link 'rel: "me" 'href: "https://g0v.social/@dannypsnl") (meta 'name: "fediverse:creator" 'content: "@dannypsnl@g0v.social") (link 'rel: "webmention" 'href: "https://webmention.io/dannypsnl.me/webmention") (link 'rel: "alternate" 'type: "application/rss+xml" 'title: "Lîm Tsú-thuàn" 'href: "/rss.xml"))))
是否發佈由設定檔控制 [tr-YGRJ]
private cards現在也納入Config as code的設計,用
(define (remove-content-p source-path) (define segs (map path->string (explode-path source-path))) (and (equal? "content" (first segs)) (equal? "private" (second segs)))) (define site (hash ; ... 'remove-content-if remove-content-p))
提供這個過濾判斷
Tufte風格的margin note [tr-P10S]
@note{...} 可以用來建立出現在右側的邊註邊註
寫在 @note 裡的 @mention["..."] 有特殊處理:會展開成可點擊的卡片,就像這樣tr。也因為這個,tr 的快取機制裡neighborhood從只看title跟taxon擴充到也看日期、作者與DOI
編號用CSS counter;寬螢幕時註解浮到正文右邊的邊界;中等寬度退化成正文裡的一個區塊;窄螢幕則是點了ref之後才會展開
自訂Header [tr-VGBX]
原本預設在每頁最上面放一個 « Home 連結,index例外。現在 Config as code 多了 header 欄位可以整個換掉它
'header (nav 'class: "site-nav"
(a 'href: "/" "my site")
(a 'href: "/about/" "about"))
設定之後index也會render這段html。tr 的快取機制的config tag也因此要拆成兩半,確保改header會重新產生每一頁的 index.html,但不會讓embed失效而重跑LaTeX、Typst