经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
go开源Hugo站点构建三步曲之集结渲染
来源:jb51  时间:2023/2/27 9:42:02  对本文有异议

Assemble

Assemble所做的事情很纯粹,那就是创建站点页面实例 - pageState。 因为支持多站点,contentMaps有多个。 所以Assemble不仅要创建pageState,还需要管理好所有的pages,这就用到了PageMaps。

  1. type pageMap struct {
  2. s *Site
  3. *contentMap
  4. }
  5. type pageMaps struct {
  6. workers *para.Workers
  7. pmaps []*pageMap
  8. }

实际上pageMap就是由contentMap组合而来的。 而contentMap中的组成树的结点就是contentNode。

正好,每个contentNode又对应一个pageState。

  1. type contentNode struct {
  2. p *pageState
  3. // Set if source is a file.
  4. // We will soon get other sources.
  5. fi hugofs.FileMetaInfo
  6. // The source path. Unix slashes. No leading slash.
  7. path string
  8. ...
  9. }

所以Assemble不仅要为前面Process处理过生成的contentNode创建pageState,还要补齐一些缺失的contentNode,如Section。

PageState

可以看出,Assemble的重点就是组建PageState,那她到底长啥样:

  1. type pageState struct {
  2. // This slice will be of same length as the number of global slice of output
  3. // formats (for all sites).
  4. pageOutputs []*pageOutput
  5. // This will be shifted out when we start to render a new output format.
  6. *pageOutput
  7. // Common for all output formats.
  8. *pageCommon
  9. ...
  10. }

