Assemble

Assemble所做的事情很纯粹,那就是创建站点页面实例 - pageState。 因为支持多站点,contentMaps有多个。 所以Assemble不仅要创建pageState,还需要管理好所有的pages,这就用到了PageMaps。
- type pageMap struct {
- s *Site
- *contentMap
- }
- type pageMaps struct {
- workers *para.Workers
- pmaps []*pageMap
- }
实际上pageMap就是由contentMap组合而来的。 而contentMap中的组成树的结点就是contentNode。
正好,每个contentNode又对应一个pageState。
- type contentNode struct {
- p *pageState
- // Set if source is a file.
- // We will soon get other sources.
- fi hugofs.FileMetaInfo
- // The source path. Unix slashes. No leading slash.
- path string
- ...
- }
所以Assemble不仅要为前面Process处理过生成的contentNode创建pageState,还要补齐一些缺失的contentNode,如Section。
PageState
可以看出,Assemble的重点就是组建PageState,那她到底长啥样:
- type pageState struct {
- // This slice will be of same length as the number of global slice of output
- // formats (for all sites).
- pageOutputs []*pageOutput
- // This will be shifted out when we start to render a new output format.
- *pageOutput
- // Common for all output formats.
- *pageCommon
- ...
- }
从注解中可以看出普通信息将由pageCommon提供,而输出信息则由pageOutput提供。 比较特殊的是pageOutputs,是pageOutput的数组。 在 基础架构中,对这一点有作分析。 这要归因于Hugo的多站点渲染策略 - 允许在不同的站点中重用其它站点的页面。
- // hugo-playground/hugolib/page__new.go
- // line 97
- // Prepare output formats for all sites.
- // We do this even if this page does not get rendered on
- // its own. It may be referenced via .Site.GetPage and
- // it will then need an output format.
- ps.pageOutputs = make([]*pageOutput, len(ps.s.h.renderFormats))
那在Assemble中Hugo是如何组织pageState实例的呢?

