持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情 ,祝福祖国,生日快乐 :)
在Hugo事件风暴中,我们了解到Hugo的设计理念 - 给用户提供始终如一的轻松写作体现。 而实现这一理念则是以Golang Template为基础,开发出更多实用功能,让内容创造者专于内容创作的同时,还拥有良好的体验。
让我们回顾一下Golang Template的实现步骤:
再看看Hugo是如何围绕其展开的:
Hugo围绕着Golang Template做了很多设计,现在我们通过和模板相关的领域事件,一起来看看模板的生命周期,从而能够有更全面的理解。
Hugo模板生命周期领域事件
还是从领域事件入手,来看看有哪些关键事件和模板强相关:
除了上面和Golang Template一一映射的事件外,还有更细节的事件,为了直观好看,我们把这些事件收集到一起来进行分析:
可以看到和模板强相关的事件,主要集中在创建HugoSites和Build阶段。 进一步细分,可发现模板生命周期可分为三个阶段:
- 开始阶段,包括注册回调,并选定模板服务提供方,并出发模板更新。
- 注册layouts回调到HugoSites初始化字段
- 设置默认模板提供方到配置项
- 通知模板提供者开始更新
- 准备阶段,包括准备好模板执行器,收集模板相关的功能函数,解析Hugo内置和用户自定义模板,将所有模板存储到模板命名空间,以及用layout处理器连接layout和模板。
- 新建模板执行器
- 收集模板函数到函数映射
- 收集文本函数到函数映射
- 新建模板命名空间
- 新建layout处理器
- 渲染阶段,准备好页面内容后,回调在开始阶段注册的layouts事项,通过layout处理器查找相应的模板,最终用模板进行渲染,生成站点页面
- 为内容结点创建页面
- 回调layout注册项进行初始化
- 为页面查找模板
- 用模板渲染页面
事件可以帮助我们清晰地了解到Hugo设计的模板生命周期。 下面我们再通过Hugo游乐场源码梳理一遍具体实现流程,好让我们能更立体地了解到模板生命周期,同时也可以为后面的代码实现讲解章节做好准备。
Template vs Layouts
我们先来看看Hugo中两个容易混淆的概念,Template和Layouts。
在创建自定义Hugo主题的时候,我们接触最多的就是Layouts。 官方文档解释Layouts就是用来当模板的,这样解释并没有问题,但这会让我们很容易产生一种Layouts既模板的错觉,并将Layouts和模板直接划上等号。
但真的是这样的吗,让我们从代码层面看看他们的关联:
通过查看新建主题的目录结构,我们会发现自动生成的的文件主要在layouts目录下,里面有首页模板,还有页头和页尾模板,等等。
在代码中,Layouts的出现通过都是以[]string字符串数组的形式出现的。
也就是说在代码中Layouts就是用来记录layout相关的文件路径信息的。
如果想将Layouts转换成Golang Template,首先需要将其转换成templateInfo。
并记录文件名,分析是否是文本类型,将layout文件内容以字符串存储在tempalte字段中。
其中是否是文本类型,涉及到Golang Template的设计知识。
Golang将Template按类型进行了划分。如HTML和Text,通过对HTML标签进行转换,最终也会被转换成Text。 说到底,通过对不同模板类型的转换,都会变成文本类型。
通过templateInfo,最终Hugo会生成真正的Hugo模板结构体templateState。
可以看出该结构体实现了Template接口。
所以我们可以得出结论:Layouts不等于Template,是制作Template的原材料。
弄清了Template和Layouts之间的关系,我们分别来看看Template生命周期中的开始、准备和渲染阶段。
开始阶段
HugoSites中的init字段是hugoSitesInit类型的,其中就包含了lazy.init类型的layouts。
这样就可以在layouts字段注册一些回调方法,方便在时机成熟的时候回调。
同时,对于HugoSites而言,直接面对的是模板服务的提供商,所以需要在这个阶段将TemplateProvider作为提供商,设置在配置信息中。
等信息都准备妥当后,就可以通知模板服务提供商开始工作更新了。
准备阶段
对外提供整体服务,并和Deps关联的是templateExec。
包含了texttemplate.executer和templateHandler,以及所有的模板功能函数。
颜色表明各结构之间的关联关系。
可以看出,texttemplate.executer包含了templateExecHelper,因为在执行的过程中,通过对模板的分析,可能会用上功能函数。
而templateHander则需要处理和template相关的一些操作。
main字段是templateNamespace类型,里面存储了HTML和Text原型信息,并存储了由原型创建的所有templateState在templateStateMap中。
layoutHandler则是连接layout和template的关键,比如通过layout查询template时,就由layoutHandler全权负责。
渲染阶段
结合开始阶段和准备阶段一览:
通过前期的准备和组织,我们来看看渲染阶段是怎么发生的:
页面渲染发生在site_render.go中,从pageRender正式开始。
总共分为两大步,一是page.resolveTemplate解析模板,拿到模板后再开始site.renderAndWritePage渲染和写入页面。
-
解析模板 因为Site组合了Deps,所以也和Deps一样,同样持有
templateExec信息,通过调用templateExec的LookupLayout方法,查询模板信息。 因为这些模板信息都已经存储在了templateNamespace里的templateStateMap中。 -
渲染页面 在
pageRender中已经拥有了页面信息pageState,通过上一步又获取了模板信息,所以是时候开始真正地site.renderForTemplate渲染了。 还是通过templateExec调用Execute方法。 因为当前的执行器是texttemplate.executer类型,所以真正地执行是在texttemplate.executer的ExecuteWithContext方法中。 这里是直接用的Golang Template源码,而不是调用Golang的默认包。 因为Golang默认包中自带的功能函数,并不能完全满足Hugo的诉求。 在后续代码实现章节将会详细讲述,这里还是专注在基础架构的初步理解上。
小结
从Golang Template应用示例开始,我们了解到了Golang中模板工作的基本流程。 这有助于我们进一步理解Hugo的设计和实现。
通过对Hugo领域事件中模板强相关的核心事件进行分析,我们将Hugo模板的生命周期大致分为三个阶段:开始、准备、渲染。
为了立体的理解模板生命周期,我们不仅从领域事件进行梳理,还从代码结构进行分析。
看到了Hugo是基于HTML和Text模板原型,帮助将所有的Layouts转换成Template,并存储在了Template命名空间中。
还看到为了拓展Golang Template的功能,Hugo将强大的自定义函数保存在了执行器中。
这让模板在渲染过程中,有了更多的帮手。
而这一切,都封装在了对外统一提供的服务templateExec中,不仅对内进行封装,还对外提供了便捷。
还有更多有意思的事情,比如Hugo为什么不能直接用Golang内置的Template包,而要独立维护? 我们也会在后续代码实现章节,进一步展开讲解。 和大家一起,一探究竟。