PlotVapor

CI Status Latest Release Swift Compatibility Platform Compatibility License

PlotVapor 是一个小型的软件包,允许在 Vapor 服务器端 Swift Web 框架中轻松渲染 Plot 生成的 HTML。

此软件包仅添加了一个小型桥接层,使 Plot 可以连接到 Vapor,这使得此库非常轻量。该库的使用方式与 LeafKitLeafRender 类类似,以增加熟悉度。

⚠️此库中的代码按原样提供,主要用作教育目的的参考。 它可能缺乏在生产应用程序中支持使用的文档、稳定性以及/或者功能。 因此,我强烈建议直接从该代码库复制文件,而不是将其作为依赖项引入,以避免任何重大更改影响您的工作流程。如果您有任何问题或反馈,请随时提出 issue/PR 或与我联系。

在继续之前,您应该阅读 Plot README,并对该库的工作原理有深入的了解。

用法

我们可以在 configureRoutes(_:) 方法中为 /home 路径添加一个新路由,并渲染一个不执行任何复杂操作的示例 HTML 对象。

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)
    }
}

由于实际上没有人希望拥有一个巨大的路由文件,因此该库还提供了 PagePageTemplate 协议,以帮助保持代码库的组织性和可维护性。

Page

遵循 Page 协议,可以快速轻松地定义页面。

struct MyPage: Page {
    let title = "My website"
    
    var content: Component {
        Div {
            H1("Hello, world!")
            Paragraph("Writing HTML in Swift is pretty great!")
        }
    }
}

然后,在定义路由的任何地方渲染它。

func configureRoutes(_ app: Application) throws {
    app.get("home") { req -> EventLoopFuture<View> in
        return req.plot.render(MyPage())
    }
}

上面的例子只会渲染一个没有样式的简单页面,这在大多数情况下是不切实际的。 因此,当您不可避免地需要修改 <head> 元素时,只需覆盖 Pagehead 属性,如下所示。

以下示例将渲染与本 README 中的第一个代码段相同的 HTML 页面。

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!")
        }
    }
}

如果需要添加 <script> 元素等,您也可以直接覆盖 body 属性。 该属性的默认实现如下所示。

struct MyPage: Page {
    ...
    
    var body: Node<HTML.DocumentContext> {
        .body(
            .component(page.content)
        )
    }

    ...
}

⚠️headbodycontent 属性以不同的方式处理的原因是,在编写本文时,Plot 没有为 <head><body> 元素提供 Component 语法的用法。 有关更多信息,请参见 Plot README 的 Components 部分。

PageTemplate

您可能希望在您的网站上包含多个页面,并且您不希望为您创建的每个页面覆盖 headbody 属性,因为这会导致代码重复且难以维护。

相反,您可以简单地创建一个可重用的 PageTemplate。 以下 DefaultPageTemplate 代码段包含在 PlotVapor 中,主要用作示例。

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)
        )
    }
}

然后,您可以遵循 TemplatedPage 而不是遵循 Page,并像这样定义 Template typealias

struct MyPage: TemplatedPage {
    typealias Template = DefaultPageTemplate

    ...
}

为了更加方便,您可以像这样定义一个全局默认模板。 这样,您只需要为使用不同模板的页面覆盖 Template typealias

extension TemplatedPage {
    typealias Template = MyPageTemplate
}

补充说明

最小化

默认情况下,所有 HTML 在渲染之前都会被最小化。 要修改此行为,请使用 indentedBy 参数传递 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))
    }
}

Swift 并发

PlotVapor 还与 Swift 5.5 的新 async/await 功能兼容。

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))
    }
}

有关在 Vapor 中使用 Swift 并发性的更多信息,请查看 Vapor 文档