Lien original : https://i18n.site/blog/tech/search
Après plusieurs semaines de développement, i18n.site (un outil de traduction multilingue et de création de sites Web purement statique) prend désormais en charge la recherche frontale en texte intégral pure.
Cet article partagera la mise en œuvre technique de la recherche en texte intégral purement frontale d'i18n.site. Visitez i18n.site pour découvrir la fonctionnalité de recherche.
Le code est open-source : Noyau de recherche / 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 sont divisées en deux catégories principales :
Le premier implique des fournisseurs de services de recherche tiers comme algolia.com qui proposent des composants frontaux pour la recherche en texte intégral.
Ces services nécessitent un paiement en fonction du volume de recherche et sont souvent indisponibles pour les utilisateurs en Chine continentale en raison de problèmes de conformité.
Ils ne peuvent pas être utilisés hors ligne ou sur intranet et présentent des limitations importantes. Cet article ne sera pas développé davantage.
La deuxième catégorie est la recherche frontale en texte intégral pure.
Actuellement, les outils de recherche en texte intégral front-end courants incluent lunrjs et ElasticLunr.js (un développement secondaire basé sur lunrjs).
lunrjs propose deux méthodes pour créer des index, toutes deux avec leurs propres problèmes.
Étant donné que l'index comprend tous les mots des documents, il est de grande taille.
Chaque fois qu'un document est ajouté ou modifié, un nouveau fichier d'index doit être chargé.
Cela augmente le temps d'attente des utilisateurs et consomme une quantité importante de bande passante.
La création d'un index est une tâche gourmande en calcul, et sa reconstruction à chaque accès peut entraîner des retards notables, conduisant à une mauvaise expérience utilisateur.
En plus de lunrjs, il existe d'autres solutions de recherche en texte intégral, telles que :
fusejs, qui recherche en calculant la similarité entre les chaînes.
Cette solution a des performances médiocres et n'est pas adaptée à la recherche en texte intégral (voir Fuse.js Une requête longue prend plus de 10 secondes, comment optimiser ?).
TinySearch, qui utilise un filtre Bloom pour la recherche, ne peut pas effectuer de recherches de préfixes (par exemple, saisir goo pour rechercher good ou google) et ne peut pas obtenir un effet de saisie semi-automatique.
En raison des inconvénients des solutions existantes, i18n.site a développé une nouvelle solution de recherche en texte intégral purement frontale avec les fonctionnalités suivantes :
Les détails de la mise en œuvre technique d'i18n.site seront présentés ci-dessous.
La segmentation des mots utilise l'Intel.Segmenter natif du navigateur, qui est pris en charge par tous les navigateurs grand public.
Le code coffeescript pour la segmentation des mots 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()
Où :
Cinq tables de stockage d'objets sont créées dans IndexedDB :
En transmettant un tableau d'URL du document et de numéro de version ver, la table doc est vérifiée pour l'existence du document. S'il n'existe pas, un index inversé est créé. Simultanément, l'index inversé des documents non transmis est supprimé.
Cette méthode permet une indexation incrémentielle, réduisant ainsi la charge de calcul.
In the front-end interface, a progress bar for index loading can be displayed to avoid lag during the initial load. See "Animated Progress Bar, Based on a Single progress + Pure CSS Implementation" English / Chinese.
The project is developed based on the asynchronous encapsulation of IndexedDB, idb.
IndexedDB reads and writes are asynchronous. When creating an index, documents are loaded concurrently to build the index.
To avoid data loss due to concurrent writes, you can refer to the following coffeescript code, which adds a ing cache between reading and writing to intercept competitive writes.
`coffee
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()
`
To display search results in real-time as the user types, for example, showing words like words and work that start with wor when wor is entered.
The search kernel uses the prefix table for the last word after segmentation to find all words with that prefix and search sequentially.
An anti-shake function, debounce (implemented as follows), is used in the front-end interaction to reduce the frequency of searches triggered by user input, thus minimizing computational load.
js
export default (wait, func) => {
var timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(func.bind(this, ...args), wait);
};
}
The search first segments the keywords entered by the user.
Assuming there are N words after segmentation, the results are first returned with all keywords, followed by results with N-1, N-2, ..., 1 keywords.
The search results displayed first ensure query precision, while subsequent loaded results (click the "Load More" button) ensure recall.
To improve response speed, the search uses the yield generator to implement on-demand loading, returning results after each limit query.
Note that after each yield, a new IndexedDB query transaction must be opened for the next search.
To display search results in real-time as the user types, for example, showing words like words and work that start with wor when wor is entered.
The search kernel uses the prefix table for the last word after segmentation to find all words with that prefix and search sequentially.
An anti-shake function, debounce (implemented as follows), is used in the front-end interaction to reduce the frequency of searches triggered by user input, thus minimizing computational load.
js
export default (wait, func) => {
var timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(func.bind(this, ...args), wait);
};
}
The index table does not store the original text, only words, reducing storage space.
Highlighting search results requires reloading the original text, and using service worker can avoid repeated network requests.
Also, because service worker caches all articles, once a search is performed, the entire website, including search functionality, becomes offline available.
The pure front-end search solution provided by i18n.site is optimized for MarkDown documents.
When displaying search results, the chapter name is shown, and clicking navigates to that chapter.
The pure front-end implementation of inverted full-text search, without the need for a server, is very suitable for small to medium-sized websites such as documents and personal blogs.
i18n.site's open-source self-developed pure front-end search is compact, responsive, and addresses the various shortcomings of current pure front-end full-text search solutions, providing a better user experience.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!