brief: | i18n.site ahora admite búsqueda de texto completo sin servidor.
Este artículo presenta la implementación de tecnología de búsqueda de texto completo frontal pura, incluyendo la creación de índices invertidos con IndexedDB, búsqueda de prefijos, optimización de segmentación de palabras y soporte multilingüe.
En comparación con las soluciones existentes, la búsqueda de texto completo frontal pura de i18n.site es más ligera y rápida, adecuada para sitios web pequeños y medianos, como documentos y blogs, y es accesible sin conexión.
Después de varias semanas de desarrollo, i18n.site (una herramienta de creación de sitios web de markdown traducción multilingüe y puramente estática) ahora admite búsqueda de texto completo en el front-end.
Este artículo compartirá la implementación técnica de la búsqueda de texto completo frontal pura en i18n.site
. i18n.site Visite para experimentar los resultados de búsqueda!
Código fuente abierto: Núcleo de búsqueda / Interfaz interactiva
Para sitios web pequeños y medianos puramente estáticos, como documentos/blogs personales, crear un backend de búsqueda de texto completo autoconstruido es demasiado pesado; la búsqueda de texto completo sin servicios es la opción más común.
Las soluciones de búsqueda de texto completo sin servidor se dividen en dos grandes categorías:
Primero, servicios de terceros de búsqueda de texto completo como algolia.com, que proporcionan componentes de interfaz para la búsqueda de texto completo.
Estos servicios requieren pago por volumen de búsqueda y, a menudo, no están disponibles para los usuarios en China continental debido a problemas de cumplimiento del sitio web.
No son accesibles sin conexión, no se pueden usar en la intranet y tienen muchas limitaciones. Este artículo no se detendrá mucho en ellas.
En segundo lugar, la búsqueda de texto completo puramente frontal.
En la actualidad, las búsquedas de texto completo de front-end puras comunes incluyen lunrjs y ElasticLunr.js (desarrollado sobre lunrjs
).
lunrjs
tiene dos métodos de creación de índices y ambos tienen sus propios problemas.
Archivos de índice predefinidos
Dado que el índice contiene todas las palabras de los documentos, es grande. Cada vez que se agrega o modifica un documento, se debe cargar un nuevo archivo de índice. Esto aumentará el tiempo de espera del usuario y consumirá mucho ancho de banda.
Cargar documentos y crear índices en tiempo real
Crear un índice es una tarea intensiva en términos de procesamiento. Reconstruir el índice cada vez que se accede causará retrasos obvios y una mala experiencia del usuario.
Además de lunrjs
, hay otras soluciones de búsqueda de texto completo, como:
fusejs, que calcula la similitud entre las cadenas a buscar.
Esta solución tiene un rendimiento extremadamente deficiente y no se puede usar para búsquedas de texto completo (ver Fuse.js: Cómo optimizar consultas largas que tardan más de 10 segundos).
TinySearch, que utiliza un filtro Bloom para buscar, no se puede usar para la búsqueda de prefijos (por ejemplo, buscar goo
para good
o google
) y no puede lograr un efecto de autocompletado similar.
Debido a las deficiencias de las soluciones existentes, i18n.site
ha desarrollado una nueva solución de búsqueda de texto completo frontal pura con las siguientes características:
gzip
es de solo 6.9KB
(a diferencia de lunrjs
, que tiene un tamaño de 25KB
)IndexedDB
, que consume menos memoria y es rápidoA continuación, se detallarán los aspectos técnicos de la implementación en i18n.site
.
La segmentación de palabras utiliza la interfaz nativa del navegador Intl.Segmenter
, que es compatible con la mayoría de los navegadores.
El código de segmentación de palabras en coffeescript
es el siguiente:
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()
En:
/\p{P}/
es una expresión regular que coincide con los signos de puntuación. Los símbolos coincidentes específicos incluyen: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _
{ | } .</p><ul><li>
split('.')se debe a que la segmentación de palabras del navegador
Firefoxno segmenta
.` .
Se crearon cinco tablas de almacenamiento de objetos en IndexedDB
:
word
: id - Palabradoc
: id - URL del documento - Número de versión del documentodocWord
: Matriz de id de documento - Matriz de id de palabraprefix
: Matriz de prefijo - Matriz de id de palabrarindex
: id de palabra - id de documento : Matriz de números de líneaPase la matriz del documento url
y el número de versión ver
, y busque si el documento existe en la tabla doc
Si no existe, cree un índice invertido. Al mismo tiempo, elimine el índice invertido de aquellos documentos que no se pasaron.
De esta manera, se puede lograr una indexación incremental y se reduce la cantidad de cálculos.
En la interacción del front-end, se puede mostrar la barra de progreso de carga del índice para evitar retrasos al cargar por primera vez. Ver "Barra de progreso con animación, basada en una implementación única de progress + Pura css" Inglés / Chino.
El proyecto se desarrolla utilizando la encapsulación asincrónica de IndexedDB, idb.
Las operaciones de lectura y escritura en IndexedDB son asincrónicas. Al crear un índice, los documentos se cargan simultáneamente para la creación del índice.
Para evitar la pérdida parcial de datos debido a la escritura competitiva, se puede consultar el código en coffeescript
a continuación y agregar un caché ing
entre la lectura y la escritura para interceptar la escritura competitiva.
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 búsqueda primero segmentará las palabras clave introducidas por el usuario.
Supongamos que hay N
palabras después de la segmentación de palabras. Al devolver resultados, primero se devolverán los resultados que contienen todas las palabras clave y luego se devolverán los resultados que contienen N-1
, N-2
, ..., 1
palabras clave.
Los resultados de búsqueda que se muestran primero garantizan la precisión de la consulta y los resultados cargados posteriormente (haciendo clic en el botón "Cargar más") garantizan la tasa de recuperación.
Para mejorar la velocidad de respuesta, la búsqueda utiliza el generador yield
para implementar la carga bajo demanda y regresa limit
resultados cada vez que se realiza una consulta.
Tenga en cuenta que cada vez que se realice una búsqueda después de yield
, se deberá volver a abrir una transacción de consulta de IndexedDB
.
Para mostrar los resultados de búsqueda en tiempo real a medida que el usuario escribe, por ejemplo, al ingresar wor
, se mostrarán palabras con el prefijo wor
como words
y work
.
El núcleo de búsqueda utilizará la tabla prefix
para la última palabra después de la segmentación de palabras para encontrar todas las palabras con prefijo y buscar en secuencia.
La función anti-rebote debounce
también se utiliza en la interacción del front-end (implementada de la siguiente manera) para reducir la frecuencia de las entradas del usuario que activan búsquedas y reducir la cantidad de cálculos.
export default (wait, func) => {
var timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(func.bind(this, ...args), wait);
};
}
La tabla de índice no almacena el texto original, solo las palabras, lo que reduce la cantidad de almacenamiento.
Para resaltar los resultados de la búsqueda, es necesario volver a cargar el texto original y usar service worker
para evitar solicitudes de red repetidas.
Al mismo tiempo, debido a que service worker
almacena en caché todos los artículos, una vez que el usuario realiza una búsqueda, todo el sitio web, incluida la búsqueda, está disponible sin conexión.
La solución de búsqueda frontal pura de i18n.site
está optimizada específicamente para documentos de Markdown
.
Al mostrar los resultados de la búsqueda, se mostrará el nombre del capítulo y se navegará al capítulo al hacer clic.
La búsqueda invertida de texto completo implementada exclusivamente en el front-end no requiere servidor. Es muy adecuada para sitios web pequeños y medianos, como documentos y blogs personales.
i18n.site
ofrece una búsqueda frontal pura de código abierto, ligera y rápida, que resuelve las deficiencias de la búsqueda frontal pura de texto completo actual y proporciona una mejor experiencia de usuario.