从上图中,可以看出Assemble阶段主要是新建pageState。 其中pageOutput在这一阶段只是一个占位符,空的nopPageOutput。 pageCommon则是在这一阶段给赋予了很多的信息,像meta相关的信息,及各种细节信息的providers。
动手实践 - Show Me the Code of Create a PageState
- package main
- import (
- "fmt"
- "html/template"
- )
- func main() {
- outputFormats := createOutputFormats()
- renderFormats := initRenderFormats(outputFormats)
- s := &site{
- outputFormats: outputFormats,
- renderFormats: renderFormats,
- }
- ps := &pageState{
- pageOutputs: nil,
- pageOutput: nil,
- pageCommon: &pageCommon{m: &pageMeta{kind: KindPage}},
- }
- ps.init(s)
- // prepare
- ps.pageOutput = ps.pageOutputs[0]
- // render
- fmt.Println(ps.targetPaths().TargetFilename)
- fmt.Println(ps.Content())
- fmt.Println(ps.m.kind)
- }
- type site struct {
- outputFormats map[string]Formats
- renderFormats Formats
- }
- type pageState struct {
- // This slice will be of same length as the number of global slice of output
- // formats (for all sites).
- pageOutputs []*pageOutput
- // This will be shifted out when we start to render a new output format.
- *pageOutput
- // Common for all output formats.
- *pageCommon
- }
- func (p *pageState) init(s *site) {
- pp := newPagePaths(s)
- p.pageOutputs = make([]*pageOutput, len(s.renderFormats))
- for i, f := range s.renderFormats {
- ft, found := pp.targetPaths[f.Name]
- if !found {
- panic("target path not found")
- }
- providers := struct{ targetPather }{ft}
- po := &pageOutput{
- f: f,
- pagePerOutputProviders: providers,
- ContentProvider: nil,
- }
- contentProvider := newPageContentOutput(po)
- po.ContentProvider = contentProvider
- p.pageOutputs[i] = po
- }
- }
- func newPageContentOutput(po *pageOutput) *pageContentOutput {
- cp := &pageContentOutput{
- f: po.f,
- }
- initContent := func() {
- cp.content = template.HTML("<p>hello content</p>")
- }
- cp.initMain = func() {
- initContent()
- }
- return cp
- }
- func newPagePaths(s *site) pagePaths {
- outputFormats := s.renderFormats
- targets := make(map[string]targetPathsHolder)
- for _, f := range outputFormats {
- target := "/" + "blog" + "/" + f.BaseName +
- "." + f.MediaType.SubType
- paths := TargetPaths{
- TargetFilename: target,
- }
- targets[f.Name] = targetPathsHolder{
- paths: paths,
- }
- }
- return pagePaths{
- targetPaths: targets,
- }
- }
- type pagePaths struct {
- targetPaths map[string]targetPathsHolder
- }
- type targetPathsHolder struct {
- paths TargetPaths
- }
- func (t targetPathsHolder) targetPaths() TargetPaths {
- return t.paths
- }
- type pageOutput struct {
- f Format
- // These interface provides the functionality that is specific for this
- // output format.
- pagePerOutputProviders
- ContentProvider
- // May be nil.
- cp *pageContentOutput
- }
- // pageContentOutput represents the Page content for a given output format.
- type pageContentOutput struct {
- f Format
- initMain func()
- content template.HTML
- }
- func (p *pageContentOutput) Content() any {
- p.initMain()
- return p.content
- }
- // these will be shifted out when rendering a given output format.
- type pagePerOutputProviders interface {
- targetPather
- }
- type targetPather interface {
- targetPaths() TargetPaths
- }
- type TargetPaths struct {
- // Where to store the file on disk relative to the publish dir. OS slashes.
- TargetFilename string
- }
- type ContentProvider interface {
- Content() any
- }
- type pageCommon struct {
- m *pageMeta
- }
- type pageMeta struct {
- // kind is the discriminator that identifies the different page types
- // in the different page collections. This can, as an example, be used
- // to to filter regular pages, find sections etc.
- // Kind will, for the pages available to the templates, be one of:
- // page, home, section, taxonomy and term.
- // It is of string type to make it easy to reason about in
- // the templates.
- kind string
- }
- func initRenderFormats(
- outputFormats map[string]Formats) Formats {
- return outputFormats[KindPage]
- }
- func createOutputFormats() map[string]Formats {
- m := map[string]Formats{
- KindPage: {HTMLFormat},
- }
- return m
- }
- const (
- KindPage = "page"
- )
- var HTMLType = newMediaType("text", "html")
- // HTMLFormat An ordered list of built-in output formats.
- var HTMLFormat = Format{
- Name: "HTML",
- MediaType: HTMLType,
- BaseName: "index",
- }
- func newMediaType(main, sub string) Type {
- t := Type{
- MainType: main,
- SubType: sub,
- Delimiter: "."}
- return t
- }
- type Type struct {
- MainType string `json:"mainType"` // i.e. text
- SubType string `json:"subType"` // i.e. html
- Delimiter string `json:"delimiter"` // e.g. "."
- }
- type Format struct {
- // The Name is used as an identifier. Internal output formats (i.e. HTML and RSS)
- // can be overridden by providing a new definition for those types.
- Name string `json:"name"`
- MediaType Type `json:"-"`
- // The base output file name used when not using "ugly URLs", defaults to "index".
- BaseName string `json:"baseName"`
- }
- type Formats []Format
输出结果:
- /blog/index.html
- <p>hello content</p>
- page
- Program exited.
PageState线上可直接运行版本
Render
基础信息是由pageCommon提供了,那渲染过程中的输出由谁提供呢?
没错,轮到pageOutput了:

可以看到,在render阶段,pageState的pageOutput得到了最终的处理,为发布做准备了。 为了发布,最重的信息是发布什么,以及发布到哪里去。 这些信息都在pageOutput中,其中ContentProvider是提供发布内容的,而targetPathsProvider则是提供发布地址信息的。 其中地址信息主要来源于PagePath,这又和站点的RenderFormats和OutputFormats相关,哪下图所示:

