static_search チュートリアル
このチュートリアルでは、MoonBit の static_search パッケージで静的サイトの検索と同じようにテキストを正規化する方法、検索インデックス付きの静的サイトをビルドして配信する方法、そのクエリの書き方を示します。インデックスと順位付けの詳細は static_search の設計 で説明しています。
クイックスタート
モジュールをプロジェクトに追加します。
moon add Luna-Flow/mooncake-impact-factor@0.1.2
このパッケージは JavaScript ターゲット専用なので、インポートする側のパッケージも同じにする必要があります。
import {
"Luna-Flow/mooncake-impact-factor/static_search",
}
supported_targets = "js"
大文字と小文字を区別しない照合は、小文字にしたテキストでの部分文字列の判定です。
test "quick start" {
let needle = @static_search.normalize_text("JSON")
let text = @static_search.normalize_text("moonbit-community/json5 JSON5 parser")
println(text.contains(needle))
}
true
moon test --target js で実行します。
よくある作業
検索ボックスの値を正規化する
検索ワーカーはクエリの値の前後の空白を除いて小文字にします。normalize_text は小文字化しかしないので、先に空白を除きます。
test "normalise a needle" {
let raw = " Http Client "
let needle = @static_search.normalize_text(raw.trim().to_owned())
println("[\{needle}]")
}
[http client]
静的サイトをビルドして配信する
リポジトリのルートで、データベースを構築し、静的データをエクスポートし、サイトをビルドします。
npm install
npm run build:static-data # builds cli and static_search, the database and public/data
npm run build:static # compiles static_search again, then runs next build into out/
npm run serve:static # serves out/ on http://localhost:4173
build:static-data には ~/.moon/registry/index/user のローカルレジストリインデックスが必要で(先に moon update を実行します)、キャッシュがなければ mooncakes.io からダウンロード数を取得します。検索インデックスは public/data/search/search-index.json で、ブラウザーはこれを一度だけ Web Worker に読み込みます。
クエリを書く
検索ボックスはネイティブの式言語を受け付けます。項は明示的な AND、OR、NOT で結び、括弧でグループ化します。
| クエリ | 見つかるもの |
|---|---|
json | 名前、オーナー、説明、キーワードのいずれかに json を含むパッケージ。 |
"http client" | 空白を含む部分文字列 http client そのもの。 |
owner:moonbitlang AND keyword:json | そのオーナーのパッケージのうち、キーワードに json を含むもの。 |
json AND score>=180 | json に言及し、スコアが 180 以上のパッケージ。 |
(yaml OR toml) AND NOT rank=D | ランク D 以外の YAML または TOML のパッケージ。 |
momentum=Rising AND recent_dependents>=5 | 最近の被依存パッケージが 5 つ以上ある急成長中のパッケージ。 |
フィールドは text、owner、package、keyword、description、license、repository、rank、momentum、score、dependents、recent_dependents、downloads、year、has_repository、has_license です。演算子は :(含む)、=、>=、<= で、> と < はありません。
結果の順序を理解する
明示的な並び順の指定がないとき、静的サイトは、パッケージが一致したクエリの項の数、スコア、名前の順に結果を並べます。次の MoonBit プログラムは、単語の OR についてこれを再現します。
priv struct Record {
name : String
text : String
score : Double
}
fn record(name : String, description : String, score : Double) -> Record {
{ name, text: @static_search.normalize_text("\{name} \{description}"), score }
}
fn coverage(r : Record, words : Array[String]) -> Int {
let mut n = 0
for w in words {
if r.text.contains(@static_search.normalize_text(w)) {
n += 1
}
}
n
}
test "coordination ranking" {
let records = [
record("alice/json", "Fast JSON parser", 120.0),
record("bob/toml", "TOML parser", 300.0),
record("carol/yaml", "YAML and JSON emitter", 80.0),
]
let words = ["json", "parser"]
let hits = records.filter(r => coverage(r, words) > 0)
hits.sort_by((a, b) => {
let by_rel = coverage(b, words).compare(coverage(a, words))
if by_rel != 0 {
return by_rel
}
let by_score = b.score.compare(a.score)
if by_score != 0 { by_score } else { a.name.compare(b.name) }
})
for r in hits {
println("\{r.name} \{coverage(r, words)}")
}
}
alice/json 2
bob/toml 1
carol/yaml 1
クエリ json OR parser は静的サイトで同じ順序になります。alice/json は両方の単語に一致し、残りの 2 つは 1 つずつ一致して同順位となり、スコアで並びます。json AND parser ではどの結果も両方の単語に一致するので、順序は単純なスコア順です。
さらに進んで
コンパイル済みモジュールを JavaScript から使う。 moon build src/static_search --target js は runtime_version と normalize_text を持つ ES モジュールを書き出します。インポートパスは static_search API を参照してください。JavaScript のコードが特定のリリースの振る舞いに依存する場合は runtime_version() を確認してください。
動的サイトと比べる。 ローカルサーバー(npm run dev)は同じクエリ言語を SQLite に対して実行します。そちらではテキストの項が部分文字列ではなく FTS5 による語の前方一致で照合され、並び順を指定しない場合の結果の順序も異なります。違いはすべて 設計ページ に挙げています。
GitHub Pages に公開する。 deploy-static ワークフローが同じコマンドを定期的に実行します。サイトをサブパスで配信する場合は、ワーカーが /<base>/data/... から取得するよう NEXT_PUBLIC_BASE_PATH を設定してください。
よくある落とし穴
- 暗黙の AND。 式言語では
ANDのないjson parserは構文エラーです。簡易検索フィールド(q)では、同じテキストが 1 つの部分文字列json parserになります。 - 空白の除去。
normalize_textは空白を残します。末尾に空白のある検索語は、空白を除いたものより一致する範囲が狭くなります。 - フィールドの演算子。
score:180は 180 以上ではなくscore = 180を意味します。score>=180を使ってください。 - ラベルは完全一致。 静的サイトでは
rank=sは何にも一致しません。rank=Sやmomentum=Risingと書いてください。 - ターゲットの誤り。
wasm-gcやnative向けにビルドするパッケージからstatic_searchをインポートすると失敗します。このパッケージは JavaScript の外部関数を使っているためです。
次のステップ
- static_search の設計: インデックスのレイアウト、関連度のカウント、クエリの計算量。
- static_search API: MoonBit と JavaScript の関数。
- score チュートリアル: 検索が絞り込みや並べ替えに使う数値。
- アーキテクチャガイド: データパイプライン全体。