关于hugo:静态博客如何高性能插入评论

45次阅读

共计 4157 个字符,预计需要花费 11 分钟才能阅读完成。

???? 前言

咱们晓得,动态博客因为不带有动静性能,所以针对评论这种动静需要比拟公众的做法就是应用第三方评论零碎。第三方评论的实质其实就是应用 JS 去调取第三方服务接口获取评论后动静渲染到页面中。尽管它很好的解决了这个问题,然而因为须要申请接口,在体验上远比动静博客的直出成果要差很多。所以当我把博客从动静博客 Typecho 迁徙到动态博客 Hugo 上来时,就始终在思考这个问题。直到我看到了 Hugo 的 getJSON 办法,发现原来动态博客也是可能像动静博客一样直出评论的。

大部分的动态博客的原理是解析存储内容的文件夹,应用一些模板语言遍历数据生成一堆 HTML 文件。而 Hugo 除了解析 Markdown 内容之外,还反对额定的数据获取办法 getJSON。因为有了 getJSON 办法的呈现,咱们能够实现在博客编译构建过程中动静的去获取评论接口数据,将其渲染到页面中,实现评论数据的直出成果。对于 getJSON 的更多介绍,能够查看 Hugo 文档数据模板一节。

???? 计划

高性能计划基本思路是在须要评论数据的中央通过 getJSON 办法调用接口获取评论数据并进行模板渲染。当评论更新的时候,咱们须要触发从新构建。实现这个计划依赖三个要害因素:

  1. 构建过程反对调取接口获取数据
  2. 评论服务提供 HTTP 接口返回数据
  3. 博客部署服务反对钩子触发从新构建

我的博客应用的是 Hugo 动态博客零碎,如上文所说通过 getJSON 即可解决第一个问题。而我的评论服务应用的是自研的 Waline 评论零碎,它提供了评论数、评论列表、最近评论等根底接口满足咱们的数据获取需要。并且 Waline 提供了丰盛的钩子性能,反对在评论公布的时候触发自第一办法。我的博客部署在 Vercel 上,它提供了 Deploy Hooks 性能,通过 URL 即可触发从新构建。也就是说我只有在 Waline 评论公布的钩子中调用 Vercel 的钩子 URL 触发从新构建即可解决第三个问题。

???? 实现

我的博客上有三处中央和评论无关,别离是首页侧边栏的最近评论,文章题目下方的评论数,以及文章详情页底部的评论列表展现。

???? 最近评论

Waline 最近评论接口:文档

{{$walineURL := .Site.Params.comment.waline.serverURL}}
<h2 class="widget-title"> 最近回复 </h2>
<ul class="widget-list recentcomments">
  {{$resp := getJSON $walineURL "/comment?type=recent"}}
  {{range $resp}}
  <li class="recentcomments">
    <a href="{{.Site.BaseURL}}{{.url}}">{{.nick}}</a>:{{.comment | safeHTML | truncate 22}}
  </li>
  {{end}}
</ul>

???? 文章评论数

Waline 获取文章对应的评论数接口:文档

{{$walineURL := .Site.Params.comment.waline.serverURL}}
{{$count := getJSON $walineURL "/comment?type=count&url=/" .Slug ".html"}}
<a href="{{.Permalink}}#comments" title="{{.Title}}">
  <i class="fas fa-comment mr-1"></i>
  <span>{{- if gt $resp 0}}{{$resp}} 条评论 {{else}} 暂无评论{{end -}}</span>
</a>

???? 评论列表

评论列表因为有分页的存在,不像最近评论和评论数一样简略的调用接口即可。先获取评论数,发现有评论时先获取第一页的评论,次要是用来获取总共有多少页评论。之后再从第二页开始循环获取评论数据。最终将获取到的数据全副存到 {{$scratch.Get "comments"}} 数组中,应用模板语法渲染该数组数据即可。

{{$baseUrl := .Site.Params.comment.waline.serverURL}}
{{$slug := .Slug}}
{{$count := getJSON $baseUrl "/comment?type=count&url=/" $slug ".html"}}
{{$scratch := newScratch}}
{{$scratch.Add "comments" slice}}

