Creating a Blog with Publish: Getting Started

Published on

Get weekly handpicked updates on Swift and SwiftUI!

Publish is a static site generator designed specifically for Swift developers. It uses Swift to build the entire website and supports themes, plugins, and a host of customization options.

Introduction

Developer John Sundell

John Sundell, the developer behind Publish, has been dedicated to producing high-quality articles, podcasts, and videos about Swift over the years. Most of his work is published on his independently operated Swift by Sundell. He developed Publish to create and manage his own site.

During the development of Publish, he also open-sourced a number of fundamental libraries, such as Ink (an efficient Markdown parser), Plot (a DSL for creating HTML, XML, RSS), Sweep (an efficient string scanning library), Codextended (enhancements for Codable), etc. These not only together built a powerful Publish but are also outstanding open-source libraries in their respective fields.

Why I Use Publish

A year ago, when I revived my blog, I used Hexo. Hexo has a solid user base in China, with many excellent tutorials online and many developers contributing their themes and plugins. Despite being quite satisfied with Hexo, as I mainly use Swift and am not very familiar with JavaScript, it was difficult for me to make deeper customizations or modifications to Hexo.

As a developer (even an amateur one), I always wanted to have more comprehensive control over my projects, so Publish, being fully developed in Swift, became my first choice.

As I delved deeper into the process of rebuilding Elbow’s Swift Notebook using Publish, I felt I had made the right choice. Publish allowed me to use the mindset and logic of developing a regular app to create a site and efficiently achieve the various customizations and changes I wanted.

The Reason for Writing This Article

At the time of writing, Publish has already garnered 3.1 K stars on Github. However, there aren’t many introductions to Publish on the internet, especially lacking information and exchanges about template customization and plugin development. The number of results for related plugins and templates on Github is also very limited.

This situation is partly due to Publish being relatively new, having a smaller user base, and the Swift community being smaller. However, I believe the following situation has also exacerbated this: unlike other static site generators, in Publish, developers can implement various functions with small pieces of code. This fragmented code is actually not conducive to sharing and not easy to search; also, as the implementation of templates and website functions in Publish is deeply bound, the utility of sharing templates alone is low.

But it is exactly these characteristics that make Publish appealing.

In light of this, I will introduce Publish in three articles (Getting Started, How to Write Templates, How to Write Plugins) and hope that more domestic Swift developers or enthusiasts can learn about and use this excellent tool.

To help everyone get started quickly, I have placed the code used for this site (including templates, custom plugins, etc.) on Github for easy understanding and mastery of Publish through the code.

Quick Start Guide

How to Install Publish

Like many other static site generators, Publish provides a CLI. You can quickly complete a series of operations such as creating templates, updating content, and remote publishing via the command line. Publish currently runs on Mac and Linux, and given its low dependency on operating systems, it is expected to appear on the Windows platform later.

Installing via brew on Mac

Bash
$brew install publish

Installing from Source Code

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

Creating Your First Project

Let’s create a new Blog project

Bash
$mkdir myblog
$cd myblog
$publish new

Publish will create the required project template in the myblog directory. Its basic composition is roughly as follows:

|-- myblog
|   |-- Content
|           |–– posts
|                 |–– first-post.md
|                 |–– index.md
|           |–– index.md
|   |-- Resources
|   |-- Sources
|           |–– Myblog
|                  |–– main.swift
  • Content

    Put the markdown files of articles, pages, etc., that you want to publish on the website here.

  • Resources

    Some resources needed for the project theme

, such as CSS, images, etc., are currently empty. After your first publication, you will see it contains the default FoundationTheme’s styles.css file.

  • Source

    The code describing the site. In main.swift, the basic properties of the site and the creation workflow are defined.

Compile and Run

Swift is a compiled language, so your site’s code needs to be compiled and run on your machine after each change to complete content generation, but all this only requires one command.

Let’s let Publish do the above work and start the built-in Web Server for us to browse the newly created project.

Bash
$publish run

On the first run, Publish will automatically fetch the required libraries from Github, so please wait a few minutes.

Bash
$ 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

Now you can access your new site by visiting http://localhost:8000 in your browser.

All content of the website is generated and placed in the Output directory. You just need to upload its contents to your server, or through simple configuration, like:

Swift
.unwrap(.gitHub("fatbobman/fatbobman.github.io", useSSH: true), PublishingStep.deploy)

