Обложка статьи «Создаём блог на Swift и размещаем его на GitHub Pages»

Создаём блог на Swift и размещаем его на GitHub Pages

Рассказывает Александр, старший iOS-разработчик Noveo

У меня уже есть блог, он на WordPress, и я устал от всей этой свистопляски с обновлением версии, плагинов, возможности взлома и прочего. Это буквально убивает желание продолжать его развивать. Моя цель — делиться с миром своими мыслями и находками, но при этом иметь полный контроль над своим сайтом. С недавних пор GitHub Pages предоставила возможность хранить простенькие сайты у них. Никаких баз данных и PHP, статический сайт, как в старые добрые. Для блога —идеально. А т.к. процесс деплоя будет проходить через GitHub, будет прозрачная история всех изменений и полный контроль над тем, что реально находится на сайте: трояны и прочая нечисть не пройдут.

Но они предлагают использовать Jekyll, статический генератор сайтов, написанный на Ruby. Все же хочется оттачивать мастерство в том языке, который ты используешь ежедневно, поэтому в качестве альтернативы я взял свежий статический генератор, написанный на Swift, от John Sundell.

Нам предстоит сделать 3 вещи:

  1. Создать блог на GitHub Pages.
  2. Разобраться, как работать с Publish.
  3. Залить свой новый блог на GitHub Pages.

Создадим блог на GitHub Pages

Официальная инструкция доступна по адресу https://pages.github.com.

Основное условие — login блога должен совпадать с вашим логином на GitHub (в моем случае это sparklone). Наши действия:

  • логинимся в GitHub;
  • идем по адресу https://github.com/new,
  • в качестве имени репозитория вводите login.github.io, в моем случае было sparklone.github.io;
  • убеждаемся, что он будет Public;
  • жмем Create Repository;
  • все, наш блог уже доступен по адресу https://login.github.io.

Но пока на сайте нет ни одного файла. Переходим ко второму пункту.

Разберёмся, как работать с Publish

Publish — это статический генератор сайтов, написанный на Swift, что позволяет выстраивать любую логику при генерации сайта: теги, карта сайта, rss, специфическая перелинковка… — все, на что нам хватит фантазии. Одна из моих идей на будущее — попробовать писать статьи на двух языках, и чтобы настроенный шаблон умел это понимать и вставлять переключатель в статью, к примеру. Не знаю, дойдут ли когда-нибудь до этого руки, но как идея — интересно.

Я предпочел установить напрямую через git, создал папку ~/Developer/tools и там выполнил

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

Далее я создал папку, где планирую хранить свой сайт (точнее, исходники его генератора), в моем случае это ~/Developer/my/blog. Внутри этой папки запускаем команду

publish new

Это создаст новый сайт, а список опций для команды можно узнать, просто запустив в консоли publish.

publish run скомпилирует все исходники и запустит веб-сервер на питоне (если прервать посредством Ctrl+C, веб-сервер не умрет и придется ковыряться в процессах, чтобы убить Python).

Первая сборка занимает какое-то время (подливаются нужные репозитории, все компилируется), при последующих запусках publish run все происходит намного быстрее.

Открываем в браузере http://localhost:8000 и видим, что за сайт получился:

Не густо, но мы еще ничего, собственно, и не сделали, чтобы ожидать чего-то большего.

Настройку стилей и прочие моменты я оставлю на потом. На сегодня вижу перед собой следующие задачи:

  1. Сделать изначальную настройку генератора для нашего сайта.
  2. Поменять пути, по которым будут лежать записи, и настроить меню.
  3. Добавить подсветку для исходных файлов и создание своей темы.

Изначальная настройка генератора для нашего сайта

Посмотрим, что нам сгенерировал Publish. Внутри папки будет файл Package.swift, его открываем XCode’ом (можно в консоли набрать open Package.swift), и это запускает процесс подтягивания всех нужных библиотек. На скрине видно, что должно получится в итоге.

Тестовая запись лежит в Content/posts/first-post.md. Проверим, что мы можем что-то изменить и это отразится на сайте: подправим текст в first-post.md (можно поменять дату, теги, описания и сам контент).

В Xcode выбираем Mac в качества таргета, для которого надо запускать, и делаем Run проекту.

После этого рефрешим веб-страницу http://localhost:8000.

Меняем пути, по которым будут лежать записи, и настраиваем меню

Почему это важно сделать сразу? Если вы решите поменять пути после того, как уже зальете часть постов, входящие ссылки на эти страницы станут невалидными, а т.к. это не свой сервер/VPS, будет довольно проблематично сделать редирект со старых ссылок на новые пути. Не невозможно (вот, к примеру, плагин для Jekyll), но зачем создавать себе головную боль в будущем, если можно ее избежать.

