github-stylingというテーマに変更した

このブログはgithub-styleからCloneされたgithub-style-plusforkしてカスタマイズしたgithub-stylingというテーマを使用している。

名前の通りデザインをGithubに寄せたブログテーマで絶大な人気はなさそうだし、Githubのデザイン変更に追従するのは辛そうだし、オプションも少なそうだっだけど、個人でカスタマイズしてくのであれば問題なそうだったので採用してみた。

結果、個人的に使い慣れた、見慣れたデザインになったのでまあ良かったと思っている。

テーマ変更時にディレクトの変更を余儀なくされ、netlifyのリダイレクト設定が必要になったりと面倒ではあったが、躓くことなくテーマ変更できた。

とはいえ、それなりに時間を食うので、このテーマとは長く付き合いたいと思っている。

ただ前述したようにいくらかカスタマイズしなくちゃいけない部分があり、中でも致命的だったのがローカル検索の遅さだった。

ローカル検索に1分かかる

個人的にGithubの最大使いやすさは検索性能の高さだと思っている。

コード検索とか本当に重宝している。

それに比べてこのテーマの検索は遅すぎた。

ブログは過去のナレッジが詰まっているので頻繁に検索したい、けどそのたびに1分かかるのは辛い。

ということで改善することにした。

実際の改善前が下記動画で、実際には1分20秒かかっている。

改善前のローカル検索

ネットワークタブをみると全ファイルを 見に行っていることがわかった。

github-style-plusのローカル検索を解読する

実際に全ファイルを見に行っているのが下記のソースになる

fetch(
  `${host.indexOf("localhost") > -1 ? "http://" : "https://"}${host}/index.xml`,
)
  .then((resp) => resp.text())
  .then(async (res) => {
    parser = new DOMParser();
    xmlDoc = parser.parseFromString(res, "text/xml");
    const linkResult = xmlDoc.getElementsByTagName("link");
    const titleResult = xmlDoc.getElementsByTagName("title");
    const arr = [];

    const matched = [];
    await (async function searchLink() {
      for (let i = 0; i < linkResult.length; i++) {
        await fetch(linkResult[i].textContent).then((resp) =>
          resp.text().then((res) => {
            const pureText = stripHtml(res);
            if (pureText.indexOf(keyword) >= 0) {
              matched.push(i);
            }
          }),
        );
      }
    })();
  });

まず、自身のindex.xmlを取得し全URLの件数を取得する。

その件数でforを回し、全URLのコンテンツを取り出しキーワードが含まれているかをチェックしている。

で含まれていたらmatchedに格納し、matchedを使用しDomを生成している。

いま200記事あるので、それを全部見に行くとなると、それはそれなりに時間がかかるに決まっている。

改善する

改善後は下記のようになった。

改善後のローカル検索

検索の高速化を調べているとFuse.jsがヒットした。

Powerful, lightweight fuzzy-search library, with zero dependencies.

下記のように実装しなおした。

<script src="https://cdn.jsdelivr.net/npm/fuse.js@7.1.0"></script>

まずはFuse.jsを読み込む。

const fetchIndex = async () => {
  const response = await fetch(
    `${host.indexOf("localhost") > -1 ? "http://" : "https://"}${host}/index.xml`,
  );
  const xmlText = await response.text();
  const parser = new DOMParser();
  const xmlDoc = parser.parseFromString(xmlText, "text/xml");
  const items = Array.from(xmlDoc.getElementsByTagName("item"));

  searchIndex = items.map((item) => ({
    title: item.getElementsByTagName("title")[0].textContent,
    link: item.getElementsByTagName("link")[0].textContent,
    content: item.getElementsByTagName("description")[0]?.textContent || "",
  }));

  fuse = new Fuse(searchIndex, {
    keys: ["title", "content"],
    threshold: 0.3,
  });
};

次にfuseを定義する。

これで特定のキーワードをtitlecontentから検索できるようになる。やっていることは改善前と変わらずに、index.xmlからtitlelinkdescriptionを取得している。

thresholdはあいまい検索の閾値らしい。

オプションは他にもあった。

で、下記コードのように検索ワードをsearchに渡せば、

const results = fuse.search(query).map((res) => res.item);

fetchIndexで返した

  • title
  • link
  • content

が取得できるので、それでDomを生成すればいい。

この方法なら初回にindex.xmlを取得するだけで検索が実装できる。

これでブログを頻繁に検索できるようになり、自身のナレッジに手軽にアクセスできるようになった。

全コードはこちら


そもそも改善前もdescriptionを使えばfetchする必要がなかったかもしれない。

あるいはもしかするとこの方法だとtagが取得できないので、あえて全URLのHTMLを見に行っていたのかもしれない。