Swep 是一个 Swift DSL,用于以 SwiftUI 的方式编写类型安全的 HTML/CSS。
请查看 SwepUI,了解类似 SwiftUI 的封装!
目前在 Swift 中渲染 HTML/CSS 的流行选择是使用模板语言,但这会使您的应用程序暴露于运行时错误和无效的 HTML/CSS。我们的库通过将 HTML/CSS 直接嵌入到 Swift 强大的类型系统中,在编译时就防止了这些运行时问题。
HTML/CSS 文档可以像创建嵌套的 SwiftUI View 一样,以类似 SwiftUI 的方式创建
import Swep
let page = document {
html {
head(title("YOLO!"))
body {
h1("Welcome!")
p("You've found our site!")
}
}
}
Style 标签内的 CSS!
import Swep
let page = document {
html {
head {
title("YOLO!")
style {
selector("h1") {
color(.tomato())
}
}
}
body {
h1("Welcome!")
p("You've found our site!")
}
}
}
更棒的是,CSS 作为标签修饰符
import Swep
let page = document {
html {
head(title("YOLO!"))
body {
h1("Welcome!")
.color(.tomato())
p("You've found our site!")
}
}
}
一旦您的文档创建完成,您可以使用 render
函数渲染它
/// when you just want to see the output..
page.render(.debug(.pretty(.spaces(2))))
// or
page.debugRender()
<!DOCTYPE html>
<html>
<head>
<title>
YOLO!
</title>
</head>
<body>
<h1 style="color:rgba(255,99,71,1.0)">
Welcome!
</h1>
<p>
You've found our site!
</p>
</body>
</html>
/// when your document is ready for release..
page.render(.release(.inline))
// or
page.render()
<!DOCTYPE html><html><head><title>YOLO!</title></head><body><h1 style="color:rgba(255,99,71,1.0);">Welcome!</h1><p>You've found our site!</p></body></html>
因为我们将 DSL 嵌入到 Swift 中,所以我们可以利用一些高级 Swift 功能,在构建 HTML/CSS 文档时添加额外的安全层。举一个简单的例子,我们可以加强许多 HTML/CSS API,强制它们使用真实的类型,而不是仅仅依赖字符串。
let imgTag = img()
.src("cat.jpg")
.width(400)
.height(300)
imgTag.render()
// <img src="cat.jpg" width="400" height="300">
这里 src
属性接受一个字符串,但 width
和 height
接受整数,因为在这些属性中放入任何其他内容都是无效的。
对于更高级的示例,<li>
标签只能放在 <ol>
和 <ul>
标签内,我们可以表示这个事实,使得构建无效的文档成为不可能
let listTag = ul {
li("Cat")
li("Dog")
li("Rabbit")
} // ✅ Compiles!
listTag.render()
// <ul><li>Cat</li><li>Dog</li><li>Rabbit</li></ul>
div {
li("Cat")
li("Dog")
li("Rabbit")
} // 🛑 Compile error
另一个高级示例,<thead>
、<tbody>
和 <tfoot>
标签只能放在 <table>
标签内,同样,<th>
和 <td>
标签只能放在 <tr>
标签内。
let tableTag = table {
tr(th("A Head"))
tr(td("A Body"))
} // ✅ Compiles!
type(of: tableTag)
// Table<Tuple<(Tr<Th<Text>>, Tr<Td<Text>>)>>
let tableTag = table {
thead(tr(th("A Head")))
tbody(tr(td("A Body")))
} // ✅ Compiles!
type(of: tableTag)
// Table<Tuple<(Thead<Tr<Th<Text>>>, Tbody<Tr<Td<Text>>>)>>
let tableTag = table {
thead(tr(th("A Head")))
for _ in 1...3 {
tbody(tr(td("A Body")))
}
} // ✅ Compiles!
type(of: tableTag)
// Table<Tuple<(Thead<Tr<Th<Text>>>, Array<Tbody<Tr<Td<Text>>>>)>>
table {
tbody(tr(td("A Body")))
thead(tr(th("A Head")))
} // 🛑 Compile error
还有更多内容..
在幕后,Swep 遵循面向协议编程 (POP) 方法,以及强大的 swift 功能 @resultBuilder。主要有两个库:Html 处理 html 方面,Css 处理 css 方面。还有一个微型库 HtmlCssSupport 处理 Html 和 Css 的组合。
是的!我们甚至提供了插件库,以减少将此库与 Vapor、Kitura 和 Perfect 一起使用时的摩擦。在以下仓库中查找更多信息
模板语言很流行且易于上手,但它们有很多缺点
字符串式 API:模板语言始终是字符串类型,因为您将模板作为大型字符串提供,然后在运行时插值值并执行逻辑。这意味着我们在 Swift 中理所当然的事情,例如编译器捕获错别字和类型不匹配,在您运行代码之前都不会被注意到。
不完整的语言:模板语言只是编程语言。这意味着您应该期望从这些语言中获得您从其他成熟语言(如 Swift)中获得的所有优点。这包括语法高亮、IDE 自动完成、静态分析、重构工具、断点、调试器以及使 Swift 强大的众多功能,例如 let 绑定、条件语句、循环等等。然而,现实情况是,没有模板语言支持所有这些功能。
僵化:模板语言是僵化的,因为它们不允许我们在 Swift 中对数据结构执行的组合和转换类型。无法简洁地遍历您构建的文档,并检查或转换您访问的节点。这种能力有很多应用,例如能够漂亮地打印或缩小您的 HTML/CSS 输出,或者编写一个转换,允许您将 CSS 样式表内联到 HTML/CSS 节点中。由于模板语言的工作方式,整个世界都对您关闭了。
此库中的 DSL 修复了所有这些问题,并打开了模板语言完全关闭的大门。
您可能仍然想使用模板语言有以下几个原因
设计师向您交付了一个大型 HTML/CSS 文档,而您只想挂钩一些值插值或逻辑。在这种情况下,您可以简单地将该 HTML/CSS 复制并粘贴到您的模板中,添加一些插值标记,您就可以很好地从您的 Web 应用程序提供完整的页面。
您需要渲染非 HTML/CSS 文档。模板语言的优点在于它直接输出到纯文本,因此它可以模拟任何类型的文档,无论是 HTML/CSS、markdown、XML、RSS、ATOM、LaTeX 等。
在单个表达式中创建非常大的文档可能会导致编译时间增加,而模板不会被 Swift 编译,因此不会影响编译时间。幸运的是,这通常不是问题,因为将文档分解成您想要的尽可能多的小块非常容易,这可能会在长期内带来更多可重用的代码。
如果您确实认为模板语言更适合您的需求,那么您应该考虑 HypertextLiteral,它为您提供类似模板的功能,但以更安全的方式。
创建一个 html 文档和 css 样式表
let titillimFont: StaticString = """
https://fonts.googleapis.com/css2?family=\
Titillium+Web:ital,wght@0,200;0,300;0,400;\
0,600;0,700;0,900;1,200;1,300;1,400;1,600;\
1,700&display=swap
"""
let plainStyle = style {
`import`(titillimFont)
selector("*, *::before, *::after") {
margin(.px(0))
padding(.px(0))
boxSizing(.borderBox)
}
selector("body") {
margin(.px(0), .auto)
backgroundColor(.hex(0x111))
fontFamily("'Titillium Web', sans-serif")
}
}
let page = document {
html {
head {
title("Hello, Swep!")
plainStyle
}
body {
h1("📄 swift-web-page (swep)")
hr()
.width(50%)
.minWidth(.px(720))
.color(.hex(0x323232))
p {
strong("Swep ")
text("is a Swift DSL for writing type-safe HTML/CSS in declarative way.")
}
p {
text("With ")
strong("Swep:")
}
ul {
li("You can write html documents along with css")
.fontWeight(.bolder)
li("Bringing all swift-language features out of the box")
#if swift(>=5.1)
for version in 1...4 {
if version != 2 {
li("supporting swift v5.\(version)")
}
}
#else
li("Unfortunately this library is built using @resultBuilder which is available in swift v5.1 and higher 😢")
#endif
}
blockquote("Enjoy! ✌️😁")
}
}
}
检查文档的类型..
type(of: page.content)
Html<Tuple<(Head<Tuple<(Title<Text>, Style<Stylesheet>)>>, Body<Tuple<(H1<Text>, Hr, P<Tuple<(Strong<Text>, Text)>>, P<Tuple<(Text, Strong<Text>)>>, Ul<Tuple<(Li<Text>, Li<Text>, Array<Optional<Li<Text>>>)>>, Blockquote<Text>)>>)>>
渲染输出文档..
let renderMode: RenderMode = .release(.pretty(.spaces(2)))
page.render(renderMode)
<!DOCTYPE html>
<html>
<head>
<title>
Hello, Swep!
</title>
<style>
@import https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap;
*, *::before, *::after {
margin: 0px;
padding: 0px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body {
margin: 0px auto;
background-color: #111;
font-family: 'Titillium Web', sans-serif;
}
</style>
</head>
<body>
<h1>
📄 swift-web-page (swep)
</h1>
<hr style="width:50%;
min-width:720px;
color:#323232">
<p>
<strong>
Swep
</strong>
is a Swift DSL for writing type-safe HTML/CSS in declarative way.
</p>
<p>
With
<strong>
Swep:
</strong>
</p>
<ul>
<li style="font-weight:bolder">
You can write html documents along with css
</li>
<li>
Bringing all swift-language features out of the box
</li>
<li>
supporting swift v5.1
</li>
<li>
supporting swift v5.3
</li>
<li>
supporting swift v5.4
</li>
</ul>
<blockquote>
Enjoy! ✌️😁
</blockquote>
</body>
</html>
如果您想在使用 SPM 的项目中使用 swift-web-page,只需将 dependencies
子句添加到您的 Package.swift
dependencies: [
.package(url: "https://github.com/alja7dali/swift-web-page.git", from: "0.0.1")
]
从那里,您可以将 Swep
添加为目标依赖项。
let Swep: Target.Dependency = .product(name: "Swep", package: "swift-web-page")
...
targets: [
.target(name: "yourProject", dependencies: [Swep]),
]
所有模块均在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE。