preview image
Posted by December 24 2023 / blog
在Hugo中如何直接输出ndjson格式文件并上传到Algolia
Updated on December 24 2023
1212 words
3 minutes read

... visits

在开发 Hugo 主题的搜索功能,并处理上传到 Algolia 的数据时,我发现网上教程通常都是教如何生成 JSON 文件的。然后手动或者使用 hugo-algolia 插件上传到 Algolia。手动上传过程繁琐,而尽管 hugo-algolia 插件目前仍可使用,但由于长时间未进行更新,我对它的可靠性产生了质疑,因此不太推荐使用。

我个人更倾向于使用 Algolia 官方的Algolia CLI工具。然而,这个工具要求上传的文件是ndjson格式,而不是 JSON 格式。虽然网上存在一些将 JSON 转换为 ndjson 的库(当然,也可以自己实现),但这些方法会增加复杂性且不够优雅。

Hugo 本身是否支持直接输出 ndjson 格式的内容呢?答案是肯定的。

Hugo 已经提供了一种解决方案,具体可以参考 Support for rendering ndJSON files #10926 ,从该 issues 的讨论中可以发现仓库 hugo-testing ,通过查看仓库的代码,我们可以了解到如何在 Hugo 中将内容输出为 ndjson 格式。

受到该仓库的启发,我们可以将我们需要上传给 Algolia 的数据,直接转换为 ndjson 文件中。

下面是具体的步骤:

前提

  • 安装了 Algolia Cli
  • 已经创建了 Algolia 索引

步骤

1. 自定义 mediaTypes

在 hugo.toml 中自定义 mediaTypes

[mediaTypes.'application/x-ndjson']
  suffixes = ['ndjson']

2. 自定义 outputFormats

在 hugo.toml 中自定义 outputFormats

[outputs]
  home = ["HTML", "Algolia"]

[outputFormats]
  [outputFormats.Algolia]
    baseName = 'algolia'
    isPlainText = true
    notAlternative = true
    mediaType = 'application/x-ndjson'

3. 新建 data-to-ndjson.html

新建 layout/partials/data-to-ndjson.html 文件,并添加如下代码:

