brief: | i18n.site тепер підтримує безсерверний повнотекстовий пошук.

У цій статті представлено реалізацію технології повнотекстового пошуку в інтерфейсі, включаючи інвертований індекс, створений IndexedDB, пошук за префіксами, оптимізацію сегментації слів і багатомовну підтримку.

У порівнянні з існуючими рішеннями повнотекстовий пошук i18n.site має невеликий розмір і швидкість, підходить для веб-сайтів невеликого та середнього розміру, таких як документи та блоги, і доступний офлайн.


Чистий Зовнішній Інвертований Повнотекстовий Пошук

Послідовність

Після кількох тижнів розробки i18n.site (чисто статичний markdown multilingualtranslation & інструмент створення веб-сайтів) тепер підтримує чистий повнотекстовий пошук.

Ця стаття i18n.site про технічну реалізацію i18n.site пошуку.

Код з відкритим вихідним кодом : / інтерфейс пошуку ядра

Огляд Безсерверних Рішень Повнотекстового Пошуку

Для суто статичних веб-сайтів невеликого та середнього розміру, таких як документи/особисті блоги, створювати власноруч створений сервер повнотекстового пошуку надто важко, і повнотекстовий пошук без сервісів є більш поширеним вибором.

Безсерверні рішення для повнотекстового пошуку поділяються на дві великі категорії:

По-перше, подібні algolia.com Сторонні постачальники послуг пошуку надають інтерфейсні компоненти для повнотекстового пошуку.

Такі послуги потребують оплати залежно від обсягу пошуку та часто недоступні для користувачів у материковому Китаї через такі проблеми, як відповідність веб-сайту.

Його не можна використовувати офлайн, не можна використовувати в інтрамережі та має великі обмеження. Ця стаття не обговорює багато.

Другий — чистий інтерфейсний повнотекстовий пошук.

На даний момент звичайний повнотекстовий пошук включає lunrjs ElasticLunr.js на основі lunrjs вторинної розробки).

lunrjs Існує два способи створення індексів, і обидва мають свої проблеми.

  1. Попередньо створені файли індексу

    Оскільки покажчик містить слова з усіх документів, він великий за розміром. Щоразу, коли документ додається або змінюється, необхідно завантажити новий файл індексу. Це збільшить час очікування користувача та споживає велику пропускну здатність.

  2. Завантажуйте документи та створюйте індекси на льоту

    Створення індексу є інтенсивним обчислювальним завданням. Перебудова індексу щоразу, коли ви звертаєтеся до нього, призведе до явних затримок і поганої взаємодії з користувачем.


Окрім lunrjs , існують інші рішення для повнотекстового пошуку, наприклад :

fusejs , обчислити подібність між рядками для пошуку.

Продуктивність цього рішення надзвичайно низька, і його не можна використовувати для повнотекстового пошуку (див Fuse.js Довгий запит займає більше 10 секунд, як це оптимізувати? ).

TinySearch , використовуйте фільтр Bloom для пошуку, не можна використовувати для пошуку за префіксом (наприклад, введіть goo , шукайте good , google ) і не можете досягти аналогічного ефекту автоматичного завершення.

Через недоліки існуючих рішень i18n.site розробив нове рішення для повнотекстового пошуку, яке має такі характеристики :

  1. Підтримує багатомовний пошук і розмір ядра пошуку 6.9KB gzip для порівняння, розмір lunrjs дорівнює 25KB ).
  2. Створіть інвертований індекс на основі indexedb , який займає менше пам’яті та є швидким.
  3. Коли документи додаються/змінюються, повторно індексуються лише додані чи змінені документи, що зменшує обсяг розрахунків.
  4. Підтримує пошук за префіксом і може відображати результати пошуку в режимі реального часу, поки користувач вводить текст.
  5. Доступний офлайн

Нижче буде детально описано i18n.site деталей технічної реалізації.

Багатомовна Сегментація Слів

Сегментація слів використовує власну сегментацію слів браузера Intl.Segmenter , і всі основні браузери підтримують цей інтерфейс.

Код сегментації coffeescript слова виглядає наступним чином

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

в:

Побудова Покажчика

5 таблиць зберігання об'єктів було створено в IndexedDB :

Передайте масив документа url і номер версії ver і знайдіть, чи існує документ у таблиці doc Якщо він не існує, створіть інвертований індекс. Водночас видаліть перевернутий індекс для тих документів, які не були передані.

Таким чином можна досягти поступової індексації та зменшити обсяг розрахунку.

Під час взаємодії з інтерфейсом індексу можна відобразити індикатор перебігу завантаження , css уникнути затримки під час / progress + .

IndexedDB З Високим Рівнем Одночасного Запису

Проект idb на основі асинхронної інкапсуляції IndexedDB

Читання та запис IndexedDB є асинхронними. Під час створення індексу документи завантажуватимуться одночасно для створення індексу.

Щоб уникнути часткової втрати даних через конкурентний запис, ви можете звернутися до коду coffeescript нижче та додати кеш ing між читанням і записом, щоб перехоплювати конкуруючі записи.

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

Точність І Відкликання

Пошук спочатку сегментує ключові слова, введені користувачем.

Припустимо, що після сегментації слів N слів. Під час повернення результатів спочатку будуть повернуті результати, що містять усі ключові слова, а потім результати, що містять N-1 , N-2 ,..., 1 ключові слова.

Результати пошуку, які відображаються першими, забезпечують точність запиту, а результати, завантажені пізніше (натисніть кнопку «Завантажити більше»), забезпечують швидкість виклику.

Навантаження На Вимогу

Щоб покращити швидкість відповіді, пошук використовує генератор yield для реалізації завантаження на вимогу та повертає limit , коли запитується результат.

Зауважте, що кожного разу, коли ви шукаєте знову після yield , вам потрібно знову відкрити транзакцію запиту IndexedDB .

Пошук Префіксів У Реальному Часі

Для відображення результатів пошуку під час введення користувачем, наприклад, коли введено wor , відображаються слова з префіксом wor наприклад words і work .

Ядро пошуку використовуватиме таблицю prefix для останнього слова після сегментації слова, щоб знайти всі слова з префіксом і здійснювати послідовний пошук.

Функція анти-тремтіння debounce також використовується у зовнішній взаємодії (реалізована таким чином), щоб зменшити частоту введення користувача, що запускає пошук, і зменшити кількість обчислень.

export default (wait, func) => {
  var timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(func.bind(this, ...args), wait);
  };
}

Доступний Офлайн

Індексна таблиця не зберігає оригінальний текст, лише слова, що зменшує обсяг пам’яті.

Виділення результатів пошуку вимагає перезавантаження вихідного тексту, а відповідність service worker може уникнути повторних мережевих запитів.

У той же час, оскільки service worker кешує всі статті, коли користувач виконує пошук, весь веб-сайт, включаючи пошук, доступний офлайн.

Оптимізація Відображення Документів MarkDown

Рішення чистого зовнішнього пошуку i18n.site оптимізовано для документів MarkDown .

Під час відображення результатів пошуку відображатиметься назва розділу, а після клацання буде здійснюватися навігація по розділу.

Підведіть Підсумки

Перевернутий повнотекстовий пошук реалізовано виключно на інтерфейсі, сервер не потрібен. Він дуже підходить для веб-сайтів невеликого та середнього розміру, таких як документи та особисті блоги.

i18n.site Власнорозроблений інтерфейсний пошук із відкритим кодом, малий розмір і швидка відповідь, усуває недоліки поточного повнотекстового пошуку в інтерфейсі та забезпечує кращий досвід роботи з користувачем.