Vue.js + lunr.js を使い hugo サイトに全文検索を追加する
Created: 2020-03-06
成果物
生の html を書きたい
hugo では Markdown を使う。一般的な Markdown では html を混在して書けるが、 hugo では以下のようになる。
index.md
<a href="#"><img src="/img.jpg"></a>
↓ index.html
<p><a href="#"><img src="/img.jpg"></a></p>
このように p タグで囲われた状態で出力されるため、たとえば Amazon のアフィリエイトを貼り付けると無駄な改行が入ってしまう。このままでは JavaScript でページを作る上で多少の不都合がある(表現が自由ではない)ので、生の html を出力する shortcode を作成する。
/layouts/shortcodes/raw.html
{{ .Inner }}
このように使える。
参照: Simple Shortcode to Insert Raw HTML in Hugo · Ana Ulin
index.json を出力する
今回は How to make a client-side search engine with Vue.js and Lunr.js - Fabio Franchino の方法をパク踏襲して、まずは動かしてみる。リンク先のコードにおいて axios
で読み込む json に相当する情報を hugo で出力する必要があるので、それを index.json とする。 /layouts/_default/index.json に layout を、config.toml に設定を追加すれば index.xml (RSS)のように出力することが出来る。
/layouts/_default/index.json
{{ $items := slice }}
{{ $counter := 0 }}
{{ range .Site.RegularPages }}
{{ if in .Permalink "/post/" }}
{{ $counter = add $counter 1 }}
{{ $date := .Date.Format "2006-01-02" }}
{{ $items = $items | append (dict "id" $counter "title" .Title "body" .Plain "url" .Permalink "date" $date) }}
{{ end }}
{{ end }}
{{ $items | jsonify }}
単純に slice として定義した $items
にページの内容を dict として追加していっている。id
は今回のソースでは使用するというだけで、別に url
を同じく用いてもよいだろうと思う(検証はしていないが)。何も考えず全ページを追加すると /profile/ や /search/ 自身まで対象となるので、リンクに /post/
が含まれるページだけという条件をつけている(if in .Permalink "/post/"
)。最終行ではループで組み立てた $items を jsonfy
で json 化して出力している。
同じようなことを解説しているページで .Scratch を使っているパターンがあるが、このように単一ページで用いるだけの変数には必要のないと思う。ドキュメントには allow for writable page- or shortcode-scoped variables.
とある。スコープをまたぐときに使うように読めるが、今回は関係ない。物事はシンプルに記述したほうが良いだろう。
また config.toml にはこのように。
[outputs]
home = ["html", "json", "rss"]
検索ページを設置する
search.md
lunr.js はここから、lunr.stemmer.support.js tinyseg.js lunr.ja.js はここからダウンロードする。
search.js
検索対象が本文とタイトルなので、そのように指定している。
lunr-languages の README では多言語に対応する方法として
this.use(lunr.multiLanguage('en', 'ru'));
と書かれているのだが、同じように日本語を、例えば 'en', 'ja'
のように指定しても希望通り動かない(英語だけが有効になる)。これは既知の問題のようだ。
そのため今回は this.use(lunr.ja)
と日本語だけを検索するよう書いたのだが、もちろん多言語対応していないので次は英単語の resource などを検索できない問題が生じる。そのため姑息的対処として this.resuls = this.searchIndex.search(
*${this.search}*)
でワイルドカードで検索できるようにした。これで resource なども検索できるのだが、ならばいっそ lunr を使わず Vue だけで検索を作っても良いのではないかという気にもなる…… lunr-languages に貢献するのがいちばん良いのかな。
以上で検索ページは実現できる。
今後
- 全記事を表示してから絞り込み検索するのはページ増えたときに……
- ページが増えたときに考えましょう
- 全記事を json で吐き出すのは……
- ページが増えたときに考えましょう