{{/* Generates a valid Algolia search index */}}
{{- $hits := slice -}}
{{- $section := $.Site.GetPage "section" .Section }}
{{- $validVars := $.Param "algolia.vars" | default slice -}}
{{- $validParams := $.Param "algolia.params" | default slice -}}
{{- $pageType := $.Param "algolia.type" | default slice -}}
{{/* Include type of content? */}}
{{- range $i, $hit := where (where .Site.Pages "Type" "in" $pageType) "IsPage" true -}}
  {{- $dot := . -}}
  {{- if or (and ($hit.IsDescendant $section) (and (not $hit.Draft) (not $hit.Params.private))) $section.IsHome -}}
    {{/* We need objectID as something unique for Algolia */}}
    {{- .Scratch.SetInMap $hit.File.Path "objectID" $hit.File.UniqueID -}}
    {{/* Keep the page attributes you need in an iterable object */}}
    {{- .Scratch.SetInMap "temp" "content" $hit.Plain -}}
    {{- .Scratch.SetInMap "temp" "date" $hit.Date.UTC.Unix -}}
    {{- .Scratch.SetInMap "temp" "description" $hit.Description -}}
    {{- .Scratch.SetInMap "temp" "image" $hit.Params.Image -}}
    {{- .Scratch.SetInMap "temp" "dir" $hit.File.Dir -}}
    {{- .Scratch.SetInMap "temp" "path" "temp" -}}
    {{- .Scratch.SetInMap "temp" "expirydate" $hit.ExpiryDate.UTC.Unix -}}
    {{- .Scratch.SetInMap "temp" "path" "temp" -}}
    {{- .Scratch.SetInMap "temp" "fuzzywordcount" $hit.FuzzyWordCount -}}
    {{- .Scratch.SetInMap "temp" "keywords" $hit.Keywords -}}
    {{- .Scratch.SetInMap "temp" "kind" $hit.Kind -}}
    {{- .Scratch.SetInMap "temp" "lang" $hit.Lang -}}
    {{- .Scratch.SetInMap "temp" "lastmod" $hit.Lastmod.UTC.Unix -}}
    {{- .Scratch.SetInMap "temp" "permalink" $hit.Permalink -}}
    {{- .Scratch.SetInMap "temp" "publishdate" $hit.PublishDate -}}
    {{- .Scratch.SetInMap "temp" "readingtime" $hit.ReadingTime -}}
    {{- .Scratch.SetInMap "temp" "relpermalink" $hit.RelPermalink -}}
    {{- .Scratch.SetInMap "temp" "summary" $hit.Summary -}}
    {{- .Scratch.SetInMap "temp" "title" $hit.Title -}}
    {{- .Scratch.SetInMap "temp" "type" $hit.Type -}}
    {{- .Scratch.SetInMap "temp" "url" $hit.Permalink -}}
    {{- .Scratch.SetInMap "temp" "weight" $hit.Weight -}}
    {{- .Scratch.SetInMap "temp" "wordcount" $hit.WordCount -}}
    {{- .Scratch.SetInMap "temp" "section" $hit.Section -}}
    {{/* Include valid page vars */}}
    {{- range $key, $param := (.Scratch.Get "temp") -}}
      {{- if in $validVars $key -}}
        {{- $dot.Scratch.SetInMap $hit.File.Path $key $param -}}
      {{- end -}}
    {{- end -}}
    {{/* Include valid page params */}}
    {{- range $key, $param := $hit.Params -}}
      {{- if in $validParams $key -}}
        {{- $dot.Scratch.SetInMap $hit.File.Path $key $param -}}
      {{- end -}}
    {{- end -}}
    {{- $.Scratch.SetInMap "hits" $hit.File.Path (.Scratch.Get $hit.File.Path) -}}
  {{- end -}}
{{- end -}}

{{ $ndJSON := "" }}
{{ range $k, $v := $.Scratch.GetSortedMapValues "hits" }}
  {{ if $k }}
    {{ $ndJSON = print $ndJSON "\n" }}
  {{ end }}
  {{ $ndJSON = print $ndJSON (jsonify $v) }}
{{ end }}
{{ return $ndJSON }}

这段代码是我的 Hugo 主题 Seven 中用于处理并生成需要上传至 Algolia 的数据的逻辑。

其中生成 ndjson 格式的关键在这部分:

{{ $ndJSON := "" }}
{{ range $k, $v := $.Scratch.GetSortedMapValues "hits" }}
  {{ if $k }}
    {{ $ndJSON = print $ndJSON "\n" }}
  {{ end }}
  {{ $ndJSON = print $ndJSON (jsonify $v) }}
{{ end }}
{{ return $ndJSON }}

对于每个键值对(即 Algolia 索引中的每一个记录),如果键存在,则在$ndJSON末尾添加一个换行符。然后,使用jsonify函数将值转化为 JSON 格式的字符串,并将其与$ndJSON字符串拼接起来。

4. 新建 list.algolia.ndjson

新建 layout/list.algolia.ndjson 文件,并添加如下代码:

{{ partial "data-to-ndjson.html" . }}

5. 将内容输出为 ndjson 格式的文件

运行hugo命令,然后会在 public 文件夹中生成一个名为algolia.ndjson的文件。

6. 使用 Algolia Cli 将 ndjson 文件上传到 Algolia

algolia objects import your_index_name -F ./public/algolia.ndjson -p your_profile_name

参考资料

  1. Hugo 集成 Algolia 搜索踩坑 – 2023 版
  2. Support for rendering ndJSON files #10926
  3. Hugo GitHub Issue #10926
TAGS
On this page