返回

文章详情

展示HN:我用Swift重写了我的网站

Hacker News2026年10月12日 18:00

这个网站过去是用Astro制作的。现在它完全是用Swift制作的:每个页面、组件和样式。与我的应用程序使用相同的语言,这种感觉很像SwiftUI。浏览器仍然获取简单的HTML和CSS,加上一点JavaScript。变化的是制作方式,以及我在上面工作的乐趣。为什么选择Swift?我整天都在写Swift,而且我喜欢它。当我从应用程序切换到网站时,我不再需要切换语言和工具。但真正的原因是编译器。一个网站是很多小东西,它们需要达成一致:链接、类名、样式、脚本,以及60多个应用程序的数据。在典型的Web堆栈中,它们只在浏览器中相遇,而一个错误会变成一个破损的页面。在这里,它们大多数在编译器中相遇。重命名一个页面,代码中对它的每个链接都停止编译。它看起来像SwiftUI。一个组件是具有一个主体的结构:swift struct BackLink : HTML { let app : App var body : some HTML { a (. href (app. path )) { "返回到\(app. title ) " } . style { $0 . secondaryText () . fontWeight (. medium ) . hoverColor (. primaryText ) } } } HTML来自Elementary。其他(样式、路由和内容)是我自己的小框架。您在这里看到的语法高亮也是Swift的。构建运行代码通过Swift自己的解析器swift-syntax,并为每个标记着色,因此颜色是正确的,而且没有JavaScript。样式是修饰符。样式应用于元素,就像SwiftUI中的修饰符一样。上述的.style { … }变成一个类,如back-link-1x2y3z4,来自文件名和样式的哈希。它不能泄漏到其他元素,并且当我检查HTML时,我知道它来自哪个文件。共享外观和变体是枚举案例,因此它们会自动补全,并且错字不会编译。以下是徽章组件的简短版本:swift struct Badge : HTML { enum Kind : StyleSet { case standard case new var style : Style { switch self { case . standard : Style (). background (. gray ( 200 ), dark: . gray ( 800 )) case . new : Style (). background (. teal ( 100 ), dark: . teal ( 900 )) } } } enum Styles : StyleSet { case root var style : Style { Style () . display (. inlineBlock ) . padding (. horizontal , . rootEm ( 0.375 )) . cornerRadius (. rootEm ( 0.5 )) . bold () } } let title : String var kind = Kind . standard var body : some HTML { span { title } . style ( Styles . root , kind) } } 使用它就像使用SwiftUI视图一样:swift Badge (title: "新" , kind: . new ) 每个页面只获得它显示的组件的CSS。在后台,它是现代CSS:级联层、light-dark()、相对颜色、基于滚动的动画和容器单位。没有预处理器,也没有Tailwind。已类型化的CSS变量。CSS自定义属性只是字符串。在这里,它们有类型:swift static let color = StyleVariable < Color >( "--overflow-menu-color" ) // 菜单使用它,具有默认值。 . color ( OverflowMenu . color . value (default: . link )) // 一个父级设置它。 . setting ( OverflowMenu . color , to: . bodyText ) 将其设置为长度不会编译。通过RegisteredProperty,页面还获得@property规则,因此浏览器可以动画它,就像主页上的渐变:swift static let gradientStart = RegisteredProperty < Color >( "--gradient-start" , syntax: "<color>" , initialValue: . black ) 响应式和可访问性断点和媒体查询是类型化的,并且媒体查询与&&结合:swift Style () . grid (columns: 2 ) . from (. tablet ) { $0. gridColumns ( 3 ) } . media (. coarsePointer ) { $0. padding (. rootEm ( 0.75 )) } . media (. hover && . finePointer && . allowsMotion ) { $0. hover { $0. setting (titleLight, to: . percent ( 35 )) } } 最后一个是跟随指针的光,位于应用程序页面的标题上。它只在有鼠标或触控板的情况下出现,并且仅在允许移动时出现。颜色是语义化的,像.secondaryText一样。每个都有一个浅值和一个深值,当您在系统设置中请求更多对比时,文本、链接和线条的颜色会获得更强的值。组件免费获得所有这些。链接链接像SwiftUI中的链接一样工作。来自404页面:swift Link ( "返回主页" , destination: . path (. root )) . buttonStyle (. primary ) Link ( "浏览应用" , destination: . path (. apps )) . buttonStyle (. secondary ) 页面是静态成员,因此.apps会自动补全,且不存在的页面不会编译。构建后,生成的HTML中的每个链接和ID也会被检查。脚本具有行为的组件是自定义元素。Swift渲染HTML,而脚本是元素的类。一个小脚本直接存在于Swift文件中,而一个较大的脚本存在于旁边的.js文件中。脚本只触碰自己的元素,因此不会破坏页面上的其他内容。没有框架,也没有捆绑器。小而快。一个博客文章压缩后约14KB:6.6KB的HTML,5.4KB的CSS和1.8KB的JavaScript。像Lungo这样的应用页面,在截图之前大约为29KB。除了一个小的共享样式表,

赞助内容

NordVPN Next-gen Antivirus

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

☕请我喝杯咖啡