Go语言中模板:Templ和Gomponents比较


Go 的标准html/template库被广泛使用,但也存在一些限制,而TemplGomponents都解决了这些问题。一位朋友曾将它们描述为“ Go 版 JSX ”。这两个流行的 UI 库提供:
  • 类型安全:确保参数符合特定类型。
  • 模板组合:类型安全、可发现且灵活的组件。
  • 直接 Go 互操作性:允许将 Go 代码直接插入模板,而无需笨重的“模板函数”

由于这两个库都具有类似的优势,真正的问题是:您应该选择哪一个?

直接上结论:
html/template这两个库都是我们不再推荐的库的绝佳替代品。对我来说,Gomponents 胜出,因为它简单、无需构建的理念和面向未来的设计。

Templ 胜出,因为它定期改进并具有 JSX 外观。

Gomponents
Gomponents 是一个优雅且功能强大的库,具有编写自定义元素和属性的能力。因为它只是一个

// Gomponents - A component is just a Go function!
func Home(args HomeArgs) g.Node {
    return Div(
        g.Text(args.name),
        Span(g.Text(fmt.Sprintf(
"%d", props.age)),)
    )
}

Templ
Templ 是一个编译器和 LSP,可将.templ文件(Go 和 HTML 的混合)转换为类似 Gomponents 的函数。由于它是一个编译器,因此它支持强大的转换功能。例如,有这种语法糖,可自动注入context.Context组件。众所周知,LSP 并非 100% 稳定(尽管它现在非常先进),它并不总是能很好地定位错误,而且还依赖于社区的努力。我敢打赌,模糊测试可能会为 templ 命令创建不稳定的输入。在 1-2 年内,它可能会比 Gomponents 更稳定、更强大,但我觉得它还没有达到那个水平。

// Templ - Templates have a special .templ syntax
type EditNotePageProps struct {
    Note    components.NoteProps
    Notes   []store.Note
// The notes containing tags to display in the sidebar
    CanEdit bool        
// Whether the user can edit the note
}

templ EditNotePage(props EditNotePageProps) {
    @page(PageProps{
        Title:        
"Nuage | " + props.Note.Note.User + "'s notes",
        RelatedNotes: props.Notes,
        IsEditing:    true,
    }) {
        @components.Toolbar(components.ToolbarProps{
            Note:      props.Note,
            IsEditing: true,
            CanEdit:   props.CanEdit,
        })
        <form
            x-data={ fmt.Sprintf(<code>{
                        title:
"%s",
                        slug:
"%s",
                        public: %t
                    }</code>, props.Note.Note.Title, props.Note.Note.Slug, props.Note.Note.Public) }
            class=
"flex flex-col gap-2 mt-2"
            method=
"POST"
            hx-boost=
"true"
            hx-trigger=
"change delay:300ms, keyup delay:300ms"
            action={ templ.SafeURL(
"/notes/" + props.Note.Note.User + "/" + props.Note.Note.ID + "/edit") }
        >

附注:这两种工具都支持“方法渲染”模式,这意味着您可以定义一个能够渲染的类型,有点像 Charm 的Bubble Tea渲染 TUI 原理。这种模式目前还不是很流行,但我预计它会越来越常用。

集成:
Gomponents 是一个库,我们只处理.go文件。您可以自行创建templates/文件夹并组织文件。此外,使用 HTML 语法的 IDE 扩展和工具不适用于 Gomponents,或者需要额外的配置,这可能会干扰其他 .go 文件(例如 tailwind 扩展)。这是此工具的一大痛点。

它和 Templ 几乎一样,只是它仅限于.templ文件。此外,它的语法更像是 Go-in-HTML 而不是 HTML-from-Go,这更易于阅读和集成。

Gomponents 的第二大痛点:采用 HTML-from-Go 方法,与 Templ 相比,可读性降低。Templ 组件的阅读体验确实很好。

但是我喜欢 gomponents 的一点就是在返回组件之前的这个小“闭包”,但在 Templ 中使用起来并不是很舒服。

func Home(args HomeArgs) g.Node {
    if args.age < 0 {
        args.age = 0
    }
    return Div(
        g.Text(args.name),
        Span(g.Text(fmt.Sprintf("%d", props.age)),)
    )
}

数据安全
这两个库都通过 Go Typing 实现数据安全。当我谈到数据安全时,它是指将正确的数据发送到模板并在编译时进行检查。

Gomponents 通过简单的 Go 类型来实现这一点。因为 Gomponents 只是 Go 函数,所以它们是安全的,因为 Go 是安全的。

type HomeArgs struct {
    name string
    age int
}

// Component usage
func myController(c fuego.ContextNoBody) (fuego.Renderer, error) {
  return Home(HomeArgs{
        name:
"Ewen",
        age: 25,
    }), nil
}

Templ 具有相同的输入语法。

两个库都减一,因为当你传递一个结构体时,你总会忘记添加一个字段(一个不幸的 Go 特性)。

return Home(HomeArgs{
    age: 18,
    // I forgot to pass Name! No warning or error from Go compiler
})


易于使用
Gomponents 不需要构建步骤——组件可直接使用,从而创造出流畅的开发人员体验,并与热重加载工具很好地集成。

Templ 总是需要这个额外的编译步骤。目前编译速度非常快(对于我的 30 多个模板项目,编译时间约为 40 毫秒),但仍需要手动触发或集成到构建链中(例如go generate)。

html/template需要运行时编译并依赖于文件系统。尽管文件系统可以虚拟化,并且可以使用包将模板嵌入二进制文件中embed,但手动管理模板编译仍然很麻烦。此外,必须使用语法奇怪的 funcMap 插入函数这一事实对我来说是行不通的。


性能
I/O 一如既往是主要瓶颈,运行时性能仅取决于写入速度。与写入速度相比,两个库进行的额外计算并不重要。

Templ 和 Gomponents 正在使用标准 Go 功能编写io.Writer接口。因此,它们的速度仅取决于满足此接口的底层类型(例如http.ResponseWriter),并且对于每个库都是相同的。但是 Templ 作为一个编译器,可以进一步优化这些字符串连接,预计会比 Gomponents 更快。

html/template根据此评论,Gomponents 和 Templ 比 快约 5 到 10 倍。但更准确的基准测试会有所帮助!