Простой блог на Swift c помощью Publish
В этой статье я расскажу о том, что такое Publish, как с его помощью создавать сайты, а так же, как размещать их на бесплатном хостинге от GitHub под названием GitHub Pages.
Prehistory instead of introduction | Предыстория вместо вступления
Написать эту статью, собственно как и на саму идею создания собственного блога - меня подтолкнул разговор с одним из моих знакомых.
Однажды, в дни осенней Covid'ной изоляции, когда заспорили мы в Telegram с одним моим знакомцем, синьором Front-End Developer'ом, о том, что Swift - так себе язык, и на нём "на Swift'е сайты не пишутъ".
Мне сразу вспомнилась фраза, из старого советского мультфильма про Левшу - Н. Лескова. Про ружьё и толченый кирпич: - "Эх, англичане ружья кирпичом не чистют". Кстати, если интересно, мульт можно посмотреть здесь.
И вот, слоняясь по бескрайним просторам бесконечного Web'а, наткнулся я, на очень замечательную и интересную вещь под названием Publish, написанную одним польским программистом, по имени John Sundell (Джон Санделл). О ней и пойдет мой дальнейший повествовательный рассказ. Кстати, как рассказывает сам Сандел (Publish is used to build all of swiftbysundell.com). Его блог полностью создан при помощи Publish. И тут меня осенило, а не написать ли мне свой сайт, да еще и на Swift, да чтоб товарищу показать, что-бы не 3.141592653... <censored>.
И так, в данной статье мы рассмотрим:
- Что такое Publish и как с ним работать?
- Создание репозитория на GitHub для нашего сайта.
- Как размещать сайты на GitHub Pages при помощи Publish
Так что же такое Publish ?
Publish - это генератор статических сайтов написанный на Swift, который позволяет строить всевозможную логику при генерации сайта. Вы пишите странички при помощи облегченного языка разметки под названием Markdown и Swift строит вам сайт с ... (with blackjack and ... <censored>) тегами, картой сайта, RSS и много чего еще на что нам только хватит фантазии. А это как раз то, что нам нужно.
Установка Publish.
Для успешного использования Publish, убедитесь что в вашей системе установлен Swift версии 5.2 (или выше). Если вы используете Mac, так-же убедитесь что xcode-select указывает на Xcode который включает в себя требуемую версию Swift, и что вы используете macOS Catalina (10.15) или новее. Обратите внимание, что Publish официально не поддерживает бета-версии программного обеспечения, включая бета-версии Xcode и macOS, или невыпущенные версии Swift.
Для начала в своей домашней директории, создадим временный каталог под названием ~/tmp, например, вы можете создать любой. Это каталог, куда мы будем скачивать исходники Publish. После установки утилиты его можно удалить, он нам больше не понадобится. Далее клонируем исходники себе на диск и устанавливаем. Для этого в терминале – выполним следующие команды.
$ mkdir tmp
$ cd tmp
$ git clone https://github.com/JohnSundell/Publish.git
$ cd Publish
$ make
После того, как все установится - проверяем, выполнив комманду:
$ which publish
/usr/local/bin/publish
Все, Publish установлен и директория ~/tmp нам больше не нужна, можно смело ее удалять, для этого выполним команду: rm -rf ~/tmp
и снова перейдем в домашний каталог командой cd ~
или просто ~
.
Создание и генерация сайта.
Создадим еще одну директорию, где мы будем размещать наш сайт, например: ~/Projects/myBlog и перейдем в нее:
$ mkdir -p ~/Projects/myBlog && cd "$_"
Теперь, если выполнить команду publish --help
или просто publish
без параметров, мы увидим список доступных нам параметров:
$ publish --help
Publish Command Line Interface
------------------------------
Interact with the Publish static site generator from
the command line, to create new websites, or to generate
and deploy existing ones.
Available commands:
- new: Set up a new website in the current folder.
- generate: Generate the website in the current folder.
- run: Generate and run a localhost server on default port 8000
for the website in the current folder. Use the "-p"
or "--port" option for customizing the default port.
- deploy: Generate and deploy the website in the current
folder, according to its deployment method.
Находясь в директории ~/Projects/myBlog выполним команду publish new
, она создаст структуру нашего сайта. (собственно исходники, из которых мы и будем в дальнейшем генерировать наш сайт) .
$ publish new
✅ Generated website project for 'MyBlog'
Run 'open Package.swift' to open it and start building
Нам предлагается открыть наш проект и начать строить сайт, команда open
, в macOS откроет нам файл Package.swift в Xcode (если вы конечно не настроили другой редактор по умолчанию для файлов с расширением .swift). Собственно Xcode и не обязателен. Вы можете например установить это все в Linux и открыть этот проект в любом своем любимом текстовом редакторе или IDE (с блэкджеком и ... <censored>), с поддержкой синтаксиса, пред просмотра Markdown и тд, и просто, после внесения изменений, каждый раз запускать команду publish generate
. Это почти то же самое, если в Xcode нажать кнопку Выполнить (Run) на панели инструментов, или сочетание клавиш
Давайте наконец сгенерируем наш сайт и запустим веб-сервер, выполнив для этого команду publish run
, по умолчанию она использует 8000 порт - это можно исправить, если запустить ее с параметром -p <номер_порта>
или --port <номер порта>
, например: publish run -p 80
.
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
И наконец, после некоторого ожидания, пока Publish подтянет все зависимости и соберет наш сайт, переходим по ссылке http://localhost:8000 и Look at that! Look at that!:
НИЧОСИ! Hooray! У нас теперь есть свой сайт. Просто, не правда ли?
Давайте теперь создадим репозиторий для нашего сайта, что-бы сохранять изменения которые мы будем вносить в дальнейшем и в случае чего, мы могли также откатить эти изменения.
Создание репозитария для GitHub Pages
Если вы не знакомы с git, и не знаете как его настраивать, перед выполнением следующего шага рекомендуется почитать: Git Book
Внимание! Название репозитория должно быть в формате {login}.github.io.
Перейдем по ссылке https://github.com/new
Готово! Tеперь настроим локальный репозиторий и свяжем его c GitHub. Для этого, в терминале, находясь в папке нашего сайта ~/Projects/myBlog выполним команды как на картинке выше:
$ git init
Так, мы создали локальный репозиторий, теперь давайте исключим папку Output, нет смысла ее индексировать т.к. ее содержимое, мы будем 'деплоить' на {login}.github.io, Добавим /Output в .gitignore
$ git add .
$ git commit -m "First commit of our Blog"
$ git branch -M main
$ git remote add origin https://github.com/{login}/{login}.github.io.git
$ git push -u origin main
С git разобрались, переходим к настройкам.
Настройки
Откроем еще одну вкладку в терминале нажав сочетание клавиш
$ open Package.swift
Откроем проект и рассмотрим структуру каталогов которую нам создал Publish
Структура нашего проекта состоит из :
- Content (исходники страниц сайта в формате markdown)
- Output (сгенерированные ресурсы - HTML, стили и тд.)
- Resources (стили, картинки, медиа)
- Sources (исходники шаблоны на Swift)
Если потом еще посмотреть в терминале командой ls -a
, то будет видно, что там есть и скрытые папки со всякими зависимостями, кешами и тд, в дальнейшем, нам еще пригодится каталог .build, а пока ...
Файл Package.swift - это менеджер пакетов Swift, сюда мы будем добавлять всякие пакеты, прописывать зависимости и Xcode будет автоматически их подгружать, или вручную в терминале командой publish generate
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "MyBlog",
products: [
.executable(
name: "MyBlog",
targets: ["MyBlog"]
)
],
dependencies: [
.package(name: "Publish", url: "https://github.com/johnsundell/publish.git", from: "0.6.0")
],
targets: [
.target(
name: "MyBlog",
dependencies: ["Publish"]
)
]
)
Файл main.swift - это настройки нашего сайта, там настраиваются всякие, описания, метаданные, подключаются всевозможные плагины, темы.
import Foundation
import Publish
import Plot
// This type acts as the configuration for your website.
struct MyBlog: Website {
enum SectionID: String, WebsiteSectionID {
// Add the sections that you want your website to contain here:
case posts
}
struct ItemMetadata: WebsiteItemMetadata {
// Add any site-specific metadata that you want to use here.
}
// Update these properties to configure your website:
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 }
}
// This will generate your website using the built-in Foundation theme:
try MyBlog().publish(withTheme: .foundation)
В общем сайт мы создали, хоть и очень простой . Но ведь мы собственно еще пока ни единой строчки кода не написали, занимались установкой тысызыть.
Deploy*
Deploy (от англ. deployment) - разворачивание, в данном случае – установка нашего локального сайта на удаленный сервер GitHub.
Пока что Publish ничего не знает о нашем репозитории, давайте его Publish загружать сгенерированные странички в созданный на GitHub репозиторий, для этого в нашем генераторе Publish существует DeploymentMethod.
В файле Sources/main.swift заменим строку:
// This will generate your website using the built-in Foundation theme:
try MyBlog().publish(withTheme: .foundation)
на
// This will generate your website using the built-in Foundation theme:
try MyBlog().publish(
withTheme: .foundation,
deployedUsing: .gitHub("{login}/{login}.github.io", useSSH: false)
)
и в терминале выполним команду:
$ publish deploy
После этого возвращаемся на github.com и в настройках нашего репозитория находим пункт GitHub Pages, где в качестве ресурса выбираем ветку master и нажимаем кнопку сохранить.
Через некоторое время наш сайт будет доступен по адресу https://{login}.github.io
Кастомизация*
Кастомизация (от англ. to customize — настраивать, изменять)
Давайте немного изменим наш сайт, добавим разделы, страницы, настроим тему, сделаем подсветку синтаксиса, мы же программисты, нам без подсветки никуда.
Итак, добавим раздел о себе, создав файл about.md в папке Content и добавим туда немного информации о себе, так же в директорию Resources/myBlog добавим файл с картинкой (с нашей аватаркой).
Но, Publish опять-таки (по умолчанию), ничего не знает об этом, он, конечно сгенерирует файл Output/about/index.html по имени markdown файла и страница будет доступна по адресу http://localhost:8000/about. Но это – не совсем то, что нам нужно, нам же еще нужна ссылка на нее (пункт меню).
Давайте расскажем Publish об этом, добавив case about
в файл main.swift
enum SectionID: String, WebsiteSectionID {
// Add the sections that you want your website to contain here:
case posts
case about
}
Снова генерируем сайт и...
Oh my God! We have done it! Божечки, мы сделали это!
Теперь создадим Theme (тему) для нашего блога на основе стандартной темы Foundation
Создание темы
Помните я говорил про скрытый каталог .build в корне каталога нашего сайта? Ну что, настало его время. В терминале находясь в каталоге ~/Projects/myBlog выполним команды:
$ cp .build/checkouts/publish/Sources/Publish/API/Theme+Foundation.swift Sources/MyBlog/Theme+myBlog.swift
$ cp .build/checkouts/publish/Resources/FoundationTheme/styles.css Resources/MyBlog/styles.css
Тем самым скопировав тему и стили поmумолчанию для нашего сайта, которые в дальнейшем мы и будем изменять:
Заставим Publish использовать их вместо стандартных, изменив в файле Sources/MyBlog/Theme+myBlog.swift строки:
import Plot
public extension Theme {
/// The default "Foundation" theme that Publish ships with, a very
/// basic theme mostly implemented for demonstration purposes.
static var foundation: Self {
Theme(
htmlFactory: FoundationHTMLFactory(),
resourcePaths: ["Resources/FoundationTheme/styles.css"]
)
}
}
на
import Plot
import Publish
public extension Theme {
/// The default "Foundation" theme that Publish ships with, a very
/// basic theme mostly implemented for demonstration purposes.
static var myblog: Self {
Theme(
htmlFactory: MyBlogHTMLFactory(),
resourcePaths: ["Resources/MyBlog/styles.css"]
)
}
}
и еще строку:
private struct FoundationHTMLFactory<Site: Website>: HTMLFactory {
заменим на:
private struct MyBlogHTMLFactory<Site: Website>: HTMLFactory
А так же в файле main.swift изменим строку:
try MyBlog().publish(withTheme: .foundation,
на строку:
try MyBlog().publish(withTheme: .myblog,
Добавление плагина подсветки синтаксиса:
Давайте теперь добавим подсветку синтаксиса для нашего блога, я выбрал Pygments, этот плагин поддерживает подсветку, около 500 языков. Я думаю для большинства задач – этого будет более чем достаточно.
Теперь в файл Package.swift добавим наш пакет
dependencies: [
.package(name: "Publish", url: "https://github.com/johnsundell/publish.git", from: "0.6.0"),
.package(url: "https://github.com/Ze0nC/SwiftPygmentsPublishPlugin", .branch("master"))
а так же зависимость SwiftPygmentsPublishPlugin
.target(
name: "MyBlog",
dependencies: ["Publish", "SwiftPygmentsPublishPlugin"]
в файле main.swift пропишем import SwiftPygmentsPublishPlugin
и plugins: [.pygments()]
import SwiftPygmentsPublishPlugin
try Blog().publish(
withTheme: .blog,
deployedUsing: .gitHub("nazares/nazares.github.io", useSSH: false),
plugins: [.pygments()]
)
Так же нужно в файле Theme+Blog.swift в head каждой страницы после свойства on:context.site
дописать путь к нашим стилям stylesheetPaths:["/style.css"]
, вот так:
.head(for: index, on: context.site, stylesheetPaths: ["/styles.css"])
.head(for: section, on: context.site, stylesheetPaths: ["/styles.css"])
.head(for: item, on: context.site, stylesheetPaths: ["/styles.css"])
.head(for: page, on: context.site, stylesheetPaths: ["/styles.css"])
После этого, нужно в файл style.css добавить стили для подсветки, например: pygments-native.css
Еще больше тем для Pygments можно найти здесь.
В последний раз соберем наш проект, убедимся, что все работает так как нужно, после чего выполним:
$ git commit -m "Blog release v0.1"
$ git push -u origin main
$ publish deploy
И насладимся трудами нашего творчества!
Cпасибо за внимание, позитива и добра!