Hugoとは
Hugo とは静的ページを生成するGo製のフレームワークである。
Lunrとは
Lunr とはJavaScript製のテキスト検索エンジンである。日本語検索に対応している。1
検索機能設置方法
以降の内容は https://gist.github.com/sebz/efddfc8fdcb6b480f567 及び https://gist.github.com/eddiewebb/735feb48f50f0ddd65ae5606a1cb41ae を参考にしている。
ファイル構成
導入後のファイル構成は以下の通り。検索機能に関わる部分のみ記載している。
BLOG_DIR
├── layouts
│ ├── _default
│ │ └── index.json
│ └── index.html
└── static
└── js
├── lunr.jp.js
├── lunr.js
├── lunr.multi.js
├── lunr.stemmer.support.js
├── search.js
└── tinyseg.js
1. 検索用データ設置
Lunrが検索対象として読み込むデータを設置する。ここでは全記事のタイトル, タグ, カテゴリ, 本文, hrefをJSON形式で書き出す。
- layouts/_default/index.json
{{- $.Scratch.Add "index" slice -}}
{{- range .Site.RegularPages -}}
{{- $.Scratch.Add "index" (dict "title" (or .Title (.Date.Format "2006/01/02")) "tags" .Params.tags "categories" .Params.categories "contents" .Plain "href" .URL ) -}}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}
3行目の"title" (or .Title (.Date.Format "2006/01/02"))
は弊サイト独自の仕様であり、通常は"title" .Title
でよい。
hugo server -Dw
でサーバを立ち上げ http://localhost:1313/index.json に目的のファイルが設置されていることを確認する。
2. Lunrのソースコード設置
static/js/
以下にLunrに関わるファイル5つを設置する。
lunr.jsはolivernn/lunr.js に、lunr.jp.js, lunr.multi.js, lunr.stemmer.support.js, tinyseg.jsはMihaiValentin/lunr-languages にある。
手動設置以外の方法としてnpm install lunr
及びnpm install lunr-languages
がある。また、lunr.jsはCDNのデータも利用可能。2
この記事では手動設置を前提とする。
3. 検索用スクリプト設置
2で設置したスクリプトを利用するためstatic/js/search.js
を設置する。
- static/js/search.js
var lunrIndex, $results, pagesIndex;
function initLunr() {
$.getJSON("index.json").done(function(index) {
pagesIndex = index;
lunrIndex = lunr(function() {
var lunrConfig = this;
lunrConfig.use(lunr.multiLanguage('en', 'jp'));
lunrConfig.ref("href");
lunrConfig.field("title", { boost: 10 });
lunrConfig.field("contents");
pagesIndex.forEach(function(page) {
lunrConfig.add(page);
});
});
})
.fail(function(jqxhr, textStatus, error) {
var err = textStatus + ", " + error;
console.error("Error getting Hugo index flie:", err);
});
}
function search(){
$results = $("#results");
$results.empty();
var query = document.getElementById('search-query').value;
if (query.length < 2) {
return;
}
renderResults(results(query));
}
function results(query) {
return lunrIndex.search(`*${query}*`).map(function(result) {
return pagesIndex.filter(function(page) {
return page.href === result.ref;
})[0];
});
}
function renderResults(results) {
if (!results.length) {
$results.append('<p>No matches found</p>');
return;
}
results.forEach(function(result) {
var $result = $("<li>");
$result.append($("<a>", {
href: result.href,
text: result.title
}));
if (result.contents.length <= 100) {
$result.append($("<p>", {
text: result.contents
}));
} else {
$result.append($("<p>", {
text: result.contents.slice(0, 100) + " ..."
}));
}
$results.append($result);
});
}
initLunr();
initLunr()で検索対象の言語, index, 検索文字列を読み込んでいる。言語はjpだけでは数列や英字文字列にマッチしないためenも指定する。
search()で検索クエリを読み込む。クエリの長さが2以上であれば検索を開始する。
results(query)で検索クエリを元にLunrを用いて検索し、マッチした記事の情報を返している。Lunrは検索結果としてindexのみを返すため、この結果を元に1の検索用データから記事内容を取得している。
renderResults(results)でresults(query)の結果を元に後述する検索用ページに検索結果を追記している。
4. 検索用ページ生成
この記事ではindex.htmlに設置しているが、どこでもよい。
- layouts/index.html
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="js/lunr.js"></script>
<script src="js/lunr.stemmer.support.js"></script>
<script src="js/tinyseg.js"></script>
<script src="js/lunr.jp.js"></script>
<script src="js/lunr.multi.js"></script>
<script src="{{ "js/search.js" | absURL }}"></script>
<form class="full-text-search-form" action="{{ .URL }}" onkeyup="search()">
<input id="search-query" placeholder="Full Text Search" name="query" autocomplete="off" search autofocus/>
</form>
<ul id="results">
最初の7行でjQueryと、2と3で設置したjsを読み込んでいる。jQueryはsearch.jsで使用している。
formは検索ワード入力用テキストボックスであり、キー入力の度にsearch.jsのsearch()が実行される。
ulは検索結果を表示する箇所であり、search.jsにより検索結果に応じて操作される。
最後に検索用ページ http://localhost:1313/index.html で検索機能が動いていることを確認する。