Hugo + Lunrによる日本語全文検索

Nov 4, 2018    #hugo   #javascript  

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形式で書き出す。

{{- $.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を設置する。

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に設置しているが、どこでもよい。

<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 で検索機能が動いていることを確認する。


  1. https://lunrjs.com/guides/language_support.html  ↩︎

  2. https://lunrjs.com/guides/getting_started.html  ↩︎