{{if gt $count 0}}
  {{$comments := getJSON $baseUrl "/comment?path=/" $slug ".html&page=1&pageSize=100"}}
  {{range $cmt := $comments.data}}
    {{$scratch.Add "comments" $cmt}}
  {{end}}

  {{$totalPages := $comments.totalPages}}
  {{if gt $totalPages 1}}
    {{range $page := seq 2 $totalPages}}
      {{$comments := getJSON $baseUrl "/comment?path=/" $slug ".html&pageSize=100&page=" $page}}
      {{range $cmt := $comments.data}}
        {{$scratch.Add "comments" $cmt}}
      {{end}}
    {{end}}
  {{end}}
{{end}}

<div class="vcards">
  {{range $cmt := $scratch.Get "comments"}}
  <div class="vcard" id={{$cmt.objectId}}>
    <img class="vimg" src="https://gravatar.loli.net/avatar/{{$cmt.mail}}?d=mp">
    <div class="vh">
      <div class="vhead">
        <a class="vnick" rel="nofollow" href="{{$cmt.link}}" target="_blank">{{$cmt.nick}}</a>
        <span class="vsys">{{$cmt.browser}}</span>
        <span class="vsys">{{$cmt.os}}</span>
      </div>
      <div class="vmeta">
        <span class="vtime">{{dateFormat $cmt.insertedAt "2006-01-02 03:04:05"}}</span>
        <span class="vat"> 回复 </span>
      </div>
      <div class="vcontent" data-expand="查看更多...">
        {{$cmt.comment | safeHTML}}
      </div>
      <div class="vreply-wrapper"></div>
      <div class="vquote">
        {{range $cmt := $cmt.children}}
        <div class="vh" id="{{$cmt.objectId}}">
          <div class="vhead">
            <a class="vnick" rel="nofollow" href="{{$cmt.link}}" target="_blank">{{$cmt.nick}}</a>
            <span class="vsys">{{$cmt.browser}}</span>
            <span class="vsys">{{$cmt.os}}</span>
          </div>
          <div class="vmeta">
            <span class="vtime">{{dateFormat $cmt.insertedAt "2006-01-02 03:04:05"}}</span>
            <span class="vat"> 回复 </span>
          </div>
          <div class="vcontent" data-expand="查看更多...">
            {{$cmt.comment | safeHTML}}
          </div>
          <div class="vreply-wrapper"></div>
        </div>
        {{end}}
      </div>
    </div>
  </div>
  {{end}}
</div>

???? 构建触发

Waline 在评论公布、更新和删除阶段都反对自定义钩子,在钩子中触发 Vercel 的构建钩子即可实现公布评论从新构建的流程。

依照如下内容批改服务端部署的 index.js 文件,查看文档理解全副的 Waline 钩子。

const Waline = require('@waline/vercel');
const https = require('https');
const buildTrigger = _ => https.get('https://api.vercel.com/v1/integrations/deploy/xxxxx');

module.exports = Waline({async postSave(comment) {if(comment.status !== 'approved') {return;}
    buildTrigger();},
  async postUpdate() {buildTrigger();
  },
  async postDelete() {buildTrigger();
  }
});

???? 后记

通过以上操作,就能在不损失用户体验的状况下实现评论数据的动静反对了。有些人可能会放心是否会在构建阶段造成超多的接口申请。这里大可不必放心,Hugo 本人会在构建的时候做接口的缓存,同 URL 的接口调用会走缓存数据而不会从新调用。

除了用户体验之外,因为只会在构建的时候触发数据的获取,针对有调用次数配额的第三方评论服务也能节俭额度。当然,实践上构建次数是远小于拜访次数的,所以额度节俭的论断是能成立的。如果说你的构建次数要比拜访次数还要大的话,那这种办法就无奈节俭额度了。

当然这种形式也会有带来些问题,次要是评论的更新没那么快。好在 Hugo 的构建速度十分快,一两分钟的工夫也能承受。而针对用户评论的公布,则能够通过评论公布后先假插入缓解该问题。

正文完
 0