Files
felixfoertsch.de/Content/Books/Set up a Publishing System using Swift/1.md

5.9 KiB

Setup a publishing environment

This tutorial explains how to set up a basic publishing environment using Swift. It uses the Swift Package Manager (SPM) and the library Publish to achieve that goal. The whole process avoids using Xcode and I restrict the tooling to the command line. It uses my personal site felixfoertsch.de as the example project.

This tutorial is for people who want to understand the process, the tools, and the file structure involved. It shows the manual steps that the Publish CLI tool can do out-of-the-box. You can install it via Homebrew to create a new project and set up the environment: brew install publish.

Set up the project structure

The first step to create a Swift project is to use the SPM to initialize the folder. By default, the SPM creates projects of type library. Since Publish requires a Swift project of type executeable, we have to tell that to SPM. Additionally we pass in a name for the project, in this case felixfoertsch.de.

mkdir ~/Developer/felixfoertsch.de
cd ~/Developer/felixfoertsch.de
swift package init --type executeable --name "felixfoertsch.de"

The SPM creates the default folder structure for an executable in the current directory. It looks like this:

📁 Sources
📄 .gitignore
📄 Package.swift

Side note: Because it is of type executeable, the SPM skips creating a Tests folder.

Add the required configuration to the Package.swift file

The main configuration file of a Swift project is called Package.swift. Its home is the root folder and it contains the project's metadata, such as its name, dependencies, and targets.

The default file is pretty empty. I removed the default comments for better legibility. It looks like this:

// swift-tools-version:5.10
import PackageDescription

let package = Package(
	name: "felixfoertsch.de",
	targets: [
		.executableTarget(
			name: "felixfoertsch.de"),
	]
)

At the time of writing, the current Swift tools are version 5.10, which is set in the first line of the Package.swift file.

You can see, that we are instantiating a Package object. We are injecting the dependencies -- or rather we are defining them there and during the build process the package takes the configuration into account. It is therefore a list of parameters we are passing to the Package object, separated by commas ,. The order also matters.

Minimum platform version

Because Publish comes with a minimum platform version, we have to set it. This goes on the top level.

platforms: [.macOS(.v12)]

Product: Executable

Even though the product of our publishing project is going to be a static website, within the system we are building an executable Package.

products: [
	.executable(
			name: "felixfoertsch.de",
			targets: ["felixfoertsch.de"]
		)
	],

Dependencies

The next entry is the list of dependencies. We have only one dependency at the moment: we add the Publish library to the Package.swift in the right place on the top level.

dependencies: [
		.package(url: "https://github.com/johnsundell/publish.git", from: "0.9.0")
	],

Since this is also a dependency of our executable target, we have to tell that to the compiler. We are referencing by the package name -- within the target array and for our executable target.

dependencies: [.product(name: "Publish", package: "publish")]

The complete Package.swift file

After adding the dependencies, the complete file should look like this:

// swift-tools-version:5.10

import PackageDescription

let package = Package(
	name: "felixfoertsch.de",
	platforms: [.macOS(.v12)],
	products: [
		.executable(
			name: "felixfoertsch.de",
			targets: ["felixfoertsch.de"]
		)
	],
	dependencies: [
		.package(url: "https://github.com/johnsundell/publish.git", from: "0.9.0")
	],
	targets: [
		.executableTarget(
			name: "felixfoertsch.de",
			dependencies: [.product(name: "Publish", package: "publish")]
		)
	]
)

This concludes the minimal required configuration. The next step is to add the website structure in Swift and its content in Markdown.

Create the Hello, World website

The website structure written in Swift goes into the Sources and Markdown content in the Content folder. We are creating dummy files in both folders to get started.

Create a index.swift file in the Sources folder and a index.md in the Content folder. The file structure should now look like this:

📁 Content
	📄 index.md
📁 Sources
	📄 index.swift
📄 .gitignore
📄 Package.swift

Add # Hello, World! to the index.md file.

The index.swift file is the entry point of the website. Note that we are using an additional dependency called Plot that is required and automatically resolved by the Publish library. It is a library that allows writing HTML in Swift and is used to generate the output.

Add the following code to the index.swift file.

import Foundation
import Publish
import Plot

struct Index: Website {

	enum SectionID: String, WebsiteSectionID {
        case posts
    }

    struct ItemMetadata: WebsiteItemMetadata {
    }

	// Update these properties to configure your website:
	var url = URL(string: "https://felixfoertsch.de")!
	var name = "felixfoertsch.de"
	var description = "A description of felixfoertsch.de"
	var language: Language { .english }
	var imagePath: Path? { nil }
}

// This will generate your website using the built-in Foundation theme:
try Index().publish(withTheme: .foundation)
swift package resolve
swift build
swift run

Glossary

  • Products define the executables and libraries a package produces, making them visible to other packages.
  • Targets are the basic building blocks of a package, defining a module or a test suite. Targets can depend on other targets in this package and products from dependencies.