从注解中可以看出普通信息将由pageCommon提供,而输出信息则由pageOutput提供。 比较特殊的是pageOutputs,是pageOutput的数组。 在 基础架构中,对这一点有作分析。 这要归因于Hugo的多站点渲染策略 - 允许在不同的站点中重用其它站点的页面。

  1. // hugo-playground/hugolib/page__new.go
  2. // line 97
  3. // Prepare output formats for all sites.
  4. // We do this even if this page does not get rendered on
  5. // its own. It may be referenced via .Site.GetPage and
  6. // it will then need an output format.
  7. 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 

  1. package main
  2. import (
  3. "fmt"
  4. "html/template"
  5. )
  6. func main() {
  7. outputFormats := createOutputFormats()
  8. renderFormats := initRenderFormats(outputFormats)
  9. s := &site{
  10. outputFormats: outputFormats,
  11. renderFormats: renderFormats,
  12. }
  13. ps := &pageState{
  14. pageOutputs: nil,
  15. pageOutput: nil,
  16. pageCommon: &pageCommon{m: &pageMeta{kind: KindPage}},
  17. }
  18. ps.init(s)
  19. // prepare
  20. ps.pageOutput = ps.pageOutputs[0]
  21. // render
  22. fmt.Println(ps.targetPaths().TargetFilename)
  23. fmt.Println(ps.Content())
  24. fmt.Println(ps.m.kind)
  25. }
  26. type site struct {
  27. outputFormats map[string]Formats
  28. renderFormats Formats
  29. }
  30. type pageState struct {
  31. // This slice will be of same length as the number of global slice of output
  32. // formats (for all sites).
  33. pageOutputs []*pageOutput
  34. // This will be shifted out when we start to render a new output format.
  35. *pageOutput
  36. // Common for all output formats.
  37. *pageCommon
  38. }
  39. func (p *pageState) init(s *site) {
  40. pp := newPagePaths(s)
  41. p.pageOutputs = make([]*pageOutput, len(s.renderFormats))
  42. for i, f := range s.renderFormats {
  43. ft, found := pp.targetPaths[f.Name]
  44. if !found {
  45. panic("target path not found")
  46. }
  47. providers := struct{ targetPather }{ft}
  48. po := &pageOutput{
  49. f: f,
  50. pagePerOutputProviders: providers,
  51. ContentProvider: nil,
  52. }
  53. contentProvider := newPageContentOutput(po)
  54. po.ContentProvider = contentProvider
  55. p.pageOutputs[i] = po
  56. }
  57. }
  58. func newPageContentOutput(po *pageOutput) *pageContentOutput {
  59. cp := &pageContentOutput{
  60. f: po.f,
  61. }
  62. initContent := func() {
  63. cp.content = template.HTML("<p>hello content</p>")
  64. }
  65. cp.initMain = func() {
  66. initContent()
  67. }
  68. return cp
  69. }
  70. func newPagePaths(s *site) pagePaths {
  71. outputFormats := s.renderFormats
  72. targets := make(map[string]targetPathsHolder)
  73. for _, f := range outputFormats {
  74. target := "/" + "blog" + "/" + f.BaseName +
  75. "." + f.MediaType.SubType
  76. paths := TargetPaths{
  77. TargetFilename: target,
  78. }
  79. targets[f.Name] = targetPathsHolder{
  80. paths: paths,
  81. }
  82. }
  83. return pagePaths{
  84. targetPaths: targets,
  85. }
  86. }
  87. type pagePaths struct {
  88. targetPaths map[string]targetPathsHolder
  89. }
  90. type targetPathsHolder struct {
  91. paths TargetPaths
  92. }
  93. func (t targetPathsHolder) targetPaths() TargetPaths {
  94. return t.paths
  95. }
  96. type pageOutput struct {
  97. f Format
  98. // These interface provides the functionality that is specific for this
  99. // output format.
  100. pagePerOutputProviders
  101. ContentProvider
  102. // May be nil.
  103. cp *pageContentOutput
  104. }
  105. // pageContentOutput represents the Page content for a given output format.
  106. type pageContentOutput struct {
  107. f Format
  108. initMain func()
  109. content template.HTML
  110. }
  111. func (p *pageContentOutput) Content() any {
  112. p.initMain()
  113. return p.content
  114. }
  115. // these will be shifted out when rendering a given output format.
  116. type pagePerOutputProviders interface {
  117. targetPather
  118. }
  119. type targetPather interface {
  120. targetPaths() TargetPaths
  121. }
  122. type TargetPaths struct {
  123. // Where to store the file on disk relative to the publish dir. OS slashes.
  124. TargetFilename string
  125. }
  126. type ContentProvider interface {
  127. Content() any
  128. }
  129. type pageCommon struct {
  130. m *pageMeta
  131. }
  132. type pageMeta struct {
  133. // kind is the discriminator that identifies the different page types
  134. // in the different page collections. This can, as an example, be used
  135. // to to filter regular pages, find sections etc.
  136. // Kind will, for the pages available to the templates, be one of:
  137. // page, home, section, taxonomy and term.
  138. // It is of string type to make it easy to reason about in
  139. // the templates.
  140. kind string
  141. }
  142. func initRenderFormats(
  143. outputFormats map[string]Formats) Formats {
  144. return outputFormats[KindPage]
  145. }
  146. func createOutputFormats() map[string]Formats {
  147. m := map[string]Formats{
  148. KindPage: {HTMLFormat},
  149. }
  150. return m
  151. }
  152. const (
  153. KindPage = "page"
  154. )
  155. var HTMLType = newMediaType("text", "html")
  156. // HTMLFormat An ordered list of built-in output formats.
  157. var HTMLFormat = Format{
  158. Name: "HTML",
  159. MediaType: HTMLType,
  160. BaseName: "index",
  161. }
  162. func newMediaType(main, sub string) Type {
  163. t := Type{
  164. MainType: main,
  165. SubType: sub,
  166. Delimiter: "."}
  167. return t
  168. }
  169. type Type struct {
  170. MainType string `json:"mainType"` // i.e. text
  171. SubType string `json:"subType"` // i.e. html
  172. Delimiter string `json:"delimiter"` // e.g. "."
  173. }
  174. type Format struct {
  175. // The Name is used as an identifier. Internal output formats (i.e. HTML and RSS)
  176. // can be overridden by providing a new definition for those types.
  177. Name string `json:"name"`
  178. MediaType Type `json:"-"`
  179. // The base output file name used when not using "ugly URLs", defaults to "index".
  180. BaseName string `json:"baseName"`
  181. }
  182. type Formats []Format

输出结果:

  1. /blog/index.html
  2. <p>hello content</p>
  3. page
  4. Program exited.

PageState线上可直接运行版本

Render 

基础信息是由pageCommon提供了,那渲染过程中的输出由谁提供呢?

没错,轮到pageOutput了:

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

其中OutputFormats, RenderFormats及PageOutput之间的关系有在 基础架构中有详细提到,这里就不再赘述。

  1. // We create a pageOutput for every output format combination, even if this
  2. // particular page isn't configured to be rendered to that format.
  3. type pageOutput struct {
  4. ...
  5. // These interface provides the functionality that is specific for this
  6. // output format.
  7. pagePerOutputProviders
  8. page.ContentProvider
  9. page.TableOfContentsProvider
  10. page.PageRenderProvider
  11. // May be nil.
  12. cp *pageContentOutput
  13. }