其中OutputFormats, RenderFormats及PageOutput之间的关系有在 基础架构中有详细提到,这里就不再赘述。
- // We create a pageOutput for every output format combination, even if this
- // particular page isn't configured to be rendered to that format.
- type pageOutput struct {
- ...
- // These interface provides the functionality that is specific for this
- // output format.
- pagePerOutputProviders
- page.ContentProvider
- page.TableOfContentsProvider
- page.PageRenderProvider
- // May be nil.
- cp *pageContentOutput
- }
其中pageContentOutput正是实现了ContentProvider接口的实例。 其中有包含markdown文件原始信息的workContent字段,以及包含处理过后的内容content字段。 如Hugo Shortcode特性。 就是在这里经过contentToRender方法将原始信息进行处理,而最终实现的。
动手实践 - Show Me the Code of Publish
- package main
- import (
- "bytes"
- "fmt"
- "io"
- "os"
- "path/filepath"
- )
- // publisher needs to know:
- // 1: what to publish
- // 2: where to publish
- func main() {
- // 1
- // src is template executed result
- // it is the source that we need to publish
- // take a look at template executor example
- // https://c.sunwei.xyz/template-executor.html
- src := &bytes.Buffer{}
- src.Write([]byte("template executed result"))
- b := &bytes.Buffer{}
- transformers := createTransformerChain()
- if err := transformers.Apply(b, src); err != nil {
- fmt.Println(err)
- return
- }
- dir, _ := os.MkdirTemp("", "hugo")
- defer os.RemoveAll(dir)
- // 2
- // targetPath is from pageState
- // this is where we need to publish
- // take a look at page state example
- // https://c.sunwei.xyz/page-state.html
- targetPath := filepath.Join(dir, "index.html")
- if err := os.WriteFile(
- targetPath,
- bytes.TrimSuffix(b.Bytes(), []byte("\n")),
- os.ModePerm); err != nil {
- panic(err)
- }
- fmt.Println("1. what to publish: ", string(b.Bytes()))
- fmt.Println("2. where to publish: ", dir)
- }
- func (c *Chain) Apply(to io.Writer, from io.Reader) error {
- fb := &bytes.Buffer{}
- if _, err := fb.ReadFrom(from); err != nil {
- return err
- }
- tb := &bytes.Buffer{}
- ftb := &fromToBuffer{from: fb, to: tb}
- for i, tr := range *c {
- if i > 0 {
- panic("switch from/to and reset to")
- }
- if err := tr(ftb); err != nil {
- continue
- }
- }
- _, err := ftb.to.WriteTo(to)
- return err
- }
- func createTransformerChain() Chain {
- transformers := NewEmpty()
- transformers = append(transformers, func(ft FromTo) error {
- content := ft.From().Bytes()
- w := ft.To()
- tc := bytes.Replace(
- content,
- []byte("result"), []byte("transferred result"), 1)
- _, _ = w.Write(tc)
- return nil
- })
- return transformers
- }
- // Chain is an ordered processing chain. The next transform operation will
- // receive the output from the previous.
- type Chain []Transformer
- // Transformer is the func that needs to be implemented by a transformation step.
- type Transformer func(ft FromTo) error
- // FromTo is sent to each transformation step in the chain.
- type FromTo interface {
- From() BytesReader
- To() io.Writer
- }
- // BytesReader wraps the Bytes method, usually implemented by bytes.Buffer, and an
- // io.Reader.
- type BytesReader interface {
- // Bytes The slice given by Bytes is valid for use only until the next buffer modification.
- // That is, if you want to use this value outside of the current transformer step,
- // you need to take a copy.
- Bytes() []byte
- io.Reader
- }
- // NewEmpty creates a new slice of transformers with a capacity of 20.
- func NewEmpty() Chain {
- return make(Chain, 0, 2)
- }
- // Implements contentTransformer
- // Content is read from the from-buffer and rewritten to to the to-buffer.
- type fromToBuffer struct {
- from *bytes.Buffer
- to *bytes.Buffer
- }
- func (ft fromToBuffer) From() BytesReader {
- return ft.from
- }
- func (ft fromToBuffer) To() io.Writer {
- return ft.to
- }
输出结果:
1. what to publish: template executed transferred result
2. where to publish: /tmp/hugo2834984546
Program exited.
Publish线上可直接运行版本
以上就是go开源Hugo站点构建三步曲之集结渲染的详细内容,更多关于go Hugo站点构建集结渲染的资料请关注w3xue其它相关文章!