По умолчанию все записи лежат в папке posts. Учитывая, что в дальнейшем хочется бить на категории, а posts — общее название, лучше переименовать папку в articles. Также не помешает добавить страницу about. Но наш генератор пока ничего не знает про это.

В main.swift поменяем на

struct Blog: Website {
    enum SectionID: String, WebsiteSectionID {
        case articles
        case about
    }
}

Добавим файл about.md в папку Content и заполним немного информации о себе.

К слову, эту статью я пишу в XCode, редактируя md-файл и периодически запуская проект, чтобы посмотреть в браузере, как это смотрится.

Я долго колебался, заморачиваться ли с датой у файлов *.md, т.е. называть не my-article.md, а, к примеру, 2020-07-10-my-article.md. И все же решил отказаться от этой идеи: т.к. в самих md-файлах есть метаданные (поле date), это при желании позволит в будущем добавлять дату автоматически. И не придется следить за корректностью даты в имени файла, к тому же это убережет от расхождений, если в имени файла одна дата, а в метаданных внутри — другая. Недостатком такого решения я вижу то, что гипотетически могут в будущем быть коллизии в именах md-файлов, но все-таки это маловероятно, к тому же мы об этом узнаем при создании поста, а не сохранении.

Добавляем подсветку для исходных файлов

Автором генератора Publish так же была написана библиотека Splash, позволяющая делать подсветку исходных кодов. Подключается она как package в Swift Package Manager.

Открываем файл Package.swift и добавляем поддержку, по сути нужно добавить 2 строки. Вот что получилось у меня:

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: "SplashPublishPlugin", url: "https://github.com/johnsundell/splashpublishplugin", from: "0.1.0")
    ],
    targets: [
        .target(
            name: "Blog",
            dependencies: [
                "Publish",
                "SplashPublishPlugin"
            ]
        )
    ]
)

А чтобы подсветка начала применяться при генерации, нужно подключить плагин в main.swift:

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

Я поменял на

try Blog().publish(
    withTheme: .foundation,
    plugins: [.splash(withClassPrefix: "")]
)

Не забываем добавить в main.swift импорт плагина import SplashPublishPlugin.

Если сгенерировать сайт сейчас, мы увидим, что блоки с кодом будут выделяться, но не подсвечиваться: это связано с тем, что нужно добавить CSS, будем использовать тот, который по умолчанию используется автором плагина.

Но вот проблема: куда добавлять этот CSS? Если покопаться в исходниках, то станет видно, что все это решается на уровне темы. Тема у нас стандартная: foundation, и она лежит в самом пакете Publish по пути Sources/Publish/API/Theme+Foundation.swift.

Внутри своего проекта (Blog) в папке, где лежит main.swift, я создал подобный файл Theme+Blog.swift. Также внутри папки Resources я создал папку Blog и поместил туда файл styles.css Внутрь файла я поместил содержимое CSS от темы Foundation, ну и CSS от Splash. Да, лучше было бы разделить, а может, вообще темы вынести в отдельный package, но не будем усложнять раньше времени.

Итого у меня в файле темы пришлось поменять таким образом:

import Plot
import Publish

public extension Theme {
    static var blog: Self {
        Theme(
            htmlFactory: BlogHTMLFactory(),
            resourcePaths: ["resources/blog/styles.css"]
        )
    }
}

Если бы мы добавили еще один css, нам потребовалось бы не только добавить его в resourcePaths, но также добавлять в каждый head страниц, чего мне делать совершенно не хотелось в данный момент.

.head(for: item, on: context.site, stylesheetPaths: ["/styles.css", "/splash.css"]),

В итоге сейчас у меня в проекте файловая структура выглядит как-то так:

Зальём свой новый блог на GitHub Pages

Итак, воспользуемся встроенным инструментом Publish для заливки нашего блога. В main.swift добавим

.deploy(using: .gitHub("login/login.github.io", useSSH: false))

У меня получилось

try Blog().publish(
    withTheme: .blog,
    deployedUsing: .gitHub("sparklone/sparklone.github.io", useSSH: false),
    plugins: [.splash(withClassPrefix: "")]
)

Проверяем в последний раз, что все работает. Последний штрих — мне кажется, что нет смысла хранить в git нашего репозитория с темплейтами, как строить сайт, содержимое папки Output, для этого у нас будет отдельный репозиторий, как раз таки login.github.io Если вы согласны — исключим эту папку из .gitignore, добавив туда строку /Output в конце. Если все ок, выполняем в консоли (из папки, где мы выполняли publish new)

git add .
git commit -m "post title or some description"
git push origin
publish deploy

В течение минуты GitHub Pages подхватит ваши изменения, и все появится онлайн.

Поздравляю! 🙂