Swiftでブログを作ってGitHub Pagesにデプロイしてみる

Swiftでブログが書きたかった。
2020.11.30

とある記事を読んだことがきっかけで個人用のウェブサイトを作ろう、どうせならSwiftで作りたいなと思ってVaporについて調べていたところ、Swift製の静的サイトジェネレータJohnSundell/Publishを知りました。

この記事ではPublishを用いて静的サイトを生成してGitHub Pagesにデプロイする所までの手順を書きます。

Publish

Publishは、Swiftを使用している開発者向けに開発された静的サイトジェネレーターです。Swiftを使用してウェブサイトを構築することができてテーマ、プラグインなどがカスタマイズできます。

PublishでウェブサイトはSwiftパッケージとして定義されます。Swiftコードでウェブサイトがどのように生成され、デプロイされるべきかの設定を行います。

環境構築

PublishはSwit5.2以降、Macで開発する場合はxcode-selectが要件に含まれるバージョンのSwiftを含んでいるXcodeを指していることと、macOS Catalina(10.15)以降であることを確認する必要があります。

コマンドラインツールをインストールします。

$ git clone https://github.com/JohnSundell/Publish.git
$ cd Publish
$ make

この後任意のディレクトリでpublish newコマンドを叩きます。

 ❯ publish new
 ✅ Generated website project for 'Blog'
 Run 'open Package.swift' to open it and start building

publishコマンドについて知りたい場合はpublish helpコマンドを叩きます。

Package.swifftを開くとXcodeが依存するモジュールを導入してくれます。

Schemeと実行するデバイス(My Mac)を選択してRunでビルドが成功することを確認します。

