Class
Saga
class SagaThe main Saga class, used to configure and build your website.
try await Saga(input: "content", output: "deploy")
// All files in the input folder will be parsed to html, and written to the output folder.
.register(
metadata: EmptyMetadata.self,
readers: [.parsleyMarkdownReader],
writers: [.itemWriter(swim(renderPage))]
)
// Run the steps we registered above.
// Static files (images, css, etc.) are copied automatically.
.run()
Mentioned In
- Getting Started with Saga
- Architecture
- Advanced Usage
- Migrating to Saga 3
- Generating a Sitemap
- Compiling Tailwind CSS
- HTML Minification
- Adding Search
- Internationalization (i18n)
- Implementing Shortcodes
- Custom Feed Formats
Initializers
init(
input: Path,
output: Path = "deploy",
fileIO: FileIO = .diskAccess,
originFilePath: StaticString = #filePath
) throwsInstance Properties
var allItems: [any AnyItem] { get }All Items across all registered processing steps.
var buildReason: BuildReason { get }Why the current build was triggered.
let inputPath: PathThe path that contains your text files, relative to the rootPath. For example “content”.
let outputPath: PathThe path that Saga will write the rendered website to, relative to the rootPath. For example “deploy”.
let rootPath: PathThe root working path. This is automatically set to the same folder that holds Package.swift.
Instance Methods
@discardableResult
@preconcurrency
func afterWrite(_ hook: @escaping (Saga) async throws -> Void) -> SelfRegister a hook that runs after the write phase of each build cycle.
For a search indexing example, see AddingSearch.
Use this for post-build steps like search indexing:
try await Saga(input: "content", output: "deploy")
.register(...)
.afterWrite { saga in
// run pagefind, etc.
}
.run()
@discardableResult
@preconcurrency
func beforeRead(_ hook: @escaping (Saga) async throws -> Void) -> SelfRegister a hook that runs before the read phase of each build cycle.
For a CSS compilation example, see TailwindCSS.
Use this for pre-build steps like CSS compilation:
try await Saga(input: "content", output: "deploy")
.beforeRead { _ in
try await tailwind.run(input: "content/static/input.css", output: "content/static/output.css")
}
.register(...)
.run()
@discardableResult
func i18n(
locales: [SagaLocale],
defaultLocale: SagaLocale,
prefixDefaultLocaleOutputFolder: Bool = false,
localizedOutputFolders: [String : [SagaLocale : String]] = [:]
) -> SelfConfigure internationalization support.
When enabled, Saga expects content to be organized in locale-prefixed folders (e.g. en/articles/, nl/articles/). Each register() call automatically fans out into per-locale processing steps.
For a complete walkthrough, see Internationalization.
try await Saga(input: "content", output: "deploy")
.i18n(
locales: ["en", "nl"],
defaultLocale: "en",
localizedOutputFolders: ["articles": ["nl": "artikelen"]]
)
.register(
folder: "articles",
metadata: ArticleMetadata.self,
readers: [.parsleyMarkdownReader],
writers: [.itemWriter(swim(renderArticle))]
)
.run()
Parameters
localesThe supported locales (e.g.
["en", "nl"]).defaultLocaleThe default locale. Its content is written to the root unless
prefixDefaultLocaleOutputFolderistrue.prefixDefaultLocaleOutputFolderWhether the default locale should also get a subdirectory prefix. Defaults to
false.localizedOutputFoldersA mapping of content folder → [locale → output folder]. Allows output folder names
to differ from content folder names per locale. Locales not in the map use the original folder name.
@discardableResult
func ignore(_ pattern: String) -> SelfDeprecated
@discardableResult
func ignoreChanges(_ pattern: String) -> SelfAdd a glob pattern to ignore during file watching in dev mode.
Use this to prevent unnecessary rebuilds when certain files change:
try await Saga(input: "content", output: "deploy")
.ignoreChanges("output.css")
.ignoreChanges("*.tmp")
.register(...)
.run()
@discardableResult
@preconcurrency
func postProcess(_ transform: @escaping (String, Path) throws -> String) -> SelfApply a transform to every file written by Saga.
The transform receives the rendered content and relative output path. Multiple calls stack: each wraps the previous write.
For a minification example, see HTMLMinification.
try await Saga(input: "content", output: "deploy")
.register(...)
.postProcess { html, path in
minifyHTML(html)
}
.run()
@discardableResult
func run() async throws -> SelfExecute all the registered steps.
Type Properties
static var isCLI: Bool { get }This is true when the SAGA_CLI environment variable is set (which saga dev does automatically). Used internally.
static var isDev: Bool { get }This is true when the SAGA_DEV environment variable is set (which saga dev does automatically). Use it to skip expensive work during development:
.postProcess { html, _ in
Saga.isDev ? html : minifyHTML(html)
}
Type Methods
@preconcurrency
static func atomFeed<Context>(
title: String,
author: String? = nil,
baseURL: URL,
summary: ((Item<Context.M>) -> String?)? = nil,
image: ((Item<Context.M>) -> String?)? = nil,
dateKeyPath: KeyPath<Item<Context.M>, Date> = \.lastModified
) -> ((Context) -> String) where Context : AtomContextA renderer which creates an Atom feed for Items.
For JSON Feed and other formats, see CustomFeedFormats.
Parameters
titleThe title of the feed, usually your site title. Example: Loopwerk.io.
authorThe author of the articles.
baseURLThe base URL of your website, for example https://www.loopwerk.io.
summaryAn optional function which takes an
Itemand returns its summary.imageAn optional function which takes an
Itemand returns its image URL (absolute or relative to baseURL).dateKeyPathA keypath to the date property to use for the
field. Defaults to \.lastModified.
Return Value
A function which takes a rendering context, and returns a string.
static func hashed(_ path: String) -> StringReturns a cache-busted file path by inserting a content hash into the filename.
link(rel: "stylesheet", href: Saga.hashed("/static/output.css"))
// → "/static/output-a1b2c3d4.css"
static func redirectHTML(to url: String) -> StringReturns an HTML page that redirects the browser to the given URL.
Uses both a <meta http-equiv="refresh"> tag and a canonical link for immediate client-side redirection with proper SEO signaling.
- Parameter url: The destination URL to redirect to.
Return Value
A complete HTML document that redirects to the given URL.
@preconcurrency
static func redirectHTML(to url: String) -> (PageRenderingContext) -> StringA renderer which creates an HTML page that redirects the browser to the given URL.
Uses both a <meta http-equiv="refresh"> tag and a canonical link for immediate client-side redirection with proper SEO signaling.
- Parameter url: The destination URL to redirect to.
Return Value
A renderer for use with createPage(_:using:).
.createPage("old-path/index.html", using: Saga.redirectHTML(to: "/new-path/"))
@preconcurrency
static func sitemap(
baseURL: URL,
filter: ((Path) -> Bool)? = nil
) -> (PageRenderingContext) -> StringA renderer which creates an XML sitemap from all generated pages.
When i18n is configured, the sitemap includes xhtml:link alternate entries for pages that have translations in other locales, following Google’s multilingual sitemap specification.
For a complete walkthrough, see GeneratingSitemaps.
Parameters
baseURLThe base URL of your website, for example
https://www.example.com.filterAn optional filter to exclude certain paths from the sitemap.
The filter receives the relative output path (e.g."404.html"or"search/index.html").
Returntrueto include the path,falseto exclude it.
Return Value
A renderer for use with createPage(_:using:). Place the sitemap as the last createPage call so it can see all generated pages before it.
.createPage("sitemap.xml", using: sitemap(
baseURL: URL(string: "https://www.example.com")!,
filter: { $0 != "404.html" }
))
Relationships
Inherits From
Conforms To
Swift.Sendable