Index

Hugo 事件风暴 #

Hugo的功能很全面,拥有强大的自定义函数库,可以用来制作模板、短代码等。 这在站点的构建过程中,可以帮助提供各种数据,如获取目录结构,主动加载文件。 以及页面信息汇总,方便展示分页列表或者搜索。 还可以帮助分类,提供标签服务。

支持多语言,创建国际化站点一样简单。

主题库更是能满足各种不同的个性化需求,更贴近专业使用场景,你还可以零成本切换。

Hugo是如何一步步将博客变成站点的? 下面我们还是通过 游乐场实例,来一探究竟。

goal

邀你一起来动手。 用我们最喜欢的IDE,打开我们的 游乐场项目源码。 通过直接输出日志的方式,来看一看hugo的完整构建流程。

$ cd /go/to/hugo-playground
$ go run .

process log gif

先来看一看主流程。 通过搜索以字符串"==> Process main:“开头的日志条目,我们可以了清楚的看到主函数的处理流程如下所示:

==> Process main: prepare example project file systems
==> Process main: load configurations from config.toml and themes
==> Process main: setup hugo file systems based on machine file system and configurations
==> Process main: create hugo sites based on deps
==> Process main: hugo building...
  • 准备样例文件系统。100%模拟真实项目,写入硬盘
  • 加载项目配置文件 - config.toml,并解析配置信息,如读取配置主题theme = mytheme信息
  • 准备好hugo file systems。因为hugo项目的默认结构有包含多个目录,hugo会根据不同目录生成多个文件系统。(tips: 可以用$ hugo new site <name> 命令查看默认项目目录结构)
  • 创建hugo sites。hugo支持多语言,会为每个语言创建一个site。创建前需要准备好所有的依赖,如上面提到的文件系统就是依赖之一。
  • 开始构建,并发布站点资源
main process

GoLang Template #

从主流程可以看出,Hugo的渲染思路并不复杂,就是用模板(Layouts),将不同的内容(Content)渲染成网站静态资源(Site)。

而实现这一设计的核心技术就是GoLang Template

template
  1. 通过Markdown解析器将post.md解析成结构体Post
  2. 通过GoLang Template包根据index.html创建模板实例
  3. 执行并生成渲染后的最终结果

Show Me The Code #

package main

import (
	"html/template"
	"os"
)

// index html template
var indexTemplate = "<html>\n  <body>\n    {{.Content}}\n  </body>\n</html>\n"

// Post struct with exposed filed Content
type Post struct {
	Content string
}

func main() {
	// New Post with content
	// Source file could be post.md
	post := Post{"<h2>Section</h2>\n    <p>Hello World</p>\n"}

	// New template with indexTemplate, name as "index"
	tmpl, err := template.New("index").Parse(indexTemplate)
	if err != nil {
		panic(err)
	}

	// Render post with template `index`
	// write result to os.Stdout
	err = tmpl.Execute(os.Stdout, post)
	if err != nil {
		panic(err)
	}
}

Output Example:

# body content with tag h2: Section
# tag p: Hello World
<html>
<body>
&lt;h2&gt;Section&lt;/h2&gt;
&lt;p&gt;Hello World&lt;/p&gt;

</body>
</html>

Try it yourself

了解了站点渲染的基本原理后,我们再从全局视角来看看Hugo完整的构建流程。

看Hugo对站点构建的理解和设计 #

游乐场项目源码下, 运行以下命令:

$ cd /go/to/hugo-playground
$ go run .

就可以得到构建全流程日志,如下所示

➜  hugo-playground git:(main) go run .
==> Process main: prepare example project file systems
==> Process main: load configurations from config.toml and themes
==> Process LoadConfig: start init configLoader
==> Process loadConfig: load config.toml from hard disk
==> Process loadConfig: set loaded config map to configLoader.cfg with key ''
==> Process LoadConfig: apply config defaults
==> Process LoadConfig: load modules config
==> Process decodeConfig: set mytheme as Imports in DefaultModuleConfig, Config{}
==> Process LoadConfig: collect modules with modulesConfig
==> Process collectHook: apply default mounts to project module
==> Process collectModules: set active modules to config with key 'allModules'
==> Process LoadConfig: done
==> Process main: setup hugo file systems based on machine file system and configurations
==> Process newFs: create /public folder
==> Process newFs: new base path fs &BasePathFs{}
==> Process main: create hugo sites based on deps
==> Process createSitesFromConfig: start
==> Process NewLanguages: create multiple languages, only 'en' in our case
==> Process newSite: create site with DepsCfg with language setup
==> Process media.DecodeTypes: set default media types
==> Process output.DecodeFormats: set default output formats based on media types, and customized output formats configuration
==> Process site output formats: map siteOutputFormats to every hugo page types(KindPage, KindHome...)
==> Process createSitesFromConfig: end
==> Process newHugoSites: get number of worker
==> Process newHugoSites: init HugoSites
==> Process newHugoSites: add data to h.init
==> Process newHugoSites: add layouts to h.init
==> Process newHugoSites: configLoader applyDeps
==> Process applyDeps: set cfg.TemplateProvider with DefaultTemplateProvider
==> Process applyDeps: new deps
==> Process New PathSpec: new PathSpec with all source filesystem built
==> Process New resources Spec: with pathSpec, outputFormats, MediaTypes
==> Process New content Spec: content converter provider inside
==> Process New source Spec: with source filesystem and language
==> Process applyDeps-onCreate: set site publisher as DestinationPublisher
==> Process applyDeps-onCreate site initializeSiteInfo: set site title and owner
==> Process applyDeps-onCreate pageMap: with pageTree, bundleTree and pages, sections, resources
==> Process applyDeps-onCreate site PageCollections: with pageMap
==> Process applyDeps-onCreate site RefLinker: to manage ref link
==> Process applyDeps: deps LoadResources to update template provider, need to make template ready
==> Process templateProvider Update: new TemplateExec
==> Process GoFuncs: map template.GoFuncs to funcMap
==> Process GoFuncs: map texttemplate.GoFuncs to funcMap
==> Process newTemplateNamespace: with funcMap
==> Process NewLayoutHandler: to process layout request
==> Process main: hugo building...
==> Process HugoSites Build: start
==> Process HugoSites Build process: site initialize with title and owner
==> Process readAndProcessContent: new source spec with PathSpec, ContentInclusionFilter and BaseFs Content.Fs
==> Process readAndProcessContent: collect content with PagesProcessor
==> Process AssemblePages: pageMaps to assemble all pages
==> Process pm.CreateMissingNodes: check root section
==> Process assemblePages: assembleSections firstly
==> Process assemblePages: walk pageMap pages
==> Process pageMap pages.Walk: new page from content node
==> Process pageMap pages.Walk: assemble resources
==> Process render: h.init layouts do start
==> Process newHugoSites: h.init run s.Tmpl().MarkReady
==> Process render: init site render formats
==> Process site preapre pages for render: pageState init output format
==> Process pageState: init page do start
==> Process pageState: init contentProvider with page content output
==> Process site preapre pages for render: pageState init output format
==> Process pageState: init page do start
==> Process pageState init: new page paths
==> Process pageState init: new page output
==> Process pageState init: init OutputFormatsProvider, targetPathDescriptor, SitesProvider
==> Process pageState: new page content output
==> Process pageState: init contentProvider with page content output
==> Process site preapre pages for render: pageState init output format
==> Process pageState: init page do start
==> Process pageState init: new page paths
==> Process pageState init: new page output
==> Process pageState init: init OutputFormatsProvider, targetPathDescriptor, SitesProvider
==> Process pageState: new page content output
==> Process pageState: init contentProvider with page content output
==> Process render: render start with siteRenderContext
==> Process Site render: render pages
==> Process renderPages: start 3 workers to do page rendering
==> Process render page: resolve template for page
==> Process render page: resolve template for page
==> Process render page: resolve template for page
==> Process render and write page: render for template
==> Process output initContent: init render hooks
==> Process output initContent: content to render
==> Process render and write page: publish page
==> Process render and write page: publish page
==> Process Site render: render 404
==> Process pageState init: new page paths
==> Process pageState init: new page output
==> Process pageState init: init OutputFormatsProvider, targetPathDescriptor, SitesProvider
==> Process hugoSite render: cross sites robots TXT
==> Process pageState init: new page paths
==> Process pageState init: new page output
==> Process pageState init: init OutputFormatsProvider, targetPathDescriptor, SitesProvider
==> Process render and write page: render for template
==> Process render and write page: publish page
==> Process HugoSites Build: done

