brief: | i18n.site はサーバーレスの全文検索をサポートするようになりました。
この記事では、IndexedDB によって構築された転置インデックス、接頭辞検索、単語分割の最適化、多言語サポートなど、純粋なフロントエンド全文検索テクノロジの実装について紹介します。
既存のソリューションと比較して、i18n.site の純粋なフロントエンド全文検索はサイズが小さく高速であり、ドキュメントやブログなどの中小規模の Web サイトに適しており、オフラインで利用できます。
数週間の開発を経て、 i18n.site (純粋に静的なmarkdown multilingualtranslation &ウェブサイト構築ツール) は、純粋なフロントエンド全文検索をサポートするようになりました。
この記事では、純粋i18n.site
フロントエンド全文検索の技術的な実装について説明します。検索効果を体験してi18n.site 。
オープン:のコード検索カーネル/インタラクティブ インターフェイス
ドキュメントや個人のブログなど、中小規模の純粋に静的な Web サイトの場合、独自に構築した全文検索バックエンドを構築するのは重すぎるため、サービス不要の全文検索がより一般的な選択肢となります。
サーバーレス全文検索ソリューションは、次の 2 つの大きなカテゴリに分類されます。
まず、同様algolia.comサードパーティ検索サービス プロバイダーが全文検索用のフロントエンド コンポーネントを提供しています。
このようなサービスは検索ボリュームに基づいて料金を支払う必要があり、Web サイトのコンプライアンスなどの問題により、中国本土のユーザーは利用できないことがよくあります。
オフラインでもイントラネットでも使用できず、大きな制限があります。 この記事ではあまり議論しません。
2 つ目は、純粋なフロントエンドの全文検索です。
現在、一般的な純粋なフロントエンドの全文検索には、 lunrjsとElasticLunr.js ( lunrjs
二次開発に基づく) が含まれます。
lunrjs
インデックスを構築するには 2 つの方法があり、どちらにも独自の問題があります。
事前に構築されたインデックスファイル
索引にはすべての文書の単語が含まれているため、サイズが大きくなります。 ドキュメントが追加または変更されるたびに、新しいインデックス ファイルをロードする必要があります。 ユーザーの待ち時間が長くなり、大量の帯域幅が消費されます。
ドキュメントをロードし、その場でインデックスを構築します
インデックスの構築は、大量の計算を必要とするタスクであり、アクセスするたびにインデックスを再構築すると、明らかな遅延が発生し、ユーザー エクスペリエンスが低下します。
lunrjs
に加えて、 :などの他の全文検索ソリューションもいくつかあります。
fusejs 、検索する文字列間の類似性を計算します。
このソリューションのパフォーマンスは非常に低く、全文検索には使用できません (「 Fuse.js長いクエリには10秒以上かかります。最適化するには?」を参照してください)。
TinySearchは、ブルーム フィルターを使用して検索しますが、接頭辞検索 (たとえば、 goo
を入力し、 good
、 google
検索) には使用できず、同様の自動補完効果は得られません。
既存のソリューションには欠点があるため、 i18n.site
次の特徴を持つ新しい純粋なフロントエンド全文検索ソリューションを開発しました:
6.9KB
gzip
(比較のために、 lunrjs
のサイズは25KB
です)。indexedb
に基づいて転置インデックスを構築します。これはメモリの使用量が少なく、高速です。以下では、 i18n.site
的な実装の詳細について詳しく紹介します。
単語のセグメンテーションでは、ブラウザーのネイティブの単語セグメンテーションIntl.Segmenter
が使用され、すべての主流ブラウザーがこのインターフェイスをサポートしています。
単語分割coffeescript
コードは次のとおりです。
SEG = new Intl.Segmenter 0, granularity: "word"
seg = (txt) =>
r = []
for {segment} from SEG.segment(txt)
for i from segment.split('.')
i = i.trim()
if i and !'| `'.includes(i) and !/\p{P}/u.test(i)
r.push i
r
export default seg
export segqy = (q) =>
seg q.toLocaleLowerCase()
で:
/\p{P}/
、句読点に一致する正規表現です。 ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _
{ | } 〜。 。</p><ul><li>
split('.')は、
Firefoxブラウザの単語分割では
. `分割しないためです。
IndexedDB
で 5 つのオブジェクト ストレージ テーブルが作成されました:
word
: id -doc
: id -ドキュメントurl -ドキュメントのバージョン番号docWord
:ドキュメントid -ワードidの配列prefix
:プレフィックス-ワードidの配列rindex
: Word id -ドキュメントid :行番号の配列document url
とバージョン番号ver
の配列を渡し、テーブルdoc
にドキュメントが存在するかどうかを検索します。存在しない場合は、転置インデックスを作成します。同時に、渡されなかったドキュメントの逆索引を削除します。
このようにして、インクリメンタルなインデックス作成が実現され、計算量が削減されます。
フロントエンド対話では、初回ロード時の遅延を避けるために、インデックスのロード進行状況バーを表示できます。「単一のprogress + Pure css実装に基づくアニメーション付きの進行状況バー」 English / Chinese を参照してください。
このプロジェクトは、 IndexedDBの非同期カプセル化に基づいてidb 。
IndexedDB の読み取りと書き込みは非同期です。インデックスを作成するときは、ドキュメントが同時にロードされてインデックスが作成されます。
競合書き込みによる部分的なデータ損失を回避するには、以下のcoffeescript
コードを参照し、読み取りと書き込みの間にing
キャッシュを追加して競合書き込みを遮断します。
pusher = =>
ing = new Map()
(table, id, val)=>
id_set = ing.get(id)
if id_set
id_set.add val
return
id_set = new Set([val])
ing.set id, id_set
pre = await table.get(id)
li = pre?.li or []
loop
to_add = [...id_set]
li.push(...to_add)
await table.put({id,li})
for i from to_add
id_set.delete i
if not id_set.size
ing.delete id
break
return
rindexPush = pusher()
prefixPush = pusher()
検索では、まずユーザーが入力したキーワードがセグメント化されます。
単語分割後の単語がN
であると仮定します。結果を返す場合、最初にすべてのキーワードを含む結果が返され、次にN-1
、 N-2
、...、 1
キーワードを含む結果が返されます。
最初に表示される検索結果によってクエリの正確さが保証され、その後読み込まれる結果 ([さらに読み込む] ボタンをクリック) によって再現率が保証されます。
応答速度を向上させるために、検索ではyield
ジェネレーターを使用してオンデマンド読み込みを実装し、結果がクエリされるたびlimit
結果を返します。
yield
後に再度検索するたびに、 IndexedDB
のクエリ トランザクションを再度開く必要があることに注意してください。
ユーザーが入力中に検索結果を表示するため、たとえばwor
を入力すると、 words
やwork
など先頭にwor
が付く単語が表示されます。
検索カーネルは、単語の分割後の最後の単語に対してprefix
テーブルを使用して、その接頭辞が付いたすべての単語を検索し、順番に検索します。
手ぶれ補正機能debounce
は、検索をトリガーするユーザー入力の頻度を減らし、計算量を減らすために、フロントエンド インタラクション (次のように実装) でも使用されます。
export default (wait, func) => {
var timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(func.bind(this, ...args), wait);
};
}
インデックス テーブルには元のテキストは保存されず、単語のみが保存されるため、ストレージの量が削減されます。
検索結果を強調表示するには、元のテキストを再ロードする必要があり、 service worker
と一致すると、ネットワーク要求が繰り返されることを回避できます。
同時に、 service worker
すべての記事をキャッシュするため、ユーザーが検索を実行すると、検索を含む Web サイト全体がオフラインで利用できるようになります。
i18n.site
の純粋なフロントエンド検索ソリューションは、 MarkDown
ドキュメント用に最適化されています。
検索結果を表示すると章名が表示され、クリックすると章へ移動します。
逆引き全文検索は純粋にフロントエンドに実装されており、サーバーは必要ありません。ドキュメントや個人のブログなどの中小規模の Web サイトに非常に適しています。
i18n.site
オープンソースの自社開発の純粋なフロントエンド検索は、サイズが小さく応答が速く、現在の純粋なフロントエンド全文検索の欠点を解決し、より良いユーザー エクスペリエンスを提供します。