先日作ったpulpというHUGOのテーマに全文検索機能を付けてみた。タグやカテゴリで分類するのが個人的に面倒臭いと思ってしまうので、何もしなくても本文が検索できればいいなと思っていた。しっかり作るならElasticSearchやAlgoliaを使うんだろうけど、そうするとThemeとして配布しづらくなるし、個人用途としてはそこまでの機能はいらないのでフロントエンドだけで完結する方法を模索した。
デモ
記事をJSONで取得できるようにする
まずは、HugoでJSONを出力 | Celeumu を参考にしつつブログの記事をJSONで取得できるようにした。config.tomlに以下を追記すると、拡張子が.jsonのテンプレートをHUGOがテンプレートとして見てくれるようになった。
[outputs]
section = ["JSON", "HTML"]
その上でlayouts/_default/list.json
を作成すると、http://hostname/section/index.json といったURLでいとも簡単にセクションのデータをJSONで引っこ抜くことができるようになる。このブログの場合だと、https://koirand.github.io/blog/index.json からデータが取得できる。
[{{ range $index, $page := .Pages }}{{ if ne $index 0 }},{{ end }}
{
"ref": "{{ $page.Permalink }}",
"title": {{ $page.Title | jsonify }},
"section": "{{ $page.Section }}",
"date" : {{ $page.Date.Format "2006.01.02" | jsonify }},
"body": {{ $page.Plain | jsonify }}
}
{{ end }}]
検索処理の実装
検索処理は hugo + gruntjs + lunrjs = search にlunr.jsを使ったサンプルコードを掲載してくれている人がいて、ほぼこのままで動いた。このサンプルコードをベースに以下のカスタマイズを加えた。具体的な変更内容は Add full-text search function by koirand · Pull Request #3 · koirand/pulp を参照。
- 検索結果に本文を表示
- 日本語での検索対応
- キーワードのハイライト
検索結果に本文を表示
本文でキーワードにヒットした箇所の前後50文字を検索結果に表示した。複数のキーワードがスペース区切りで入力された場合は先頭のキーワードだけを使っている。また、タイトルのみにヒットした場合は、本文の先頭100文字を表示している。(恐らくもっと良いやり方がある)
日本語での検索対応
標準のlunr.jsだとスペースを単語の区切りとみなすのか日本語を検索できないので、MihaiValentin/lunr-languages: A collection of languages stemmers and stopwords for Lunr Javascript library を使う必要がある。READMEには書いてないが、日本語に対応させる場合は同封されているtinyseg.jsも読み込む必要があった。
キーワードのハイライト
キーワードのハイライトは mark.js – JavaScript keyword highlight を使うと簡単だった。
感想
たまに一部の単語が検索にヒットしないものの、概ねちゃんと検索できてるしサクサクで良い感じ。ページの数が増えてくるとパフォーマンス落ちたりするのかなと、試しに Skycoin社のブログデータ のリポジトリをクローンしてこのテーマを適用してみたが普通にサクサク動いたので個人サイトであれば大丈夫だろう。