直观感受好像没有Golang Template流程看起来那么清晰。

我们现在利用DDD领域驱动开发的事件风暴方法来对日志进行分析,以帮助我们进一步理解Hugo对站点构建这一领域的理解。

DDD之事件风暴 #

ddd event storming

为了帮助我们充分理解某一特定领域,统一团队所有成员对这一领域知识的认知。 DDD提出了事件风暴这一方法。 该方法以研讨会的形式,让领域专家和团队成员坐在一起,用对话的形式将领域中关键事件一一识别出来,并用卡片将达成一致的事件记录下来,贴在墙壁或白板上。

我们的领域专家就是Hugo的源码库,这里就是简化后的 Hugo游乐场。 而上面产出的全流程日志,就是站点领域专家对关键事件的描述。

Hugo构建之主流程 - 事件风暴版 #

ddd event storming main process

和之前的时序图版本对比:

main process

同样轻量,便于理解。

Hugo构建之LoadConfig #

通过对日志3到13行的分析,加载配置项的关键事键按发生时间排列如图如示:

==> Process main: load configurations from config.toml and themes
==> Process LoadConfig: start init configLoader
==> Process loadConfig: load config.toml from hard disk
==> Process loadConfig: set loaded config map to configLoader.cfg with key ''
==> Process LoadConfig: apply config defaults
==> Process LoadConfig: load modules config
==> Process decodeConfig: set mytheme as Imports in DefaultModuleConfig, Config{}
==> Process LoadConfig: collect modules with modulesConfig
==> Process collectHook: apply default mounts to project module
==> Process collectModules: set active modules to config with key 'allModules'
==> Process LoadConfig: done

ddd event storming LoadConfig process

  • 引入配置加载器,用来处理配置项的相关操作。
  • 从硬盘读取配置文件。因为Hugo支持多种格式的配置项,如yaml, toml, json等,所以要适配这些不同类型的配置文件。
  • 解析配置文件,根据文件后缀选用相应的解析工具,读取配置文件中的配置信息。
  • 将解析过后的map值,保存到配置加载器的根结点,对应的键值是空字符串''。并会对所有的配置键值进行小写处理,并保存,所以配置项对大小写不敏感。
  • 应用默认配置。在用户的配置文件上中没有进行说明的,将会采用Hugo默认配置项。
  • 加载模块配置项。这里引入了模块的概念,但和Nginx的模块有所区别。Hugo的模块指的是包含Hugo文件标准结构的目录,如主题,用户项目本身。 其中主题是可以嵌套主题的,也就是说我们所使用的Theme A可能是基于Theme B构建的,而项目本身也有自己的标准目录,也可以有自己的layouts文件夹。 那覆盖顺序就是Project Layouts > Theme A Layouts > Theme B Layouts。Hugo模块只有两类,Project模块和其它模块。 从文件结构上讲,其实是一样的。
  • 解析模块配置项。在项目配置文件config.toml中通常会配置主题项:theme = “mytheme”,这就是告诉配置加载器,本项目依赖于这个主题,即这个模块。
  • 设置解析结果到模块配置项。既然模块之间可以相互依赖,那加载资源的时候就有加载顺序,需要将依赖关系提前梳理好。 Hugo实现这一需求依赖的是模块配置项的Imports字段。
  • 根据模块配置,收集模块。通过上一步的关系梳理,现在就是加载模块的时候了,将模块文件结构,按标准文件结构解析成一个个的挂载项,为构建文件系统做准备。
  • 应用默认挂载到项目模块。项目本身也是遵循的标准文件结构的,也需要将文件结构消化成相应的挂载项。
  • 将活跃模块添加到配置项。在我们的用例中,有Project和Mytheme两个模块。在配置项中,对应的字段是"allModule”。

