🗺 swift-html
一种用于类型安全、可扩展和可转换 HTML 文档的 Swift DSL。
目录
动机
如今,Swift 中渲染 HTML 的流行选择是使用模板语言,但它们会使您的应用程序暴露于 运行时错误 和 无效的 HTML。我们的库通过将 HTML 直接嵌入到 Swift 强大的类型系统中,在编译时防止这些运行时问题。
示例
HTML 文档可以像创建嵌套 JSON 文档一样以树形结构创建
import Html
let document: Node = .document(
.html(
.body(
.h1("Welcome!"),
.p("You’ve found our site!")
)
)
)
这些标签函数 html
、body
、h1
等,实际上只是在创建和嵌套 Node
类型的实例,后者是一个简单的 Swift 枚举。因为 Node
只是一个简单的 Swift 类型,我们可以以各种有趣的方式进行转换。例如,如果我们想要从文档中删除所有的感叹号呢?
func unexclaim(_ node: Node) -> Node {
switch node {
case .comment:
// Don't need to transform HTML comments
return node
case .doctype:
// Don't need to transform doctypes
return node
case let .element(tag, attrs, children):
// Recursively transform all of the children of an element
return .element(tag, attrs, children.map(unexclaim))
case let .fragment(children):
// Recursively transform all of the children of a fragment
return .fragment(children.map(unexclaim))
case let .raw(string), .text(string):
// Transform text nodes by replacing exclamation marks with periods.
return string.replacingOccurrences(of: "!", with: ".")
}
}
unexclaim(document) // Node
创建完您的文档后,您可以使用 render
函数来渲染它
render(document)
// <!doctype html><html><body><h1>Welcome!</h1><p>You’ve found our site!</p></body></html>
当然,您可以先对文档执行 unexlaim
转换,然后再进行渲染
render(unexclaim(document))
// <!doctype html><html><body><h1>Welcome.</h1><p>You’ve found our site.</p></body></html>
现在文档就非常严肃和认真了
安全
由于我们在 Swift 中嵌入我们的 DSL,我们可以利用一些高级 Swift 特性在构建 HTML 文档时添加额外的安全层。以一个简单的例子来说,我们可以增强许多 HTML API 的真正类型,而不是仅仅依赖字符串。
let imgTag = Node.img(attributes: [.src("cat.jpg"), .width(400), .height(300)])
render(imgTag)
// <img src="cat.jpg" width="400" height="300">
在这里,src
属性接受一个字符串,但 width
和 height
接受整数,因为将这些属性放置其他任何内容都是无效的。
对于更复杂的情况,仅可以在 <ol>
和 <ul>
标签内放置 <li>
标签,且我们可以表示这个事实,使其无法构建无效文档
let listTag = Node.ul(
.li("Cat"),
.li("Dog"),
.li("Rabbit")
) // ✅ Compiles!
render(listTag)
// <ul><li>Cat</li><li>Dog</li><li>Rabbit</li></ul>
Node.div(
.li("Cat"),
.li("Dog"),
.li("Rabbit")
) // 🛑 Compile error
设计
库的核心是一个具有 6 个情况的单一枚举
public enum Node {
case comment(String)
case doctype(String)
indirect case element(String, [(key: String, value: String?)], Node)
indirect case fragment([Node])
case raw(String)
case text(String)
}
此类型允许您表达可以存在的每一个 HTML 文档。然而,直接使用此类可能会有些不便,因此我们提供了一组辅助函数,以类型安全的方式从整个 HTML 规范构建每个元素和属性
// Not using helper functions
Node.element("html", [], [
.element("body", [], [
.element("p", [], [.text("You’ve found our site!")])
])
])
// versus
// Using helper functions
Node.html(
.body(
.h1("Welcome!"),
.p("You’ve found our site!")
)
)
这使得 HTML 文档的“Swift化”看起来与原始文档非常相似。
常见问题解答
我是否可以用它与现有的 Swift 网络框架(如 Kitura 和 Vapor)一起使用?
是的!我们甚至提供了插件库,以降低在 Kitura 和 Vapor 上使用此库的摩擦力。更多详细信息请参阅以下存储库
我为什么要在模板语言之外使用这个工具?
模板语言非常受欢迎,入门也简单,但它们有很多缺点
-
字符串API:模板语言总是字符串类型的,因为您提供模板作为一个大字符串,然后在运行时插入值和执行逻辑。这意味着在Swift中我们习以为常的许多功能,如编译器捕获笔误和类型不匹配,将不被注意到,直到你运行代码。
-
不完整的语言:模板语言只是这样:编程语言。这意味着您应该从这些语言中获得所有其他完整语言(如Swift)的优点。这包括语法高亮、IDE自动完成、静态分析、重构工具、断点、调试器以及让Swift强大的各种功能,如let绑定、条件语句、循环等。然而,现实是没有任何模板语言支持所有这些功能。
-
严格:模板语言在某种程度上是严格的,因为它们不允许我们像在Swift中的数据结构上执行的那种组合和变换。您无法简短地遍历构建的文档,并检查或转换访问的节点。这一能力有很多用途,例如能够美化打印或压缩您的HTML输出,或编写允许您将CSS样式表内联到HTML节点的变换。由于模板语言的工作方式,关闭了整个世界。
这个库中的领域特定语言(DSL)修复了所有这些问题,并为模板语言完全关闭的大门打开了。
何时更合适使用模板语言而不是swift-html?
有一些原因可能会让你仍然想使用模板语言
-
设计师提供了一个大的HTML文档给你,而你只想添加一点点值插值或逻辑。在这种情况下,你可以直接把HTML复制粘贴到模板中,添加几个插值标记,就可以从您的Web应用程序中服务完整页面。
-
您需要渲染非HTML文档。模板语言的优点在于它直接输出到纯文本,因此可以模拟任何类型的文档,无论是HTML、Markdown、XML、RSS、ATOM、LaTeX,还是更多。
-
在一个单一代数表达式中创建非常大的文档会导致编译时间上升,而模板不是由Swift编译的,因此不会影响编译时间。幸运的是,这并不是一个常见问题,因为很容易将文档分成你想要那么多的小块,这可能在长远来看会带来更多可重用代码。
安装
Carthage
如果您使用 Carthage,可以在您的 Cartfile
中添加以下依赖项
github "pointfreeco/swift-html" ~> 0.3
CocoaPods
如果您的项目使用 CocoaPods,只需在您的 Podfile
中添加以下内容
pod 'Html', '~> 0.3'
# SnapshotTesting helpers
pod 'HtmlSnapshotTesting', '~> 0.3'
SwiftPM
如果您想在使用 SwiftPM 的项目中使用 swift-html,只需在 Package.swift
中添加一个 dependencies
子句
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-html.git", from: "0.3.0")
]
然后您可以添加 Html
或 HtmlSnapshotTesting
作为目标依赖项。
Xcode 子项目
通过子模块克隆或下载 swift-html,然后将 Html.xcodeproj
拖入您的项目。
想要了解更多吗?
这些概念(以及更多)在 Point-Free 的系列视频中得到了深入探讨,这是一个由 Brandon Williams 和 Stephen Celis 主持的关于函数式编程和 Swift 的视频系列。
该库的想法在以下几集中进行了探讨
许可协议
所有模块均在MIT许可下发布。有关详细信息,请参阅LICENSE。