返回

文章详情

Zig中的类型安全HTML模板

Hacker News2026年10月12日 05:02

多年来,我做了很多网页开发。我大约在2000年开始,正值PHP 3.0发布不久,并开始主宰网络世界。我使用过各种生成HTML的方法:原始PHP、Python中的字符串连接、PHP中的Smarty模板、Perl中的Template Toolkit,以及Python中的Jinja/Django模板。随着世界转向单页应用程序和JavaScript框架,我停止了前端开发。两年前,我接触了Zig,我从未想过会在这样的低级语言中考虑网页开发。但当我在构建自己的生态系统,与zio和Dusty合作时,我意识到这并不是那么不可思议。我为API后端构建了这些库,输出为JSON或MessagePack,但我发现通过适当地使用arena分配器,甚至网页开发实际上也是可行的,因为你不需要记得释放所有通常涉及的字符串。而且通过Zig的comptime,你可以构建非常高级的接口,而不会失去性能的好处。借助htmx或Datastar,你甚至可以构建单页应用,而无需接触JavaScript/TypeScript,这是我曾经承诺自己绝不会去做的。不过,有一件事仍然缺失:模板。字符串连接不是一个选项,我不想回到2000年前的CGI时代。我对模板库有几个要求:它必须是类型安全的,所有输入在编译时都有类型并被检查。我在生活中已经处理过足够多的动态语言,我不想再处理运行时模板错误。它必须快速。没有分配,没有中间状态,直接把输出写入std.Io.Writer。它需要有安全的默认值。我不想再处理XSS,因此所有输入必须默认转义。写HTML的感觉仍然应该存在。我不想要一个基于Zig的DSL,我不想不断与之作斗争。我找不到能满足所有这些要求的解决方案。然后我看到了Ziex,我感到非常惊讶。时任开发者Nurul Huda在Zig中构建的开发者体验让我难以置信。虽然它并未直接满足我的需求,因为我不想要WASM框架,但它的语法和将Zig代码集成到模板的方式给了我一些启发。经过进一步的研究,我发现了Templ for Go,我很清楚我想要构建的内容。因此,让我来介绍zt(抱歉,命名方面我不太好)。它是Templ在Zig中的适配版本。模板在zig build过程中被解析和转译为Zig代码。这意味着你可以直接从应用代码导入它们,但更重要的是,模板也可以导入你的应用代码。因此,所有参数都可以是完全类型安全的。如果无法渲染,Zig编译器会告诉你。生成的代码相当简单,它只是在std.Io.Writer中写入。你可以直接写入套接字或将输出积累在缓冲区中,选择权在于你。以下是一个模板的样子:const User = @import ( "../models.zig" ). User ; pub templ Layout ( title : [] const u8 ) { <! DOCTYPE html > < html > < head > < title > { title } </ title > </ head > < body > @children </ body > </ html > } pub templ HomePage ( user : ? User ) { @Layout ( "Home" ) { if ( user ) | u | { < h1 > 欢迎回来 , { u . name } !</ h1 > < a href = "/users/{u.id}" > 你的个人资料 </ a > } else { < h1 > 欢迎 , 客人 !</ h1 > < a href = "/login" > 登录 </ a > } } } 这里是如何在Zig代码中使用它的示例:const std = @import ( "std" ); const User = @import ( "models.zig" ). User ; const pages = @import ( "templates/pages.zig" ); pub fn main ( init : std . process . Init ) ! void { var buf : [ 4096 ] u8 = undefined ; var stdout = std . Io . File . stdout (). writer ( init . io , & buf ); const w = & stdout . interface ; const user : User = . { . id = 1 , . name = "Lukas" }; try pages . HomePage . render ( . { user }, w ); try w . flush (); } 如果你想在Dusty中使用它来渲染HTML页面,你只需这样做:try res . render (. html , pages . HomePage , . { user }); 顺便提一下,上面的主要片段在错误处理方面有一个非常不明显的错误,如果你在某处使用相同的模式,std.Io的取消会影响结果。我可能会写一篇博客文章来讨论此事,但如果你感兴趣,可以查看这个Ziggit线程。Dusty中的render函数封装了std.Io.Writer处理,解决了这个问题。所有动态值默认都是HTML转义的,无论是在文本中还是属性中,像href或src这样的URL属性中的值也会检查危险的方案。如果你确实需要输出原始HTML,你必须明确要求{!value} ,这样在模板中很容易识别。解析器还要求所有元素都正确关闭,因此你无法意外产生损坏的标记。它不会保护你免受一切威胁,但可以移除大多数常见的XSS源。我最喜欢的是

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

☕请我喝杯咖啡