Pony Gets a Template Engine

★★★★★ 5星 | 编程语言 模板引擎 安全 Web开发
URL: https://www.ponylang.io/blog/2026/03/pony-gets-a-template-engine/

摘要

Pony语言获得新模板引擎,深度实现上下文感知的HTML转义机制,为构建Phoenix-like Web框架铺平道路。

核心亮点

  • 上下文感知HTML转义:状态机跟踪标签、属性名、属性值、CSS、JavaScript上下文
  • URL属性安全过滤:过滤javascript:、vbscript:、data:等危险URI scheme
  • 事件处理器保护:onclick等事件处理器自动进行JavaScript字符串转义
  • TemplateSink接口:支持LiveView风格差量渲染,最小化网络传输
  • 参考Go的html/template:借鉴成熟设计理念

深度分析

大多数模板引擎使用单一转义函数处理所有变量,将<转换为<,>转换为>,&转换为&。这种方法在变量位于文本内容时可以工作,但在其他上下文中存在安全问题:

Template示例:
<a href="{{ url }}">{{ label }}</a>
<button onclick="{{ handler }}">Click</button>
<div style="color: {{ color }}">{{ text }}</div>

四个变量,三个不同HTML上下文。{{ url }}在URL属性中,entity encoding无法阻止javascript:alert('xss')执行。{{ handler }}在JavaScript事件处理器中。{{ color }}在CSS上下文中。每个上下文需要不同的转义规则。

Pony的HtmlTemplate不回避这个问题。状态机逐字符处理模板字面量段,跟踪当前是否在标签中、属性名中、属性值中(哪种类型)、注释中、脚本块中、样式块中,还是纯文本中。当模板遍历器到达变量时,状态机知道当前上下文。正确的转义自动跟随:

  • 文本内容和常规属性:entity encoding
  • URL属性(href, src, action):scheme过滤 + percent-encoding
  • 事件处理器(onclick等):JavaScript字符串转义
  • style属性:CSS值转义
  • 注释:--序列剥离

TemplateSink与差量渲染

TemplateSink是应对真实问题的通用渲染接口。传统方式是调用render()获取字符串。但Livery (LiveView风格框架) 需要更精细的控制。

Livery思路:服务器渲染HTML但不每次发送整个页面。它找出变化的最小差异并发送。对于差量渲染,渲染层需要分离静态模板内容和动态值。静态({{ }}标记之间的HTML结构)在渲染之间从不改变。动态(被替换的值)可能会改变。

TemplateSink接口:
接收器接收交替的literal和dynamic_value调用。严格交错:对于N个动态插入,正好N+1个literal调用,从literal开始和结束。控制流块将其渲染输出折叠为单个动态值。

Livery的RenderSink实现此接口。第一次渲染时,收集静态和动态内容,发送到客户端并缓存。每次后续渲染时,将每个新动态值与缓存的先前值比较。只有实际改变的槽的索引和值通过WebSocket发送。如果没有变化,什么都不发送。

生态进展

Pony Web开发栈正在逐步成形:

  • Lori - 网络通信
  • Stallion - HTTP服务器
  • JSON - 标准库中的JSON支持
  • Templates - 页面渲染
  • Livery - LiveView风格交互式UI

目标是构建Phoenix-like Web框架。基于Pony的actor模型和引用能力,提供服务器渲染HTML和最小化有线传输的交互式UI体验。

核心洞察

"It figures out the context for you. The right escaping follows automatically."

上下文感知转义是成熟的设计模式。Go的html/template已经实现了多年。这是一个好想法,比大多数模板引擎做的要好。知道有一个好的解决方案存在,复制它不是愚蠢的行为。