使用 Swift 宏编写 HTML。通过全局属性支持 HTMX。

Requires at least Swift 5.9 Apache 2.0 License

目录

原因

用法

基本

如何使用此库?

使用 #html(encoding:attributes:innerHTML:) 宏。 宏和默认 HTML 元素的所有参数默认为可选。 默认 HTML 元素由内部宏生成。

HTML 宏

#html(
  encoding: HTMLEncoding = .string,
  attributes: [<global attribute>] = [],
  <element specific attribute>: <element specific attribute value>? = nil,
  _ innerHTML: CustomStringConvertible...
)

HTMLElement

所有默认 HTML 元素都符合 HTMLElement 协议,并包含其相应的元素属性。 可以在初始化元素时声明它们,也可以在初始化后通过直接访问属性变量来更改它们。

用于创建 HTML 元素的默认初始化程序遵循以下语法

<html element name>(
  attributes: [<global attribute>] = [],
  <element specific attribute>: <value>? = nil,
  _ innerHTML: CustomStringConvertible...
)

示例

// <div class="dark"><p>Macros are beautiful</p></div>
#html(
  div(attributes: [.class(["dark"])],
    p("Macros are beautiful")
  )
)

// <a href="https://github.com/RandomHashTags/litleagues" target="_blank"></a>
#html(
  a(href: "https://github.com/RandomHashTags/litleagues", target: ._blank)
)

// <input id="funny-number" max="420" min="69" name="funny_number" step="1" type="number" value="69">
#html(
  input(
    attributes: [.id("funny-number")],
    max: 420,
    min: 69,
    name: "funny_number",
    step: 1,
    type: .number,
    value: "69"
  )
)

// html example
let test:String = #html(
  html(
    body(
        div(
            attributes: [
                .class(["dark-mode", "row"]),
                .draggable(.false),
                .hidden(.true),
                .inputmode(.email),
                .title("Hey, you're pretty cool")
            ],
            "Random text",
            div(),
            a(
                div(
                    abbr()
                ),
                address()
            ),
            div(),
            button(disabled: true),
            video(autoplay: true, controls: false, preload: .auto, src: "https://github.com/RandomHashTags/litleagues", width: .centimeters(1)),
        )
    )
  )
)
如何转义 HTML?

编译后的输出会自动转义仅在编译时已知的破坏源 HTML 的字符。

您还可以使用 #escapeHTML(innerHTML:) 宏来转义编译时已知的数据。

如果您正在处理运行时数据

如何编码变量?

使用字符串插值。

您会收到一个编译器警告,提示插值可能会引入原始 HTML

是否禁止显示此警告或使用上述方法之一在运行时转义 HTML 取决于您。

Swift HTMLKit 尝试在编译时使用等效的 StaticString 提升已知的插值,以获得最佳性能。 由于宏扩展是沙盒式的,并且宏参数类型无法使用词法上下文/AST,因此目前受到限制。 这意味着在 html 宏中引用内容不会提升到其预期值。 阅读有关此限制的更多信息

示例

let string:String = "any string value", integer:Int = -69, float:Float = 3.141592

// ✅ DO
let _:String = #html(p("\(string); \(integer); \(float)"))
let _:String = #html(p("\(string)", "; ", String(describing: integer), "; ", float.description))

let integer_string:String = String(describing: integer), float_string:String = String(describing: float)
let _:String = #html(p(string, "; ", integer_string, "; ", float_string))

// ❌ DON'T; compiler error; compile time value cannot contain interpolation
let _:StaticString = #html(p("\(string); \(integer); \(float)"))
let _:StaticString = #html(p("\(string)", "; ", String(describing: integer), "; ", float.description))
let _:StaticString = #html(p(string, "; ", integer_string, "; ", float_string))

高级

我需要一个自定义元素!

使用默认的 custom(tag:isVoid:attributes:innerHTML:) html 元素。

示例

我们想展示 Apple Pay 按钮

#html(
  custom(
    tag: "apple-pay-button",
    isVoid: false,
    attributes: [
      .custom("buttonstyle", "black"),
      .custom("type", "buy"),
      .custom("locale", "el-GR")
    ]
  )
)

变成

<apple-pay-button buttonstyle="black" type="buy" locale="el-GR"></apple-pay-button>
我需要一个自定义属性!

使用 .custom(id:value:) 全局属性。

示例

我们想展示 Apple Pay 按钮

#html(
  custom(
    tag: "apple-pay-button",
    isVoid: false,
    attributes: [
      .custom("buttonstyle", "black"),
      .custom("type", "buy"),
      .custom("locale", "el-GR")
    ]
  )
)

变成

<apple-pay-button buttonstyle="black" type="buy" locale="el-GR"></apple-pay-button>
我需要监听事件!

警告

内联事件处理程序是一种过时的事件处理方式。

普遍认为这是一种“不良做法”,您不应该将 HTML 和 JavaScript 混合在一起。

此功能仍然已被弃用,以鼓励使用其他技术。

https://mdn.org.cn/en-US/docs/Learn/JavaScript/Building_blocks/Events#inline_event_handlers_—_dont_use_these 了解更多信息。

使用 .event(<event type>, "<value>") 全局属性。

示例

#html(
  div(
    attributes: [
      .event(.click, "doThing()"),
      .event(.change, "doAnotherThing()")
    ]
  )
)
我需要将输出作为不同的类型!

#html 宏中声明您想要的编码。

#html(
  encoding: HTMLEncoding = .<type>
)

目前支持的类型:

HTMX

如何使用 HTMX?

使用 .htmx(<htmx attribute>) 全局属性。 支持所有 HTMX 2.0 属性(包括服务器发送事件和 Web 套接字)。

示例

// <div hx-boost="true"></div>
var string:StaticString = #html(div(attributes: [.htmx(.boost(.true))]))

// <div hx-get="/test"></div>
string = #html(div(attributes: [.htmx(.get("/test"))]))

// <div hx-on::abort="bruh()"></div>
string = #html(div(attributes: [.htmx(.on(.abort, "bruh()"))]))

// <div hx-on::after-on-load="test()"></div>
string = #html(div(attributes: [.htmx(.on(.afterOnLoad, "test()"))]))

// <div hx-on:click="thing()"></div>
string = #html(div(attributes: [.htmx(.onevent(.click, "thing()"))]))

// <div hx-preserve></div>
string = #html(div(attributes: [.htmx(.preserve(true))]))

// <div sse-connect="/connect"></div>
string = #html(div(attributes: [.htmx(.sse(.connect("/connect")))]))

// <div ws-connect="/chatroom"></div>
string = #html(div(attributes: [.htmx(.ws(.connect("/chatroom")))]))

// <div hx-ext="ws" ws-send></div>
string = #html(div(attributes: [.htmx(.ext("ws")), .htmx(.ws(.send(true)))]))

基准测试

测试机器

基准测试命令: swift package --allow-writing-to-package-directory benchmark --target Benchmarks --metric throughput --format jmh

静态

动态

结论

此库在性能和效率方面明显领先。静态网页提供最佳性能,而动态页面仍然名列前茅(我正在积极研究和测试动态页面的改进)。

贡献

欢迎大家贡献。有关最佳实践,请参阅 CONTRIBUTIONS.md