brief: | i18n.site prend désormais en charge la recherche en texte intégral sans serveur.
Cet article présente la mise en œuvre d'une technologie de recherche en texte intégral purement frontale, y compris l'index inversé construit par IndexedDB, la recherche de préfixe, l'optimisation de la segmentation des mots et la prise en charge multilingue.
Par rapport aux solutions existantes, la recherche en texte intégral frontale pure d'i18n.site est petite et rapide, adaptée aux sites Web de petite et moyenne taille tels que les documents et les blogs, et est disponible hors ligne.
Après plusieurs semaines de développement, i18n.site (un outil de création de sites Web & markdown traduction multilingue purement statique) prend désormais en charge la recherche en texte intégral purement frontale.
Cet article partagera la mise en œuvre technique de i18n.site
recherche en texte intégral frontale pure. Visitez i18n.site
Code open source : Rechercher dans le noyau / interface interactive
Pour les sites Web purement statiques de petite et moyenne taille, tels que les documents/blogs personnels, la création d'un moteur de recherche en texte intégral auto-construit est trop lourde, et la recherche en texte intégral sans service est le choix le plus courant.
Les solutions de recherche en texte intégral sans serveur se répartissent en deux grandes catégories :
Tout d'abord, similaire algolia.com Les fournisseurs de services de recherche tiers fournissent des composants frontaux pour la recherche en texte intégral.
Ces services nécessitent un paiement basé sur le volume de recherche et sont souvent indisponibles pour les utilisateurs de Chine continentale en raison de problèmes tels que la conformité des sites Web.
Il ne peut pas être utilisé hors ligne, ni sur l’intranet et présente de grandes limitations. Cet article ne discute pas de grand chose.
La seconde est une pure recherche frontale en texte intégral.
À l'heure actuelle, les recherches en texte intégral purement front-end incluent lunrjs et ElasticLunr.js (basées sur lunrjs
développement secondaire).
lunrjs
Il existe deux manières de créer des index, et toutes deux ont leurs propres problèmes.
Fichiers d'index prédéfinis
Étant donné que l’index contient des mots de tous les documents, il est de grande taille. Chaque fois qu'un document est ajouté ou modifié, un nouveau fichier d'index doit être chargé. Cela augmentera le temps d'attente de l'utilisateur et consommera beaucoup de bande passante.
Chargez des documents et créez des index à la volée
La création d'un index est une tâche gourmande en calcul. La reconstruction de l'index à chaque fois que vous y accédez entraînera des retards évidents et une mauvaise expérience utilisateur.
En plus de lunrjs
, il existe d'autres solutions de recherche en texte intégral, telles que :
fusejs , calcule la similarité entre les chaînes à rechercher.
Les performances de cette solution sont extrêmement médiocres et ne peuvent pas être utilisées pour la recherche en texte intégral (voir Fuse.js Une requête longue prend plus de 10 secondes, comment l'optimiser ? ).
TinySearch , utilisez le filtre Bloom pour rechercher, ne peut pas être utilisé pour la recherche de préfixe (par exemple, entrez goo
, recherchez good
, google
) et ne peut pas obtenir un effet de complétion automatique similaire.
En raison des lacunes des solutions existantes, i18n.site
a développé une nouvelle solution de recherche plein texte purement frontale, qui présente les caractéristiques suivantes :
gzip
est 6.9KB
(à titre de comparaison, la taille de lunrjs
est 25KB
).indexedb
, qui prend moins de mémoire et est rapide.Ci-dessous, i18n.site
détails de mise en œuvre technique seront présentés en détail.
La segmentation des mots utilise la segmentation des mots native du navigateur Intl.Segmenter
, et tous les navigateurs grand public prennent en charge cette interface.
Le code de segmentation de mots coffeescript
est le suivant
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()
dans:
/\p{P}/
est une expression régulière qui correspond aux signes de ponctuation. Les symboles correspondants spécifiques incluent : ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _
{ | } ~. .</p><ul><li>
split('.')est dû au fait que la segmentation du mot
Firefoxdu navigateur ne segmente pas
. ` .
5 tables de stockage d'objets ont été créées en IndexedDB
:
word
: id - motsdoc
: id - Numéro de document url - de version du documentdocWord
: Tableau du document id - mot idprefix
: Tableau de préfixe - mot idrindex
: Word id - Document id : Tableau de numéros de ligneTransmettez le tableau du document url
et du numéro de version ver
et recherchez si le document existe dans le tableau doc
S'il n'existe pas, créez un index inversé. Dans le même temps, supprimez l'index inversé pour les documents qui n'ont pas été transmis.
De cette manière, une indexation incrémentielle peut être obtenue et la quantité de calcul est réduite.
Dans l'interaction frontale, la barre de progression du chargement de l'index peut être affichée pour éviter le décalage lors du premier chargement. Voir "Barre de progression avec animation, basée sur une seule progress + Pure css Implémentation" Anglais / Chinois .
Le projet est idb sur la base de l'encapsulation asynchrone de IndexedDB
Les lectures et écritures IndexedDB sont asynchrones. Lors de la création d'un index, les documents seront chargés simultanément pour créer l'index.
Afin d'éviter une perte partielle de données causée par une écriture compétitive, vous pouvez vous référer au code coffeescript
ci-dessous et ajouter un cache ing
entre la lecture et l'écriture pour intercepter les écritures concurrentes.
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()
La recherche segmentera d’abord les mots-clés saisis par l’utilisateur.
Supposons qu'il y ait N
mot après la segmentation des mots. Lors du renvoi des résultats, les résultats contenant tous les mots-clés seront renvoyés en premier, puis les résultats contenant N-1
, N-2
,..., 1
mots-clés seront renvoyés.
Les résultats de recherche affichés en premier garantissent l'exactitude de la requête, et les résultats chargés ultérieurement (cliquez sur le bouton Charger plus) assurent le taux de rappel.
Afin d'améliorer la vitesse de réponse, la recherche utilise le générateur yield
pour implémenter le chargement à la demande et renvoie limit
fois qu'un résultat est interrogé.
Notez que chaque fois que vous effectuez une nouvelle recherche après yield
, vous devez rouvrir une transaction de requête de IndexedDB
.
Afin d'afficher les résultats de la recherche pendant que l'utilisateur tape, par exemple lorsque wor
est saisi, les mots préfixés par wor
tels que words
et work
sont affichés.
Le noyau de recherche utilisera la table prefix
pour le dernier mot après la segmentation des mots afin de trouver tous les mots préfixés par celui-ci et de rechercher dans l'ordre.
La fonction anti-tremblement debounce
est également utilisée dans l'interaction frontale (implémentée comme suit) pour réduire la fréquence des recherches déclenchant les entrées utilisateur et réduire la quantité de calcul.
export default (wait, func) => {
var timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(func.bind(this, ...args), wait);
};
}
La table d'index ne stocke pas le texte original, uniquement les mots, ce qui réduit la quantité de stockage.
La mise en évidence des résultats de recherche nécessite de recharger le texte original, et la correspondance avec service worker
peut éviter des requêtes réseau répétées.
Dans le même temps, étant donné que service worker
met en cache tous les articles, une fois que l'utilisateur effectue une recherche, l'intégralité du site Web, y compris la recherche, est disponible hors ligne.
La solution de recherche purement frontale de i18n.site
est optimisée pour les documents MarkDown
.
Lors de l'affichage des résultats de la recherche, le nom du chapitre sera affiché et le chapitre sera parcouru lorsque vous cliquerez dessus.
Recherche en texte intégral inversée implémentée uniquement sur le front-end, aucun serveur requis. Il convient très bien aux sites Web de petite et moyenne taille tels que les documents et les blogs personnels.
i18n.site
La recherche frontale pure auto-développée open source, de petite taille et de réponse rapide, résout les lacunes de la recherche frontale pure en texte intégral actuelle et offre une meilleure expérience utilisateur.