配置项包含了所有配置相关的信息,除了用户自定义的字段,还有Hugo的默认配置。 其中较为特殊的就是"allModule"字段,里面包含了包括项目模块在内的所有模块信息。 LoadConfig不仅包含常规配置项信息,不包含了模块的配置信息。

Hugo构建之HugoFs #

通过对日志14到24行的分析,可以看到Hugo对自身File System的理解和设计:

==> Process main: setup hugo file systems based on machine file system and configurations
==> Process newFs: create /public folder
==> Process newFs: new base path fs &BasePathFs{}
==> Process main: create hugo sites based on deps
==> Process createSitesFromConfig: start
==> Process NewLanguages: create multiple languages, only 'en' in our case
==> Process newSite: create site with DepsCfg with language setup
==> Process media.DecodeTypes: set default media types
==> Process output.DecodeFormats: set default output formats based on media types, and customized output formats configuration
==> Process site output formats: map siteOutputFormats to every hugo page types(KindPage, KindHome...)
==> Process createSitesFromConfig: end

ddd event storming Hugo Fs

  • 创建public文件夹。public是默认的站点发布文件夹名,当然,也支持自定义。
  • 创建基本路径Fs。只包含源项目文件系统和发布文件系统,也是Hugo文件系统基础所在。
  • 创建多语言。Hugo支持多语言站点发布。会是语言为单位,在包含基础配置信息的基础上,准备相应的语言配置信息,也可以将语言描述对象理解为语言配置项。
  • 设置站点依赖。如果要构建站点,会依赖很多信息。Hugo将所有的信息都放在了依赖里。如上面提到的基础路径文件系统及配置项。当然远不止这两项。
  • 设置站点语言。Hugo公为每个语言生成一个站点。
  • 设置站点默认媒体类型。站点资源要符合Web服务器要求,就需要保证站点的媒体内容是Hugo所支持的,并可正确转换成Web服务器支持的媒体类型。
  • 设置站点默认输出格式。用户产出的内容将会以Web服务器支持的格式进行输出,所以输出格式也是站点的一部分。
  • 映射站点输出格式到所有页面类型。Hugo将页面分为了五类,每一类和输出格式是如何对应的,也需要在这里进行说明。

从HugoFs可以理解到。 Hugo认为一个站点,需要包含:

  • 源,也就是用户创造的内容。
  • 发布地址,public文件系统
  • 配置信息,包含用户自定义、默认配置,及所有模块的相关信息。
  • 语言,和站点一一对应,多个语言对应多个站点
  • 输出格式,以什么形式进行渲染。输出格式中包含了媒体类型,如HTML格式通常是text/html类型。

Hugo构建之Sites #

通过对日志25到46行的分析,可以看到Hugo对Sites的理解和设计:

==> Process newHugoSites: get number of worker
==> Process newHugoSites: init HugoSites
==> Process newHugoSites: add data to h.init
==> Process newHugoSites: add layouts to h.init
==> Process newHugoSites: configLoader applyDeps
==> Process applyDeps: set cfg.TemplateProvider with DefaultTemplateProvider
==> Process applyDeps: new deps
==> Process New PathSpec: new PathSpec with all source filesystem built
==> Process New resources Spec: with pathSpec, outputFormats, MediaTypes
==> Process New content Spec: content converter provider inside
==> Process New source Spec: with source filesystem and language
==> Process applyDeps-onCreate: set site publisher as DestinationPublisher
==> Process applyDeps-onCreate site initializeSiteInfo: set site title and owner
==> Process applyDeps-onCreate pageMap: with pageTree, bundleTree and pages, sections, resources
==> Process applyDeps-onCreate site PageCollections: with pageMap
==> Process applyDeps-onCreate site RefLinker: to manage ref link
==> Process applyDeps: deps LoadResources to update template provider, need to make template ready
==> Process templateProvider Update: new TemplateExec
==> Process GoFuncs: map template.GoFuncs to funcMap
==> Process GoFuncs: map texttemplate.GoFuncs to funcMap
==> Process newTemplateNamespace: with funcMap
==> Process NewLayoutHandler: to process layout request