Publishing Blog (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 Blog
 Program ended with exit code: 0

publish runコマンドでローカルホストで生成されたサイトを確認できます。

ディレクトリ構成は以下です。

Blog
   - Package.swift
   - Content
     - posts
       - first-post.md
       - index.md
     - index.md
   - Output
   - Resources
   - Sources
     - Blog
       - main.swift
   - Package.resolved

Output以下が生成されているhtmlファイルなどの実際にユーザーが閲覧するものを配置するディレクトリで、Resourcesはcustom CSSファイルや画像などの静的ファイルを配置するディレクトリです。Sources以下で生成するためのコードを記述します。

基本的な使い方

サイトの基本的な情報を構成する構造体を定義します。

import Foundation
import Publish
import Plot

// This type acts as the configuration for your website.
struct Blog: Website {
    enum SectionID: String, WebsiteSectionID {
        case posts
    }

    struct ItemMetadata: WebsiteItemMetadata {
    }

    // Update these properties to configure your website:
    var url = URL(string: "http://tanabe1478.github.io/")!
    var name = "t__nabe1478's Blog"
    var description = "t__nabe1478のブログです。務め先のブログには書く程でない技術に関することや私事について書きます。"
    var language: Language { .japanese }
    var imagePath: Path? { nil }
}

セクションやメタデータは任意にカスタマイズできます。

localhostで試したいだけなら以下のコードで十分です。

try DeliciousRecipes().publish(withTheme: .foundation)

このpublish(withTheme:)でウェブサイトのHTML、RSSフィード、サイトマップなどが生成されます。withThemeパラメータに.foundationを指定することで組み込みのテーマを使用しています。

構造体の定義に登場したSectionIDという列挙型でセクションを表現します。各セクションは、それ自身の HTML ページを持ってそのセクション内の入れ子になった HTML ページを表すアイテムのリストのコンテナとして機能します。Pagesは、任意の種類のフォルダ階層に配置することができるフリーフォームページを構築する方法を提供します。

HTML Theme

PublishはHTMLテーマエンジンにSwift でタイプセーフな HTML、XML、RSSを書けるDSLであるJohnSundell/Plot を採用しています。このカスタムThemeを作る時に不要な要素を削除していくのが良いと思います。

Publishテーマの構築方法や、その際に推奨されるベストプラクティスについてはまだドキュメントが整っていないので組み込みのテーマのコードを見て実装する必要があります。HTML Themeの作成にはHTMLFactory protocolに準拠するオブジェクトを定義する必要があります。

定義したHTMLFactoryに準拠した構造体を呼び出すstaticメソッドを定義します。これは組み込みのテーマの実装方法に則っています。

struct MyHtmlFactory<Site: Website>: HTMLFactory {
    // HTMLFactoryに準拠するために実装が必要なメソッドが以下
    func makeIndexHTML(for index: Index,
                       context: PublishingContext<Site>) throws -> HTML {
    }

    func makeSectionHTML(for section: Section<Site>, context: PublishingContext<Site>) throws -> HTML {
    }

    func makeItemHTML(for item: Item<Site>, context: PublishingContext<Site>) throws -> HTML {
    }

    func makePageHTML(for page: Page, context: PublishingContext<Site>) throws -> HTML {
    }

    func makeTagListHTML(for page: TagListPage, context: PublishingContext<Site>) throws -> HTML? {
    }

    func makeTagDetailsHTML(for page: TagDetailsPage, context: PublishingContext<Site>) throws -> HTML? {
    }
}

extension Theme {
    static var myTheme: Theme {
        Theme(htmlFactory: MyHtmlFactory(), resourcePaths: ["Resources/MyHtmlTheme/styles.css"])
    }
}

シンタックスハイライトについて

デフォルトのテーマだとシンタックスハイライトが効いていません。そこでプラグインの導入が必要になります。

最初はJohnSundell/SplashPublishPlugin を導入することにして進めていました。README.mdに記載の通り、Package.swiftに記載していたのですがビルドエラーになりました。Package.swiftの書き方が変わっているようだったので修正して記載の通りimportしてプラグインを追加しましたがハイライトが効かないままでした。

これらの問題についてはPRが立っているので時間を待てば解決するかもしれません。

シンタックスハイライトはすぐに効くようにしておきたかったので、今回はalex-ross/HighlightJSPublishPlugin を使用しました。

Package.swiftは以下のようにしました。

// swift-tools-version:5.2

import PackageDescription

let package = Package(
    name: "Blog",
    products: [
        .executable(
            name: "Blog",
            targets: ["Blog"]
        )
    ],
    dependencies: [
        .package(name: "Publish", url: "https://github.com/johnsundell/publish.git", from: "0.6.0"),
        .package(name: "HighlightJSPublishPlugin", url: "https://github.com/alex-ross/highlightjspublishplugin", from: "1.0.0")
    ],
    targets: [
        .target(
            name: "Blog",
            dependencies: ["Publish", "HighlightJSPublishPlugin"]
        )
    ]
)

プラグインを導入できるpublishメソッドを使用してプラグインを追加します。テーマはダークモードにも対応しているので、リポジトリにあるサンプルCSSをとりあえずそのまま使用しました。

try Blog().publish(using: [
    .installPlugin(.highlightJS()), // プラグインの追加
    .addMarkdownFiles(),
    .generateHTML(withTheme: .myTheme),
    .optional(.copyResources()),
    .sortItems(by: \.date, order: .descending),
    .generateRSSFeed(including: [.posts]),
    .generateSiteMap(),
])

GitHub Pagesへのデプロイ

GitHubへのデプロイにデフォルトで対応しているので今回はそれを利用しました。このプロジェクトをGitHubのリポジトリにPushした後にOutputs以下のみをアップロードするリポジトリも作成します。静的ファイルを生成しているプロジェクトはprivateですがOutputs以下をアップロードするリポジトリはPublicにしました。リポジトリ名はusername.github.ioでアクセスできるようにしたかったのでGitHub Pages について - GitHub Docsに従った命名でリポジトリを作りました。

GitHub へのデプロイはdeploy(using:useSSH:)メソッドをパイプラインに追加することで行えます。

.deploy(using: .gitHub("tanabe1478/tanabe1478.github.io", useSSH: true)),

このパイプラインはシーケンシャルに実行されます。Highlight.jsのプラグインの読み込みをdeployの後に指定してしまいシンタックスハイライトが効かないことがありました。

try Blog().publish(using: [
    .installPlugin(.highlightJS()),
    .addMarkdownFiles(),
    .generateHTML(withTheme: .myTheme),
    .optional(.copyResources()),
    .sortItems(by: \.date, order: .descending),
    .generateRSSFeed(including: [.posts]),
    .generateSiteMap(),
    .deploy(using: .gitHub("tanabe1478/tanabe1478.github.io", useSSH: true)),
])

コードを記述した後、ターミナルでpublish deployコマンドを叩きます。

❯ publish deploy
Publishing t__nabe1478's Blog (1 steps)
[1/1] Deploy using Git (git@github.com:tanabe1478/tanabe1478.github.io.git)
✅ Successfully published t__nabe1478's Blog

成功したらリポジトリの設定画面にも反映されます。

デプロイされていることを確認できました。

まとめ

Swiftは出来ることがどんどん増えていて楽しいです。Publishはまだ出来たばかりのOSSなので日本語の情報は全くなく、ドキュメントもこれから整備されていくとのことなのでプラグインの作成などにチャレンジしつつコントリビュートできる機会があれば良いなと思います。まずは最低限ウェブサイトが見れるレベルになるまでCSSを整えることが最優先です。。。

掲載したリンク以外に参考にしたもの