惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
博客园 - 聂微东
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
IT之家
IT之家
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
B
Blog
爱范儿
爱范儿
阮一峰的网络日志
阮一峰的网络日志
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Hackread – Cybersecurity News, Data Breaches, AI and More
H
Help Net Security
J
Java Code Geeks
aimingoo的专栏
aimingoo的专栏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
B
Blog RSS Feed
Blog — PlanetScale
Blog — PlanetScale
S
SegmentFault 最新的问题
Apple Machine Learning Research
Apple Machine Learning Research

Jiajun的技术笔记

你好,2026! TiDB 源码阅读(六):TiDB Coprocessor 源码解析 性能优化的核心思想 TiDB 源码阅读(五):索引 TiDB 源码阅读(四):AST、逻辑计划、物理计划 CockroachDB Serverless Architecture podman 无故退出 Cursor Control-L (CTRL-L) Keyboard Shortcuts in Terminal Replace docker with podman Using xmonad with xfce4 A RC script for freebsd frpc 自己动手写一个k8s controller AI 会取代你的(编程)岗位吗? 自建DERP服务器提升Tailscale连接速度(使用Nginx转发) 自动升级Docker容器 再读《程序员修炼之道-从小工到专家》 让浏览器下载文件 再读《软件随想录》/《黑客与画家》/《软技能》 HTTP 压力测试中的 Coordinated Omission 2的补码 编程语言中的 context 是什么? flutter macOS 构建出错 Flatpak 使用小记 Golang CAS 操作是怎么实现的 PostgreSQL 当MQ来使用 Clash 结合 工作VPN 的网络设计 使用 PostgreSQL 搭建 JuiceFS PostgreSQL 配置优化和日志分析 有GitHub Copilot?那就可以搭建你的ChatGPT4服务 窗口函数的使用(以PG为例)
Golang的template(模板引擎)简明教程
Jiajun Huang · 2019-08-23 · via Jiajun的技术笔记

模板语言,在前后端分离的时代,大概可以归类到上古时代的技术了。不过前后端分离并不是银弹(而且也只是 把模板从后端移到前端而已),它也有很多问题:

- SEO
- 需维护两套程序
- ...

模板语言仍然有它的用处,例如我的博客部署在一台512M的小机器上,起两套程序不仅写起来麻烦,而且内存占用 大,部署也不方便。

Go语言的模板与大多数语言一样,是用 {{}} 来做标识,{{ }} 里可以是表达式,也可以是变量,不过 Go语言的模板比较简陋,没办法像Python中 jinja2 那样使用继承的写法,而只能是用拼接的写法。

举个例子,看看这个博客的模板(https://github.com/jiajunhuang/blog/tree/master/templates):

{{ block "header.html" . }}{{ end }}

{{ .content }}

{{ block "footer.html" . }}{{ end }}

这是渲染这篇博客的模板,很明显,它由三部分组成:

  • header.html 这是顶栏
  • {{ .content }} 这是内容
  • footer.html 这是右侧边栏和底部

它的写法,就是把一个完整的HTML页面拆成几个部分,然后进行拼装。有点像这样的意思:

function renderHeader() {
    renderNavbar()
    renderPartOfBody()
}

function renderContent() {
    renderContentBody()
}

function renderFooter() {
    renderPartOfBody()
    renderSidebar()
    renderBottom()
}

renderHeader()
renderContent()
renderFooter()

接下来我们来看看具体的语法:

  • 使用 {{ define "layout" }}{{ end }} 来定义一个块,这个块的名字就是 “layout”,如果不使用 define,那么名字就是文件名
  • 使用 {{ block "template filename" . }} {{ end }} 来调用一个模板,就像上面的例子中,调用一个函数那样,其中 . 也可以是变量名等等,就是引用变量的上下文,如果我们传入 .,那么子模板里可以访问的变量就和当前可以访问的上下文一样
  • {{ range $i := .items }} {{ end }} 相当于Go语言里的 for i := range items
  • {{ if .items }} 相当于Go语言里的 if items,完整的是 {{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
  • {{ .variable }} 渲染 variable 的值
  • {{/* a comment */}} 是注释
  • {{- /* a comment with white space trimmed from preceding and following text */ -}} 可以跨行的注释

当然,还有个比较坑的地方在于,Go会自动把输出进行转义,渲染的时候如果想要不转义,就使用 template.HTML("blablabla"),这里的 template 就是导入的包 html/template

例如,这个博客输出RSS的Controller是这样的:

// RSSHandler RSS
func RSSHandler(c *gin.Context) {
	c.Header("Content-Type", "application/xml")
	c.HTML(
		http.StatusOK, "rss.html", gin.H{
			"rssHeader": template.HTML(`<?xml version="1.0" encoding="UTF-8"?>`),
			"articles":  articles,
		},
	)
}

模板是这样的:

{{ .rssHeader }}
<rss version="2.0">
    <channel>
        <title>Jiajun的技术笔记</title>
        <link>https://jiajunhuang.com</link>
        <description>Jiajun的技术笔记</description>

        {{ range $article := .articles }}
            <item>
                <title>{{ .Title }}</title>
                <link>https://jiajunhuang.com/{{ .DirName }}/{{ .Filename }}.html</link>
                <description>{{ .Title }}</description>
            </item>
        {{ end }}

    </channel>
</rss>

这样才成功的避免了XML的第一个尖括号 < 被转义。


参考资料: