Publish是一款專門為Swift開發(fā)者打造的靜態(tài)網(wǎng)站生成器。它使用Swift構(gòu)建整個網(wǎng)站,并支持主題、插件和其他大量的定制選項(xiàng)。
作為Swift開發(fā)者,通過Publish創(chuàng)建站點(diǎn)的開發(fā)過程和體驗(yàn)同開發(fā)其他程序很類似。
開篇
開發(fā)者John Sundell
Publish的開發(fā)者John Sundell這些年一直致力于發(fā)表關(guān)于Swift的高質(zhì)量文章、播客和視頻。他的作品大多都發(fā)布在其獨(dú)立運(yùn)營的 Swift by Sundell上。他開發(fā)了Publish用以創(chuàng)建并管理自己的站點(diǎn)。
在開發(fā)Publish的過程中,他還開源了其他大量的基本庫,比如Ink(高效的Markdown解析器)、Plot(創(chuàng)建HTML、XML、RSS的DSL)、Sweep(高效的字符串掃描庫)、Codextended(Codable增強(qiáng))等。它們不僅一起構(gòu)建了強(qiáng)大的Publish,并且在各自的領(lǐng)域也是極為出色的開源庫。
我為什么使用Publish
我在一年前恢復(fù)自己的個人博客時使用的是Hexo。Hexo在國內(nèi)有非常好的群眾基礎(chǔ),網(wǎng)上有大量優(yōu)秀的教程,也有非常多的開發(fā)者貢獻(xiàn)了自己創(chuàng)作的各種主題和插件。盡管Hexo讓我相當(dāng)滿意,但由于我主要使用的語言是Swift,且對JavaScript非常不熟悉,因此想要對Hexo做更深入的定制或修改很困難。
作為開發(fā)者(即使是業(yè)余的),總希望對自己的項(xiàng)目有更全面的掌控,因此完全由Swift開發(fā)的Publish就成為了我的首選。
隨著使用Publish對肘子的Swift記事本重建過程的深入,我感覺自己做出了正確的選擇。Publish讓我可以用開發(fā)普通app的思路和邏輯來創(chuàng)建站點(diǎn),高效地完成我想要的各種定制和改動。
寫本文的原因
截至落筆時,Publish已經(jīng)在Github上獲得了3.1K的好評。但網(wǎng)絡(luò)上對Publish的介紹并不多,尤其欠缺關(guān)于主題定制、插件開發(fā)方面的資料和交流。在Github上搜索相關(guān)的插件和主題的結(jié)果數(shù)量也非常有限。
造成上述的情況固然有Publish誕生時間較短、使用量不大,Swift圈子較小等原因,但我認(rèn)為下面的情況也加劇了這一局面的形成:由于不同于其他的靜態(tài)網(wǎng)站生成器,在Publish項(xiàng)目中,開發(fā)者可以用短小的代碼實(shí)現(xiàn)各種功能。這種碎片化的代碼其實(shí)是不利于分享且并不容易被搜索;另外,由于Publish中的主題和網(wǎng)站的功能具體實(shí)現(xiàn)綁定的較深,單獨(dú)分享的主題的利用度也較低。
但Publish的這種特質(zhì)也恰恰是其吸引人之處。
有鑒于此,我將用三篇文章(入門、主題開發(fā)、插件開發(fā))完成對Publish的簡紹,也希望國內(nèi)的Swift開發(fā)者或愛好者們可以更多的了解和使用這個優(yōu)秀的工具。
為了讓大家能夠快速上手,我已將肘子的Swift記事本站點(diǎn)所用的代碼(包括主題、自定義插件等)放置在Github上,方便大家通過代碼更快的了解和掌握Publish。
快速使用入門
如何安裝Publish
同大量的其他靜態(tài)網(wǎng)站生成器一樣,Publish提供了CLI。你可以通過命令行快速的完成創(chuàng)建模板、內(nèi)容更新、遠(yuǎn)程發(fā)布等一系列操作。Publish目前可以運(yùn)行在Mac和Linux上,由于其代碼對操作系統(tǒng)的依存度極低,估計(jì)其后也出現(xiàn)在Windows平臺上。
Mac下通過brew安裝
$brew install publish
源代碼安裝
$git clone https://github.com/JohnSundell/Publish.git
$cd Publish
$make
創(chuàng)建你的第一個項(xiàng)目
讓我們來創(chuàng)建一個新的Blog項(xiàng)目
$mkdir myblog
$cd myblog
$publish new
Publish將在myblog目錄中創(chuàng)建我們所需的項(xiàng)目模板。它的基本構(gòu)成大概如下:
|-- myblog
| |-- Content
| |–– posts
| |–– first-post.md
| |–– index.md
| |–– index.md
| |-- Resources
| |-- Sources
| |–– Myblog
| |–– main.swift
-
Content
在此放入你要在網(wǎng)站發(fā)布的文章、頁面等使用markdown編寫的文件。
-
Resources
項(xiàng)目主題需要的一些資源,比如css,圖片等,目前為空。在你進(jìn)行第一發(fā)布后,可以看到它包含了默認(rèn)的FoundationTheme的styles.css文件。
-
Source
描述網(wǎng)站的代碼。在
main.swift中定義了網(wǎng)站的基本屬性、創(chuàng)建工作流等。
編譯及運(yùn)行
Swift是編譯型語言,因此你的站點(diǎn)的代碼在每次修改之后,都需在本機(jī)編譯并運(yùn)行才能完成內(nèi)容的生成工作,好在這一切都只需要一條命令。
我們讓Publish完成上述工作并啟動內(nèi)置的Web Server供我們?yōu)g覽新創(chuàng)建的項(xiàng)目。
$publish run
第一次運(yùn)行,Publish會自動從Github上獲取所需的其他庫,請稍等幾分鐘。
$publish run
............
Publishing Myblog (6 steps)
[1/6] Copy 'Resources' files
[2/6] Add Markdown files from 'Content' folder
[3/6] Sort items
[4/6] Generate HTML
[5/6] Generate RSS feed
[6/6] Generate site map
? Successfully published Myblog
?? Starting web server at http://localhost:8000
Press ENTER to stop the server and exit
現(xiàn)在你就可以用瀏覽器訪問 http://localhost:8000 來訪問你的新站點(diǎn)了。
網(wǎng)站的全部內(nèi)容都被生成并放置在了Output目錄下。你只需要將其中的內(nèi)容上傳到你的服務(wù)器,或者通過簡單的配置,比如:
.unwrap(.gitHub("fatbobman/fatbobman.github.io", useSSH: true), PublishingStep.deploy)
然后使用
$publish deploy
便可將內(nèi)容發(fā)布到你的github.io上(具體配置后面說明)。
此時你在Content - posts中添加如下文件second-post.md
---
date: 2021-01-29 19:58
description: 我的第二篇文章
tags: publish,swift
---
# Hello Wolrd
再度執(zhí)行 publish run 便可以看到新文章已經(jīng)出現(xiàn)在頁面上了。
使用我提供的模板快速上手
首先要確保已經(jīng)安裝了Publish CLI
$ git clone https://github.com/fatbobman/PublishThemeForFatbobmanBlog.git
$ cd PublishThemeForFatbobmanBlog
$ publish run
更多關(guān)于Publish的知識
本節(jié)的內(nèi)容將介紹幾個Publish中的概念,對于后面了解主題定制和功能擴(kuò)展十分重要。
Site
當(dāng)你使用Publish來創(chuàng)建項(xiàng)目時,Publish會自動生成一個Swift Package。網(wǎng)站的生成和部署配置都是通過該包完成的,使用的都是原生的且類型安全的Swift代碼。
下面的代碼便是使用publish new myblog生成的main.swift(包的入口文件)中內(nèi)容。
//Site的定義
struct Myblog: Website {
enum SectionID: String, WebsiteSectionID {
// 添加你需要的Section
case posts
}
struct ItemMetadata: WebsiteItemMetadata {
// 在這里添加任何您想使用的特定站點(diǎn)元數(shù)據(jù)
}
// 你網(wǎng)站的一些配置xn'xi
var url = URL(string: "https://your-website-url.com")!
var name = "Myblog"
var description = "A description of Myblog"
var language: Language { .english }
var imagePath: Path? { nil }
}
//可以在主題或插件等位置訪問Site中的屬性信息
// 執(zhí)行入口。當(dāng)前使用的是默認(rèn)的主題,且使用的是Publish預(yù)置的生成、導(dǎo)出、發(fā)布流程。
// 工作流的定義,更多內(nèi)容見Step
try Myblog().publish(withTheme: .foundation)
Site不僅定義了網(wǎng)站項(xiàng)目的基礎(chǔ)配置信息,而且定義了網(wǎng)站從生成到發(fā)布的工作流程。
Section
每個Section都會在Output下生成的一個子目錄。在main.swift中,通過枚舉的方式對Section進(jìn)行定義。你可以把Section可以作為一組Item(文章)的容器,也可以僅指向某個Page(非Item的自有頁面)。 當(dāng)需要使用Section管理一組文章時,只需要在Content目錄下創(chuàng)建同該Section名字相同的子目錄即可,具體可以查看范例中Content下的posts和project。
Section的定義
enum SectionID: String, WebsiteSectionID {
case posts //新創(chuàng)建的項(xiàng)目缺省只有這個,對應(yīng)content-posts目錄
case links //可以自己添加,將屬于該section的文章放置到對應(yīng)的目錄即可
case about
}
在肘子的Swift記事本中,每個Section同時也對應(yīng)著上方導(dǎo)航區(qū)的一個選項(xiàng)。Section可以有多種用途,在主題定制章節(jié)會做更多探討。
Item
保存在Content--對應(yīng)Sectioin目錄中的文章。每個Item都對應(yīng)一個Section,無需特別設(shè)置,其保存在哪個Section的目錄中,就屬于哪個Section。如果該Section不需要作為文件容器,可以直接在Content中創(chuàng)建和Section同名的md文件。我提供的范例模板中,about就是這種形式。
Page
不歸屬于任何Section的文章。Page不會出現(xiàn)在Section的item列表中,通常也不會出現(xiàn)在index(首頁)列表中。在content下的不屬于任何Section的目錄中按如下結(jié)構(gòu)添加文件即可創(chuàng)建Page。注意Page的創(chuàng)建路徑和訪問路徑的關(guān)系。
| content -- 404
| | -- index.md
你可以通過訪問http://localhost:8000/404/index/查看
Page為我們提供一種構(gòu)建自由頁面的方法。比如你可以用它來創(chuàng)建不需要顯示在列表中的文章,或者像范例主題的演示一樣創(chuàng)建404??。
Content
在此特指Item、Page中的content屬性。作為內(nèi)容集,其范圍包括文本(如標(biāo)題和描述)、所屬標(biāo)簽(tag)、轉(zhuǎn)換后HTML代碼、音頻、視頻等各種元數(shù)據(jù)。元數(shù)據(jù)需要在Markdonw文章的頭部注明。
Section也有Content,它的內(nèi)容對應(yīng)著你在該Section對應(yīng)的Content子目錄中創(chuàng)建的index.md(如果有必要的話)。
在代碼中將來還會碰到另一種Content,確切的說是PublishingContext。里面包含著整個項(xiàng)目的所有信息(Site、Sections、Items、Tags等),通過將它的實(shí)例傳遞給Step或者Plugin來完成修改或配置網(wǎng)站的各種工作。
Metadata
Markdown文件的元數(shù)據(jù),在文章(Markdown)文件的頭部做出標(biāo)識。分為兩類,一種是Publish預(yù)置的。另一種是通過在Site中ItemMetadata自定義的。
---
date: 2021-01-29 19:58
description: A description of my first post.
tags: first, article
author: fat
image: /images/first-post.png
---
預(yù)設(shè)
-
title 文字標(biāo)題
如果沒有設(shè)置,Publish會直接查找文章正文中第一個Top-level Head
#作為標(biāo)題 description 文章簡介
-
date 文章創(chuàng)作日期
如果沒設(shè)置則直接使用文件的modificationDate
tags 文章標(biāo)簽,每篇文章可以設(shè)置多個標(biāo)簽,為文章的組織多一個維度
image 圖片地址 比如可以用來在
item列表中顯示一個文章的主題圖片(需在主題中定義)audio 音頻數(shù)據(jù)
-
video 視頻數(shù)據(jù)
音視頻的定義過于復(fù)雜,如果確實(shí)需要可以自行定義。
自定義
struct ItemMetadata: WebsiteItemMetadata {
// Add any site-specific metadata that you want to use here.
var author:String
}
如果預(yù)置的metadata不足以滿足你的需求,可以在ItemMetadata中自行定義。
兩種metadata的區(qū)別
預(yù)設(shè)的metadata在Publish中是作為的屬性存在的。
for item in content.allItems(sortedBy: \.date){
print(item.title)
}
自定義的metadata需通過如下方式獲取
let author = (item.metadata as! Myblog.ItemMetadata).author
在主題中使用更方便
.text(item.metadata.author)
Publish中預(yù)設(shè)的metadata,Item并不要求必須填寫。但是對于自定義的metadata則必須在markdown文檔中添加。index.md、 Page 可以沒有metadata(無論是否為自定義)
Theme
Publish使用Plot作為其HTML主題的描述引擎,開發(fā)者可以用非常Swift的方式來定義頁面。如果使用過DSL類型的開發(fā)方式,會感覺非常親切。比如下面的代碼定義了Section List的布局呈現(xiàn)
func makeSectionHTML(
for section: Section<Site>,
context: PublishingContext<Site>
) throws -> HTML {
HTML(
.lang(context.site.language), //網(wǎng)頁語言
.head(for: section, on: context.site), //頭文件,metadata、css等
.body(
.header(for: context, selectedSection: section.id), //網(wǎng)站的上部區(qū)域,范例中包括了Logo,以及導(dǎo)航條
.wrapper(
.itemList(for: section.items, on: context.site) // 文章列表
),
.footer(for: context.site) //最下方的版權(quán)區(qū)域
)
)
}
在Theme中定義的布局細(xì)節(jié)仍需要在css中進(jìn)行進(jìn)一步設(shè)置。
上面代碼中 header、wrapper等在Plot中都被稱作Node,除了使用Publish中預(yù)置的大量Node外,我們可以使用自己編寫的Node。
更多關(guān)于Theme的內(nèi)容,將在用Publish創(chuàng)建博客(二)中做詳細(xì)介紹。
Step
Publish采用工作流(Pipeline)的方式來明確定義各個環(huán)節(jié)的操作過程。從文件讀取、markdown解析、HTML生成、RSS導(dǎo)出等等。通過publish new生成的main.swift中,盡管只使用了一條語句
try Myblog().publish(withTheme: .foundation)
但其背后對應(yīng)著下面一系列操作步驟:
using: [
.group(plugins.map(PublishingStep.installPlugin)),
.optional(.copyResources()),
.addMarkdownFiles(),
.sortItems(by: \.date, order: .descending),
.group(additionalSteps),
.generateHTML(withTheme: theme, indentation: indentation),
.unwrap(rssFeedConfig) { config in
.generateRSSFeed(
including: rssFeedSections,
config: config
)
},
.generateSiteMap(indentedBy: indentation),
.unwrap(deploymentMethod, PublishingStep.deploy)
]
在多數(shù)的情況下,我們都會顯式的將每一個操作步驟標(biāo)明出來。每個步驟在Publish中被稱為Step。Publish已經(jīng)預(yù)置了不少Step供開發(fā)者使用。我么也可以將自己創(chuàng)建的Step注入到工作流中合適的位置以實(shí)現(xiàn)更多功能。
每個Step都會被傳遞一個PublishContent實(shí)例,該實(shí)例可用于更改網(wǎng)站中的各種元素(包括文件、文件夾、Item、Page等)。關(guān)于PublishContent同Content的不同,請見上文。
Plugin
.installPlugin(.highlightJS()), //語法高亮
上面的代碼在Publish工作流中通過名為installPlugin的Step來完成插件highlightJS的安裝。
Plugin的開發(fā)和Step非常類似,都會獲得一個PublishContent實(shí)例,并通過其完成相關(guān)工作。
比如說,你可以用Step來完成某些具有副作用的操作;用Plugin來完成類如Modifier(markdown的定制化解析)注入的工作。
對于自定義代碼,從功能角度講,兩者都能實(shí)現(xiàn)對方的工作。因此在創(chuàng)建功能擴(kuò)展時,采用Step還是Plugin取決于個人的偏好。
關(guān)于如何定制Step和Plugin將在用Publish創(chuàng)建博客(三)中做詳細(xì)說明。
Publish適合什么人
Publish同當(dāng)前主流的靜態(tài)網(wǎng)站生成器相比還略有不足,如社區(qū)活躍度較低、開發(fā)時間較短、Swift語言用戶量較小等。當(dāng)前Publish較適合符合如下狀況的朋友:
- 使用Swift語言的開發(fā)者或Swift的愛好者
- 欠缺Javascripte的經(jīng)驗(yàn),或者喜歡Javascripte free的風(fēng)格
- 追求高效、簡潔的網(wǎng)頁呈現(xiàn)方式
- 希望能夠完整掌握網(wǎng)站的各個環(huán)節(jié)并通過自己的雙手逐步實(shí)現(xiàn)各項(xiàng)功能
- 善于嘗鮮者
Next
我將在用Publish創(chuàng)建博客(二)——主題開發(fā)中探討Theme的開發(fā),在用Publish創(chuàng)建博客(三)——插件開發(fā)中了解如何通過多種手段擴(kuò)展Publish的功能。
如果你已經(jīng)開始感興趣,馬上在Github上開通你的github.io站點(diǎn),用Publish一鍵deploy屬于自己的博客吧。
我的個人博客肘子的Swift記事本中會有更多關(guān)于Swift、SwiftUI、CoreData的內(nèi)容。