Then using

Bash
$publish deploy

you can publish the content to your github.io (specific configuration explained later).

At this point, you add the following file second-post.md in Content - posts

---
date: 2021-01-29 19:58
description: My second article
tags: publish,swift 
---
# Hello World

Executing publish run again, you can see the new article has already appeared on the page.

Quickly Get Started Using My Provided Template

First, make sure the Publish CLI is installed

Bash
$git clone https://github.com/fatbobman/PublishThemeForFatbobmanBlog.git
$cd PublishThemeForFatbobmanBlog
$publish run

More About Publish

This section introduces several concepts in Publish, crucial for understanding template customization and feature expansion.

Site

When you create a project with Publish, it automatically generates a Swift Package. The website’s generation and deployment configuration are completed through this package using native and type-safe Swift code.

Below is the content of main.swift (the entry file of the package) generated using publish new myblog.

Swift
// Definition of Site
struct Myblog: Website {
    enum SectionID: String, WebsiteSectionID {
        // Add your required Sections
        case posts
    }

    struct ItemMetadata: WebsiteItemMetadata {
        // Add any specific site metadata you want to use here
    }

    // Some configurations of 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 }
}
// You can access the properties in the Site from templates or plugins

// Entry point for execution. Currently using the default template and Publish's preset generation, export, and publishing process.
// Definition of the workflow, more details in Step
try Myblog().publish(withTheme: .foundation)

Site not only defines the basic configuration information of the website project but also defines the workflow from generation to publication.

Section

Each Section creates a subdirectory in Output. In main.swift, Section is defined using enumeration. You can consider a Section as a container of a group of Items (articles) or merely pointing to a Page (a non-Item specific page). To use a Section to manage a group of articles, you just need to create a subdirectory under Content with the same name as that Section, as seen in the example with posts and project.

Definition of Section:

Swift
enum SectionID: String, WebsiteSectionID {
     case posts // New projects only have this by default, corresponding to the content-posts directory
     case links // You can add your own, place the articles belonging to this section in the corresponding directory
     case about
}

In Elbow’s Swift Notebook, each Section also corresponds to an option in the upper navigation area. Section can have various uses, which will be discussed more in the template customization chapter.

Item

Articles saved in Content--corresponding Section directory. Each Item corresponds to a Section without special settings; it belongs to whichever Section directory it is saved in. If a Section does not need to act as a file container, you can directly create an md file with the same name as the Section in Content. In the example template I provided, about is of this form.

Page

Articles that do not belong to any Section. Page will not appear in the item list of Section and usually not in the index (homepage) list either. Create a Page by adding files in the following structure in a directory under content that does not belong to any Section. Note the relationship between the creation path and access path of Page.

| content -- 404
|             | -- index.md

You can view it by visiting http://localhost:8000/404/index/

Page provides us with a way to build free-form pages. For example, you can use it to create articles that don’t need to be displayed in the list, or as demonstrated in the example template, create a 404 page😅.

Content

Specifically refers to the content attribute in Item and Page. As a content set, its range includes text (such as titles and descriptions), tags, converted HTML code, audio, video, and various metadata. Metadata needs to be indicated at the top of the Markdown article.

Section also has Content, corresponding to the index.md you create in the Content subdirectory for that Section (if necessary).

In the code, you will also encounter another type of Content, precisely PublishingContext. It contains all the information of the entire project (Site, Sections, Items, Tags, etc.), and by passing its instance to Step or Plugin, various tasks like modifying or configuring the website can be completed.

Metadata

Metadata of the Markdown file, marked at the top of the article (Markdown) file. It is divided into two categories: one is preset by Publish, and the other is custom-defined in ItemMetadata in Site.

---
date: 2021-01-29 19:58
description: A description of my first post.
tags: first, article
author:

 fat 
image: https://cdn.fatbobman.com/first-post.png
---

Preset

  • title: Text title. If not set, Publish will directly find the first Top-level Head # in the article text as the title.
  • description: Article description.
  • date: Article creation date. If not set, the file’s modificationDate is used directly.
  • tags: Article tags, each article can have multiple tags, adding another dimension to the organization of articles.
  • image: Image address, e.g., for displaying a theme image of the article in the Item list (needs to be defined in the template).
  • audio: Audio data.
  • video: Video data. The definition of audio and video is too complex; if really needed, it can be defined by yourself.