其中pageContentOutput正是实现了ContentProvider接口的实例。 其中有包含markdown文件原始信息的workContent字段,以及包含处理过后的内容content字段。 如Hugo Shortcode特性。 就是在这里经过contentToRender方法将原始信息进行处理,而最终实现的。

动手实践 - Show Me the Code of Publish

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "os"
  7. "path/filepath"
  8. )
  9. // publisher needs to know:
  10. // 1: what to publish
  11. // 2: where to publish
  12. func main() {
  13. // 1
  14. // src is template executed result
  15. // it is the source that we need to publish
  16. // take a look at template executor example
  17. // https://c.sunwei.xyz/template-executor.html
  18. src := &bytes.Buffer{}
  19. src.Write([]byte("template executed result"))
  20. b := &bytes.Buffer{}
  21. transformers := createTransformerChain()
  22. if err := transformers.Apply(b, src); err != nil {
  23. fmt.Println(err)
  24. return
  25. }
  26. dir, _ := os.MkdirTemp("", "hugo")
  27. defer os.RemoveAll(dir)
  28. // 2
  29. // targetPath is from pageState
  30. // this is where we need to publish
  31. // take a look at page state example
  32. // https://c.sunwei.xyz/page-state.html
  33. targetPath := filepath.Join(dir, "index.html")
  34. if err := os.WriteFile(
  35. targetPath,
  36. bytes.TrimSuffix(b.Bytes(), []byte("\n")),
  37. os.ModePerm); err != nil {
  38. panic(err)
  39. }
  40. fmt.Println("1. what to publish: ", string(b.Bytes()))
  41. fmt.Println("2. where to publish: ", dir)
  42. }
  43. func (c *Chain) Apply(to io.Writer, from io.Reader) error {
  44. fb := &bytes.Buffer{}
  45. if _, err := fb.ReadFrom(from); err != nil {
  46. return err
  47. }
  48. tb := &bytes.Buffer{}
  49. ftb := &fromToBuffer{from: fb, to: tb}
  50. for i, tr := range *c {
  51. if i > 0 {
  52. panic("switch from/to and reset to")
  53. }
  54. if err := tr(ftb); err != nil {
  55. continue
  56. }
  57. }
  58. _, err := ftb.to.WriteTo(to)
  59. return err
  60. }
  61. func createTransformerChain() Chain {
  62. transformers := NewEmpty()
  63. transformers = append(transformers, func(ft FromTo) error {
  64. content := ft.From().Bytes()
  65. w := ft.To()
  66. tc := bytes.Replace(
  67. content,
  68. []byte("result"), []byte("transferred result"), 1)
  69. _, _ = w.Write(tc)
  70. return nil
  71. })
  72. return transformers
  73. }
  74. // Chain is an ordered processing chain. The next transform operation will
  75. // receive the output from the previous.
  76. type Chain []Transformer
  77. // Transformer is the func that needs to be implemented by a transformation step.
  78. type Transformer func(ft FromTo) error
  79. // FromTo is sent to each transformation step in the chain.
  80. type FromTo interface {
  81. From() BytesReader
  82. To() io.Writer
  83. }
  84. // BytesReader wraps the Bytes method, usually implemented by bytes.Buffer, and an
  85. // io.Reader.
  86. type BytesReader interface {
  87. // Bytes The slice given by Bytes is valid for use only until the next buffer modification.
  88. // That is, if you want to use this value outside of the current transformer step,
  89. // you need to take a copy.
  90. Bytes() []byte
  91. io.Reader
  92. }
  93. // NewEmpty creates a new slice of transformers with a capacity of 20.
  94. func NewEmpty() Chain {
  95. return make(Chain, 0, 2)
  96. }
  97. // Implements contentTransformer
  98. // Content is read from the from-buffer and rewritten to to the to-buffer.
  99. type fromToBuffer struct {
  100. from *bytes.Buffer
  101. to *bytes.Buffer
  102. }
  103. func (ft fromToBuffer) From() BytesReader {
  104. return ft.from
  105. }
  106. func (ft fromToBuffer) To() io.Writer {
  107. return ft.to
  108. }

输出结果:

1. what to publish:  template executed transferred result
2. where to publish:  /tmp/hugo2834984546
Program exited.

Publish线上可直接运行版本

以上就是go开源Hugo站点构建三步曲之集结渲染的详细内容,更多关于go Hugo站点构建集结渲染的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号