ddd event storming sites process

  • 获取worker容量。将会决定在渲染过程中有几个goroutine用来渲染页面。
  • 初始化Hugo Sites。在Hugo里有两个site,一个是Sites,另一个是HugoSites。Site和Language一一对应,HugoSites和Site是一对多的关系。
  • 注册data相关处理行为到初始化。Hugo用到了lazy init的,因为有一些初始化的工作依赖于某些处理后的数据,需要在一开始说明,但在适当的时机激活。
  • 注册layouts相关处理行为到初始化。同上,Hugo layouts就相当于是golang里的模板,这里会真正加载内部模板和用户自定义模板。
  • 配置加载器应用依赖。将依赖关联到配置加载器中。
  • 设置默认模板提供方到配置。负责提供模板相关的服务,并且关联到配置中。
  • 新建依赖。创建依赖实例,关联之前准备的依赖项。
  • 新建路径规范。PathSpec,根据所有模块的挂载信息,和Hugo标准文件结构,生成OverlayFs格式的统一文件系统服务。
  • 新建资源规范。ResourceSpec,基于PathSpec,提供不同资源的处理工具,如图片。
  • 新建内容规范。ContentSpec,基于PathSpec中的Content.Fs,提供站点内容相关的具体服务,如内容长度统计等。
  • 新建来源规范。SourceSpec,对BaseFs中的source统一管理,提供过滤等服务。
  • 设置站点发布者。当站点准备好发布时,发布相关的工作都由发布者负责。
  • 设置站点标题。对站点基础信息标题进行设置,包括标题的处理函数。如全大写、驼峰、首标题第一个字首字母大写等。
  • 设置站点所有者。既站点的拥有者。
  • 初始化页面映射。PageMap,一个站点会有一个PageMap,用来管理所有的Page,在Hugo里Page指所有站点不同类型的页面,像Home, Section, Term等。
  • 设置站点页面收集器。如何将实际用户创作的内容,如post、页面管理生成的内容,如section和目录结构一一映射,并用PageMap管理起来,谁来负责 - 页面收集器。
  • 设置站点索引链接器。页面是一个个的页面,页面之间的关系需要索引来进行管理。
  • 通过模板提供者开始更新。通过上面的配置信息,对模板配置进行更新。
  • 新建模板执行器。当模板全部加载和转换完成后,新建TemplateExec对外提供执行功能,将内容按模板进行渲染。
  • 收集模板函数到函数映射。Golang Template提供了一些内置函数,方便用程序的形式处理一些内容,如print。Hugo对此进行了增强,定义了很多功能强大的函数。
  • 收集文本函数到函数映射。Hugo提供了两大类函数,一大类来自于默认template,另一大类来自于text template。
  • 新建模板命名空间。前模板相关信息封闭在命名空间中,这样方便治理各类模板和函数。
  • 新建layout处理器。在对外提供服务时,还可以根据需求对layout进行最后的转换,以满足一些定制化需求。

Hugo构建之LoadConfig #

通过对日志47到99行的分析,可以看到Hugo如何构建站点的:

==> Process main: hugo building...
==> Process HugoSites Build: start
==> Process HugoSites Build process: site initialize with title and owner
==> Process readAndProcessContent: new source spec with PathSpec, ContentInclusionFilter and BaseFs Content.Fs
==> Process readAndProcessContent: collect content with PagesProcessor
==> Process AssemblePages: pageMaps to assemble all pages
==> Process pm.CreateMissingNodes: check root section
==> Process assemblePages: assembleSections firstly
==> Process assemblePages: walk pageMap pages
==> Process pageMap pages.Walk: new page from content node
==> Process pageMap pages.Walk: assemble resources
==> Process render: h.init layouts do start
==> Process newHugoSites: h.init run s.Tmpl().MarkReady
==> Process render: init site render formats
==> Process site preapre pages for render: pageState init output format
==> Process pageState: init page do start
==> Process pageState: init contentProvider with page content output
==> Process site preapre pages for render: pageState init output format
==> Process pageState: init page do start
==> Process pageState init: new page paths
==> Process pageState init: new page output
==> Process pageState init: init OutputFormatsProvider, targetPathDescriptor, SitesProvider
==> Process pageState: new page content output
==> Process pageState: init contentProvider with page content output
==> Process site preapre pages for render: pageState init output format
==> Process pageState: init page do start
==> Process pageState init: new page paths
==> Process pageState init: new page output
==> Process pageState init: init OutputFormatsProvider, targetPathDescriptor, SitesProvider
==> Process pageState: new page content output
==> Process pageState: init contentProvider with page content output
==> Process render: render start with siteRenderContext
==> Process Site render: render pages
==> Process renderPages: start 3 workers to do page rendering
==> Process render page: resolve template for page
==> Process render page: resolve template for page
==> Process render page: resolve template for page
==> Process render and write page: render for template
==> Process output initContent: init render hooks
==> Process output initContent: content to render
==> Process render and write page: publish page
==> Process render and write page: publish page
==> Process Site render: render 404
==> Process pageState init: new page paths
==> Process pageState init: new page output
==> Process pageState init: init OutputFormatsProvider, targetPathDescriptor, SitesProvider
==> Process hugoSite render: cross sites robots TXT
==> Process pageState init: new page paths
==> Process pageState init: new page output
==> Process pageState init: init OutputFormatsProvider, targetPathDescriptor, SitesProvider
==> Process render and write page: render for template
==> Process render and write page: publish page
==> Process HugoSites Build: done

ddd event storming build process

  • 检查站点标题。开始构建之前,先检查一下是否设置好了这些基础信息。
  • 检查站点所有者信息。同上。
  • 为内容Fs新建来源规范。SourceSpec,针对正在构建的站点,进一步定制化,说明哪些文件需要处理,哪些应该忽略。
  • 用页面收集器收集站点内容。上一阶段是准备阶段,构建阶段是真正地收集阶段。
  • 用页面映射聚集所有页面。一个站点对应一个PageMap,根据上面收集的结果,对收集到的页面进行一一映射管理。
  • 走查缺失结点。Hugo引入了section概念,这样方便管理内容的层级关系。 在收集页面时,Hugo用的是 基数树对页面进行管理的。 这样对于section而言,就需要在树结构中补全相应缺失结点。
  • 聚集章节。章节也是一种页面类型,上面是针对页面走查缺失的章节结点,对页面信息进行补全。这里是专门用来处理章节。
  • 为内容结点创建页面。我们最终的目标是把收集到的信息,用模板给渲染出来,这里创建的页面PageState结构体,就是上面代码例子中的Post,用来为渲染做准备。
  • 聚集页面资源。一篇博客中会引用一起图片,数据等,都属于博客的资源,需要和页面对应上。
  • 回调layout注册项进行初始化。在一开始注册的模板提供方和注册到lazy init里的加调方法,对模板内容文件进行处理。如模板解析,加入命名空间等。
  • 初始化渲染格式。通过前面的输出信息,明确渲染要求,并进行输出。
  • 初始化页面状态。PageState,前面是准备初始化所需的内容,这里专门进行初始化。 作为渲染的关键,因为所有模板需要的信息都依赖于这个结构体,这导致PageState结构体体积巨大。
  • 初始化内容提供商。对于渲染阶段而言,重点是将内容转换为站点文件,抽象出对应的服务,有助于封装隐藏在背后的复杂度,方便消费者使用。
  • 标明页面内容输出格式。Hugo支持对同一页面输出多种格式,这里就是为满足用户的输出需求进行准备。
  • 新建页面路径。PagePath,为PageState准备页面路径相关的信息,帮助说明输出格式,及目标发布地址。
  • 新建页面输出。PageOutput,包含所有输出所需要的信息,如页面渲染提供商、Table of Content提供商等。
  • 初始化页面输出供应商。获取相应的配置信息并初始化。
  • 初始化页面目标路径描述符。页面将要发布到哪个目标,需要用描述符承载相应的信息。
  • 初始化页面站点供应商。和站点相关的信息在此维护。
  • 新建页面内容输出。和页面内容输出相关的信息在此提供。
  • 用页面输出初始化内容供应商。内容供应商依赖于页面内容输出。
  • Worker开始渲染页面。当渲染流程中的对象都准备就绪时,由上一步提供的worker开始获取渲染业务,并进行实际渲染。
  • 为页面查找模板。通过模板提供商查找页面模板,为了组织起所有内容,Hugo提供了模板查找逻辑,帮助定位合适的模板。
  • 用模板渲染页面。通过模板执行器,对PageState进行渲染。
  • 初始化渲染回调钩子。提供渲染过程中的交互接口。
  • 初始化渲染内容。为页面输出配置相关博客内容。
  • 发布页面。将渲染好的内容发布到对应目标地址。
  • 渲染404。对应用户博客,404页面属于独立页面,可以重用上面的流程进行渲染和发布。
  • 渲染robots TXT说明文件。对于搜索引擎提供爬取说明。和404页面一样属于独立页面。

