How I Made This Site!
TLDR; I built this site with Publish, a Swift framework for static site generation by John Sundell
Before I began writing this week's post, I decided to update the look and feel of some of the elements of this website. I centered some images, updated the look of the website title and header and updated some styles.
In the spirit of using Swift everywhere, I decided this week's post will slightly deviate from the previous series about setting the foundation for using Swift in a more general sense, and focus on talking about how I used Swift to make this website.
This will also give me some more time to keep building out some general Swift samples 😇
A New Kind of Publisher
This site was built using a Swift framework called Publish.
At a high level, it works by exposing HTML tags used when building websites, as Swift functions, and then generates actual HTML from that code to show on a web page.
It lets you bring your HTML chops, and website design fundamentals into the Swift world. This gives you the type safety and compiler assistance you get when writing Swift code, but when writing HTML.
I was pleasantly surprised by how organic this felt. On the surface level, It almost feels like I'm making any old XCode project when starting off with the template. But in actuality I'm writing websites.
This also made me realize how you can integrate Swift with other design paradigms and how it can fit in the middle of existing systems and enhance them.
For example, using this framework doesn't let me easily get away with hacks or mistakes I could make writing regular HTML, like misformatting tags or placing tags in locations I shouldn't.
It's also pretty backwards compatible with the current web since you can shove in just about any web snippet into this code if you need to.
This makes it easy to integrate current web stuff if you so chose...but as militant Swift users, we're going to try to keep our best Swift practices in line as much as we can right?
It even includes an adorable little local python server you can use to view and test your changes on your browser, which I cover below.
And what's more, you can literally just pass around your websites with Swift Package manager which is an interesting concept to think about for packaging your website code around. It feels pretty snappy and just works.
In any case, be sure to checkout the repo link above to get set up with the framework if you wan't to try it out. All you really need is a recent version of Swift installed on your system.
Themeing Your Website
I only have positive things to say about John Sundell after working with his suite of swift web framework tools. Naturally the first thing I set out to do after setting up the publish template was to personalize it with a custom theme.
This post details the steps I took in order to accomplish that. For more details on the original implementation documentation, feel free to this out.
Setting up your theme extension
Normally you would rely on a variation of customized style sheets with some scripting languages here or there (especially with frameworks such as React or Rails) to get the job done with traditional static / dynamic web frameworks.
In Publish's case, we're able to still rely on css while also relying on statically typed conventions thanks to the underlying pure swift HTML generation provided by Plot
.
The way we do that is by first creating a Theme
extension with a single statically defined member to function as the theme's name.
We will also be creating a struct that follows the HTMLFactory
protocol to leverage Plot's HTML creation engine.
I will be specifying this theme to interface directly with my site as well.
NOTE: As of this post, Silverpoint was constructed as a personal github pages site and thus is named to follow that naming convention.
import Publish
import Plot
extension Theme where Site == Hggzgithubio {
static var hggz: Self {
Theme(
htmlFactory: HggzHTMLFactory()
)
}
}
You'll notice at this point that we have yet to define our HTMLFactory
struct that follows the conventions set in place for laying out our sites various html pages. We'll cover that next.
Creating your theme's HTML
At its core, all a web browser needs to display content is an html file. You can format your website in any order or style by supplying it the html files of its pages.
The way Publish organizes its content is through the use of 3 logical representations: Sections, items and pages.
As pointed out on the Publish framework's readme, each Section
, Item
, and Page
can define its own set of Content — which can range from text (like titles and descriptions), to HTML, audio, video and various kinds of metadata.
We'll start off by declaring our struct that conforms to the HTMLFactory
protocol.
private struct HggzHTMLFactory: HTMLFactory {
typealias Site = HggzGithubio
func makePageHTML(for page: Page, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML { }
func makeIndexHTML(for index: Index, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML { }
func makeTagListHTML(for page: TagListPage, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML? { }
func makeTagDetailsHTML(for page: TagDetailsPage, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML? { }
func makeItemHTML(for item: Item<HggzHTMLFactory.Site>, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML { }
func makeSectionHTML(for section: Section<HggzHTMLFactory.Site>, context: PublishingContext<HggzHTMLFactory.Site>) throws -> HTML { }
}
The following snippet shows the available functions available from the HTMLFactory
protocol as well as the overriding of the Site
associtedtype so the generic constraints in the struct know that we're referring to our specific site.
This allows us to access some of the site specific properties our site has in the factory class, like our specific sectionId's, our metadata, etc.
You can use these functions to describe how each section's HTML will look, as well as the html in all the various other parts of our site such as the specific items (our posts like this one).
When populating each of the functions, you need to provide it some type safe swift HTML. You write it using Plot's html structures in a format similar to how you would lay out regular html tags. Heres an example of how you can lay out the html for the index of the website.
func makeIndexHTML(for index: Index,
context: PublishingContext<Site>) throws -> HTML {
HTML(
.lang(context.site.language),
.head(for: index, on: context.site),
.body(
.header(for: context, selectedSection: nil),
.wrapper(
.h1(.text(index.title)),
.itemList(
for: context.allItems(
sortedBy: \.date,
order: .descending
),
on: context.site
)
),
.footer(for: context.site)
)
)
}
I decided to rely heavily on the initial Foundation
theme provided by JohnSundell here.
Hopefully once you check it out you'll see how extensible it is to modify it for your needs.
It was in the itemList
helper function that I leveraged my prettyPrint Date extension function to display dates on each post.
static func itemList<T: Website>(for items: [Item<T>], on site: T) -> Node {
return .ul(
.class("item-list"),
.forEach(items) { item in
.li(.article(
.h1(.a(
.href(item.path),
.text(item.title)
)),
.p(.class("date-text"), .text(item.date.prettyPrintDate())),
.br(),
.tagList(for: item, on: site),
.p(.text(item.description))
))
}
)
}
Adding your CSS
You'll notice in the snippet above that the HTML is leveraging 2 'classes'. One for the list ('item-list') and one for the date ('date-text').
Thats no mistake. Those elements are leveraging a css style sheet for those classes.
In order to have Publish utilize your custom css, you must provide your theme a resource path pointing to it. You do this in the static member Theme
declaration above.
extension Theme where Site == Hggzgithubio {
static var hggz: Self {
Theme(
htmlFactory: HggzHTMLFactory(),
resourcePaths: ["Resources/HggzTheme/styles.css"]
)
}
}
I created a Resources
directory in the root directory with the Package.swift
file and followed the convention above with a subdirectory named after the theme and the actual css file.
I also leveraged the css file utilized by the Foundation
theme as a starting point and modified it myself.
Testing your changes
With all this said and done, all that was left was updating the main.swift
file to use your new theme. You should now be able to see your changes on the browser.
The Publish framework has a provided CLI application that handles most of the development needs for any website work.
I developed the site on XCode but should have been perfectly capable handling the development on any other platform.
My personal workflow consisted of me running the local development server by running publish run
in the project directory and running the site binary on XCode everytime I wanted to visualize changes. I was able to view my site on http://localhost:8000.
NOTE: If you notice that some css changes weren't immediately reflected after refreshing the browser page. you can get around this by doing a hard refresh in the browser. I used Safari and Chrome to test my changes and didn't really run into these issues much.