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.


Búsqueda pura de texto completo invertida en el front-end

Secuencia

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

Una descripción general de las soluciones de búsqueda de texto completo sin servidor

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.

  1. 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.

  2. 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:

  1. Soporta búsqueda multilingüe y es más ligera. El tamaño del núcleo de búsqueda después del empaquetado gzip es de solo 6.9KB (a diferencia de lunrjs, que tiene un tamaño de 25KB)
  2. Se crea un índice invertido basado en IndexedDB, que consume menos memoria y es rápido
  3. Cuando se agregan/modifican documentos, solo se vuelven a indexar los documentos agregados o modificados, lo que reduce la cantidad de cálculos
  4. Soporta búsqueda de prefijos, lo que permite mostrar los resultados de búsqueda en tiempo real a medida que el usuario escribe
  5. Disponible sin conexión

A continuación, se detallarán los aspectos técnicos de la implementación en i18n.site.

Segmentación de palabras multilingüe

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:

Constructión de índices

Se crearon cinco tablas de almacenamiento de objetos en IndexedDB:

Pase 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.

Escritura concurrente de alta capacidad en IndexedDB

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()

Precisión y recuperación

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.

Carga bajo demanda

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.

Búsqueda de prefijos en tiempo real

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);
  };
}

Disponible sin conexión

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.

Optimización de visualización de documentos Markdown

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.

Resumen

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.