PlotVapor is a small package that allows easy rendering of Plot-generated HTML within the Vapor server-side Swift web framework.
This package only adds a small bridging layer that allows Plot
to hook into Vapor
, which keeps this library extremely lightweight. Usage of this library mirrors LeafKit's LeafRender class for additional familiarity.
⚠️ The code in this library has been provided as-is and is intended primarily as a reference for educational purposes. It may lack the documentation, stability, and/or functionality necessary to support usage in production applications. As such, I highly recommend simply copying files from this codebase directly rather than pulling it in as a dependency to avoid any breaking changes impacting your workflow. Feel free to open an issue/PR or reach out if you have any questions or feedback.
Before continuing, you should review the Plot README and have a solid understanding of how the library works.
We can add a new route for the /home
path within the configureRoutes(_:)
method and render a sample HTML
object that doesn't do anything fancy.
func configureRoutes(_ app: Application) throws {
app.get("home") { req -> EventLoopFuture<View> in
let html = HTML(
.head(
.title("My website"),
.stylesheet("styles.css")
),
.body(
.div(
.h1("Hello, world!"),
.p("Writing HTML in Swift is pretty great!")
)
)
)
return req.plot.render(html)
}
}
Since nobody wants a massive routing file in practice, this library also provides the Page
and PageTemplate
protocols to help with keeping the codebase organized and maintainable.
Conform to the Page
protocol to quickly and easily define a page.
struct MyPage: Page {
let title = "My website"
var content: Component {
Div {
H1("Hello, world!")
Paragraph("Writing HTML in Swift is pretty great!")
}
}
}
Then, render it wherever you define your routes.
func configureRoutes(_ app: Application) throws {
app.get("home") { req -> EventLoopFuture<View> in
return req.plot.render(MyPage())
}
}
The example above will only render a simple page with no styling, which is impractical for most use cases. As such, when you inevitably need to modify the <head>
element, simply override the head
property of Page
, like so.
The following example will render an HTML page identical to the first snippet in this README.
struct MyPage: Page {
let title = "My website"
var head: Node<HTML.DocumentContext> {
.head(
.title(self.title),
.stylesheet("styles.css")
)
}
var content: Component {
Div {
H1("Hello, world!")
Paragraph("Writing HTML in Swift is pretty great!")
}
}
}
You can also override the body
property directly as well, in the event you need to add <script>
elements and so on. The default implementation of that property looks like this the following.
struct MyPage: Page {
...
var body: Node<HTML.DocumentContext> {
.body(
.component(page.content)
)
}
...
}
⚠️ The reason thehead
,body
, andcontent
properties are all handled differently is due to the fact that, at time of writing,Plot
doesn't provide usage of theComponent
syntax for<head>
and<body>
elements. For more information, see the Components section of thePlot
README.
You'll probably want to include than one page on your site, and you won't want to override the head
and body
properties for every single page you create as it will lead to duplicated, hard-to-maintain code.
Instead, you can simply create a reusable PageTemplate
. The following snippet of DefaultPageTemplate
is included within PlotVapor
, primarily to serve as an example.
public struct DefaultPageTemplate: PageTemplate {
public static func head(with page: Page) -> Node<HTML.DocumentContext> {
.head(
.title(page.title)
)
}
public static func body(with page: Page) -> Node<HTML.DocumentContext> {
.body(
.component(page.content)
)
}
}
Then, instead of conforming to Page
, you can conform to TemplatedPage
and define the Template
typealias
like so.
struct MyPage: TemplatedPage {
typealias Template = DefaultPageTemplate
...
}
And for even more convenience, you can define a global default template like so. With this, you'll only need to override the Template
typealias
for pages that use a different template.
extension TemplatedPage {
typealias Template = MyPageTemplate
}
By default, all HTML
is minified before it is rendered. To modify this behavior, use the indentedBy
parameter to pass an Indentation.Kind
.
func configureRoutes(_ app: Application) throws {
app.get("home") { req -> EventLoopFuture<View> in
let html = HTML(
// elements, etc.
)
return req.plot.render(html, indentedBy: .spaces(2))
}
}
PlotVapor
is also compatible with Swift 5.5's new async/await functionality.
func configureRoutes(_ app: Application) throws {
app.get("home") { req async throws -> View in
let html = HTML(
// elements, etc.
)
return req.plot.render(html, indentedBy: .spaces(2))
}
}
For more information on using Swift concurrency within Vapor, check out the Vapor docs.