Custom

Swift
struct ItemMetadata: WebsiteItemMetadata {
    // Add any site-specific metadata that you want to use here.
    var author:String
}

If the preset metadata is not enough for your needs, you can define it yourself in ItemMetadata.

Difference Between Two Types of Metadata

Preset metadata exists as properties in Publish.

Swift
for item in content.allItems(sortedBy: \.date){
         print(item.title)
 }

Custom metadata must be accessed as follows:

Swift
let author = (item.metadata as! Myblog.ItemMetadata).author

It’s more convenient to use in the template:

Swift
.text(item.metadata.author)

In Publish, while preset metadata is not mandatory for Item, custom metadata must be added in the markdown document. index.md and Page can be without metadata (whether custom or not).

Theme

Publish uses Plot as its HTML theme description engine, allowing developers to define pages in a very Swift-like way. If you have used DSL-type development methods, it will feel very familiar. For example, the code below defines the layout presentation of the Section List.

Swift
func makeSectionHTML(
        for section: Section<Site>,
        context: PublishingContext<Site>
    ) throws -> HTML {
        HTML(
            .lang(context.site.language), // Web page language
            .head(for: section, on: context.site), // Header files, metadata, CSS, etc.
            .body(
                .header(for: context, selectedSection: section.id), // The upper area of the site, including Logo and navigation bar in the example
                .wrapper(
                    .itemList(for: section.items, on: context.site) // Article list
                ),
                .footer(for: context.site) // Bottom copyright area
            )
        )
    }

The layout details defined in the Theme still need further settings in CSS.

In the code above, header, wrapper, etc., are referred to as Node in Plot. Besides using a large number of Node preset by Publish, we can use our own written Node.

More about the Theme will be detailed in Creating a Blog with Publish (Part II).

Step

Publish uses a workflow (Pipeline) approach to clearly define the operation process of each stage, from file reading, markdown parsing, HTML generation, RSS export, etc. In main.swift generated by publish new, although only one statement is used,

Swift
try Myblog().publish(withTheme: .foundation)

it corresponds to the following series of operation steps behind it:

Swift
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)
         ]

In most cases, we will explicitly mark each operation step. Each step in Publish is called a Step. Publish has already preset many Steps for developers to use. We can also inject our own created Step into an appropriate position in the workflow to achieve more functions.

Each Step will be passed a PublishContent instance, which can be used to change various elements in the website (including files, folders, Items, Pages, etc.). For the difference between PublishContent and Content, see above.

Plugins

Swift
.installPlugin(.highlightJS()), // Syntax highlighting

The code above in the Publish workflow uses a Step named installPlugin to install the highlightJS plugin.

Developing a Plugin is very similar to a Step in Publish, both receiving an instance of PublishContent to perform relevant tasks.

For example, you can use a Step for operations with side effects, and a Plugin for tasks like injecting Modifier (for custom Markdown parsing).

From a functionality standpoint, both custom code in Step and Plugin can accomplish each other’s tasks. Therefore, the choice between using a Step or a Plugin when creating a feature extension depends on personal preference.

Detailed explanations on customizing Step and Plugin will be provided in “Creating a Blog with Publish (Part III)“.

Who is Publish For?

Publish, compared to mainstream static site generators, has some shortcomings, such as lower community activity, shorter development time, and a smaller user base of the Swift language. Currently, Publish is more suitable for:

  • Developers or enthusiasts using the Swift language.
  • Those lacking experience in Javascript, or who prefer a Javascript-free style.
  • Those seeking efficient and concise web presentation.
  • Those who want to fully grasp each aspect of their website and implement features step by step by themselves.
  • Early adopters and experimenters.

Next

In “Creating a blog with Publish: Theme” I will explore the development of Themes, and in “Part III,” learn how to extend Publish’s functionality through various means.

If you’re already interested, set up your github.io site on Github and deploy your own blog with Publish’s one-click deployment.

I'm really looking forward to hearing your thoughts! Please Leave Your Comments Below to share your views and insights.

Fatbobman(东坡肘子)

I'm passionate about life and sharing knowledge. My blog focuses on Swift, SwiftUI, Core Data, and Swift Data. Follow my social media for the latest updates.

You can support me in the following ways