小结 #

hugo whole process events

本章用DDD事件驱动开发中的事件风暴Event Storming方法,对Hugo构建全流程中的关键事件进行了收集和分析,以帮助大家了解到构建的全过程。

可以看到,基于Golang Template提供的基础功能,Hugo在此基础上,进行了增强:

hugo whole process with golang template

Hugo眼中的站点构建,需要涵盖从用户博客项目到发布站点的全流程。

首先要通过配置文件理解用户对站点的具体需求,如想用哪个风格的主题,站点叫什么名字,要支持几种语言,以及以什么格式进行输出。 为了减轻用户的认知负担,Hugo采用主动配置加默认配置的策略,帮助用户补全构建站点所需要的其它信息。

了解了用户需求后,Hugo需要将用户的博客内容有效地进行组织。 项目内包含了主题,甚至主题里面还嵌套了主题。 还包含了项目本身的所有文件。 无论是主题,还是博客本身,都遵循下面这一目录结构:

.
├── archetypes
├── config.toml
├── content
├── data
├── layouts
├── public
├── static
└── themes

在组织这些文件时,就需要考虑这些文件的依赖关系,像layouts之间是覆盖的关系,这就对挂载顺序有要求。 而像static目录相对比较特殊,所有的文件是什么样,就原封不动的拷贝到最终目录。 面对这些需求,Hugo的做法是将博客本身视为一个模块,同样将主题模块也视为一个模块,并清晰的用挂载点来帮助说明具体的信息,并将解析后的信息放入配置结构中。 后续通过这些信息,用OverlayFs技术提供需要合并的服务,用BaseFs提供基础的文件服务,还有大而全的标准服务。

配置信息全了,文件系统也按需求准备好了,接下来就要开始收集站点的页面了。 Hugo为content目录定义了Section章节概念:

content
└── blog        <-- Section, because first-level dir under content/
    ├── funny-cats
    │   ├── mypost.md
    │   └── kittens         <-- Section, because contains _index.md
    │       └── _index.md
    └── tech                <-- Section, because contains _index.md
        └── _index.md

这样,在扫描所有文章的同时,还需要关注章节的相关信息。 所以在扫描完所有的文章后,还需要补全章节的结点,并进行专门的章节扫描。

最后根据输出的要求,用layouts模板对收集到的页面信息PageState进行渲染,输出并发布到指定目录。