«

go开源Hugo站点怎么构建集结渲染

时间:2024-3-8 21:23     作者:韩俊     分类: Go语言


本篇内容主要讲解“go开源Hugo站点怎么构建集结渲染”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“go开源Hugo站点怎么构建集结渲染”吧!

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("
")),
        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线上可直接运行版本

标签: golang

热门推荐