Getting started with SwiftPM Snippets
SwiftPM Snippets are one of the most powerful features of the Swift Package Manager, and yet two years after their introduction few developers know they exist. This tutorial will explain some of the advantages of using SwiftPM Snippets and show you how to add Snippets to a Swift package.
In this tutorial, we will use the Apple DocC tool to preview and iterate on Snippets locally. The DocC tool itself does not support rendering clickable references within Snippets, however the finished SwiftPM project containing Snippets can be published to a platform like Swiftinit where the Snippets will be rendered with clickable references, allowing readers to interact with the symbols contained within them and navigate to supplemental documentation.
What are Swift Snippets?
Swift Snippets were invented in 2022 by Ashley Garland and first shipped in Swift 5.7. Originally conceived as a way to validate example programs by compiling them, developers have since found a variety of additional applications for them, ranging from testing to documentation to even full-blown prototyping of multi-module setups.
Despite their immense potential, very little documentation exists on how to use Swift Snippets, and awareness of the feature remains surprisingly low.
The outlook for this feature is pessimistic considering the low adoption rate. This might change if Apple invests in better documentation and adopts Snippets in their own repositories.
β A critical look at Swift Snippets by Marco Eidinger
Why use Swift Snippets?
Swift Snippets are incredibly versatile. Some common applications include:
Scratch modules. Snippets are effectively single-file Swift modules, and can be used to quickly prototype code or sketch patterns without the overhead of declaring a proper library target in the package manifest. Snippets are especially useful when the code being prototyped depends on other modules, as Snippets have the ability to
import
modules from the current package.Reproductions. Snippets can be used to reproduce bugs in a minimal environment, with little to no setup required. Although you could also compile your own playgrounds manually with
swiftc -parse-as-library
, it is much easier to run Snippets because they are automatically discovered by theswift run
command.Snippets are especially useful for reproducing compiler bugs. Some developers find it helpful to keep a
.gitignore
βdSnippets/Crashes
directory in their local environment specifically for collecting compiler crashes.Examples. Snippets can be used to provide runnable examples that ship with your package. This allows you to avoid cluttering the package manifest with example targets, and helps you organize your examples in a self-documenting manner.
Documentation. Snippets can be embedded in Markdown documentation and displayed in DocC. This allows you to include live code examples in your documentation that are guaranteed to compile and run.
Snippets can be sliced and embedded as individual code fragments, allowing you to write tutorials that discuss each section of an example program in detail.
Some documentation engines such as Unidoc can render Snippets in the browser with linked identifiers, allowing readers to interact with the symbols in the code.
Adding Snippets to a Swift package
For this tutorial, we will create a package named snippets-example
.
$ mkdir snippets-example
$ cd $_
$ swift package init --name 'Swift Snippets'
This should initialize a new Swift package with a Package.swift
resembling the following.
Package.swift
// swift-tools-version: 5.10
import PackageDescription
let package = Package(
name: "Swift Snippets",
products: [
.library(name: "Swift Snippets", targets: ["Swift Snippets"]),
],
targets: [
.target(name: "Swift Snippets"),
]
)
Manifest.1.swiftRename the library target to SnippetsExample
.
Package.swift
// swift-tools-version: 5.10
import PackageDescription
let package = Package(
name: "Swift Snippets",
products: [
.library(name: "SnippetsExample", targets: ["SnippetsExample"]),
],
targets: [
.target(name: "SnippetsExample"),
]
)
Manifest.2.swiftMake sure the Sources/SnippetsExample
directory contains at least one Swift file. For this tutorial, we will add an empty file named anchor.swift
.
Next, create a new directory named Snippets
at the top level of the package.
$ mkdir Snippets
Inside the Snippets
directory, create a new Swift file named SnippetsExample_I.swift
.
print("Hi Barbie!")
SnippetsExample_I.swiftThe directory structure should now look like this:
π swift-snippets
βββ π Snippets
β βββ π SnippetsExample_I.swift
βββ π Sources
β βββ π SnippetsExample
β βββ π anchor.swift
βββ π Package.swift
βββ π .gitignore
The Swift package now contains the Snippet file and is ready to be built.β
Running Snippets
The swift build
command will automatically discover and compile all Snippets in a package.
Snippets are just modules with discovery enabled, which means you can also build a single Snippet with the --target
flag.
$ swift build --target SnippetsExample_I
You can run a Snippet with the swift run
command.
$ swift run SnippetsExample_I
Building for debugging...
[5/5] Linking SnippetsExample_I
Build complete! (0.97s)
Hi Barbie!
You could also run a Snippet with release optimizations, just like any other executable target.
$ swift run -c release SnippetsExample_I
Building for production...
[17/17] Linking SnippetsExample_I
Build complete! (9.73s)
Hi Barbie!
Embedding Snippets in documentation
Most modern documentation engines support embedding Snippets in Markdown documentation via the @Snippet
block directive.
Letβs create a documentation bundle for the SnippetsExample
target.
$ mkdir -p Sources/SnippetsExample/docs.docc
Create a markdown article named My article.md
in the docs.docc
directory.
My article.md
# My article
This is a simple article that demonstrates how to use snippets in documentation.
@Snippet(path: "Swift Snippets/Snippets/SnippetsExample_I")
My article (1).md.txtIn the example above, we have specified the Snippet to include by path
identity.
Despite its naming, the path
syntax is not a file path. The first component is the name of the package as specified by the PackageDescription/Package/name
field in the manifest. The second component is always the string Snippets
. The third component is the Snippet ID, which is the name of the Snippet file without the .swift
extension.
If the Snippet ID contains special characters, you should pass the ID as-is, without replacing any characters.
Some documentation engines such as Unidoc support referencing Snippets by id
.
My article.md
# My article
This is a simple article that demonstrates how to use snippets in documentation.
@Snippet(id: SnippetsExample_I)
My article (2).md.txtThe project layout should now look like this:
π swift-snippets
βββ π Snippets
β βββ π SnippetsExample_I.swift
βββ π Sources
β βββ π SnippetsExample
β βββ π docs.docc
β β βββ π My article.md
β βββ π anchor.swift
βββ π Package.swift
βββ π .gitignore
Previewing Snippets with DocC
Many developers find DocC helpful for previewing documentation locally. To use DocC, add the swift-docc-plugin to the package manifest.
Package.swift
// swift-tools-version: 5.10
import PackageDescription
let package = Package(
name: "Swift Snippets",
products: [
.library(name: "SnippetsExample", targets: ["SnippetsExample"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"),
],
targets: [
.target(name: "SnippetsExample"),
]
)
Manifest.3.swiftPlease note that while it is possible to build DocC documentation using Xcode, Snippets will not render, due to FB13482049.
You can then launch DocC with the preview-documentation
package subcommand:
$ swift package --disable-sandbox preview-documentation --target SnippetsExample
You can find the rendered article at http://localhost:8080/documentation/snippetsexample/my-article
.
Using Snippet captions
If a Snippet begins with contiguous line comments, those comments will be parsed as Markdown and treated as a Snippet caption. Try adding the following code to a Snippet named SnippetsExample_II.swift
.
SnippetsExample_II.swift
// This is a snippet caption.
//
// You can use normal features in captions, such as links to symbols like ``Int``.
print("Hi Raquelle!")
SnippetsExample_II.swiftWhen embedded, it should look like this:
This is a snippet caption.
You can use normal features in captions, such as links to symbols like Int
.
print("Hi Raquelle!")
SnippetsExample_II.swiftRedacting parts of a Snippet
You can redact portions of a Snippet using slice directives. A slice directive is a line comment token that starts with snippet
followed by a dot and an identifier. The following identifiers have special meanings:
Identifier | Behavior |
---|---|
snippet.hide | Hides the content following the directive. |
snippet.end | Hides the content following the directive. |
snippet.show | Shows the content following the directive. |
Below is an example of a Snippet that uses redactions to hide the import
statements.
SnippetsExample_III.swift
// snippet.hide
import SnippetsExample
// snippet.show
let _:String = """
The import statements at the top of the file are redacted from \
the rendered Snippet.
// snippet.hide
Snippet markers slice the source code at the token syntax level. \
This means strings resembling Snippet markers inside multiline \
string literals do not need to be escaped. However, there is no \
requirement for slices to contain complete lexical blocks. This is
different from the behavior of constructs like `#if`.
// snippet.show
"""
SnippetsExample_III.swiftWhen embedded, it should look like this:
let _:String = """
The import statements at the top of the file are redacted from \
the rendered Snippet.
// snippet.hide
Snippet markers slice the source code at the token syntax level. \
This means strings resembling Snippet markers inside multiline \
string literals do not need to be escaped. However, there is no \
requirement for slices to contain complete lexical blocks. This is
different from the behavior of constructs like `#if`.
// snippet.show
"""
SnippetsExample_III.swift:4Slice indentation
The indentation of the first snippet.show
determines specifies the maximum amount of indentation to remove from the Snippet. In the example below, four spaces of indentation will be removed from the rendered Snippet. Note that the snippet.end
token is required in order to prevent the Snippet from including the final brace, which would have prevented the indentation from being removed.
SnippetsExample_IV.swift
// snippet.hide
import SnippetsExample
enum Main
{
// snippet.show
static
func main() -> Int
{
return 2 + 2
}
// snippet.end
}
SnippetsExample_IV.swiftWhen embedded, it should look like this:
static
func main() -> Int
{
return 2 + 2
}
SnippetsExample_IV.swift:6Using named slices
You can also use named slices to create Snippets with multiple embeddable sections. Itβs a good idea to give slices uppercase names to distinguish them from special slice directives.
Below is an example of a Snippet with a caption and three named slices.
SnippetsExample_V.swift
/// This is a caption to associate with the first named slice.
// snippet.DECLARATION
func f() -> Int
{
// snippet.BODY
return 2 + 2
// snippet.EXIT
}
SnippetsExample_V.swiftHereβs how you might embed the slices in a Markdown article.
My article.md
# My article
This is a simple article that demonstrates how to embed a Snippet with named slices.
@Snippet(path: "Swift Snippets/Snippets/SnippetsExample_V", slice: DECLARATION)
@Snippet(path: "Swift Snippets/Snippets/SnippetsExample_V", slice: BODY)
@Snippet(path: "Swift Snippets/Snippets/SnippetsExample_V", slice: EXIT)
If you do not specify a slice, all slices will be included in the article.
@Snippet(path: "Swift Snippets/Snippets/SnippetsExample_V")
My article (3).md.txtAnd hereβs how the embedded slices should look.
This is a caption to associate with the first named slice.
func f() -> Int
{
SnippetsExample_V.swift:3return 2 + 2
SnippetsExample_V.swift:6}
SnippetsExample_V.swift:8Where to go from here
DocC cannot read the metadata emitted by the Swift compiler that associates the tokens in a Snippet with their definitions, nor can it link to symbols that originate from a packageβs dependencies.
However, other documentation engines such as Unidoc do support rendering Snippets with linked identifiers, and you can easily leverage this feature by publishing your documentation to a platform like Swiftinit. Because the Swift compiler already checks that the Snippets are valid and buildable, most DocC documentation can be uploaded to Swiftinit as-is without additional iteration.
Third-party websites (such as Swift on Server itself) can also leverage this feature through the Swiftinit API.
Related posts
Using OpenAPI Generator with Hummingbird
Learn how to use OpenAPI Generator to create Swift APIs with Hummingbird.
Logging for server-side Swift apps
Discover how to integrate the Logging library into an application, use various log levels, and tailor the unified logging API for backend projects.