brief: | i18n.site тепер підтримує безсерверний повнотекстовий пошук.
У цій статті представлено реалізацію технології повнотекстового пошуку в інтерфейсі, включаючи інвертований індекс, створений IndexedDB, пошук за префіксами, оптимізацію сегментації слів і багатомовну підтримку.
У порівнянні з існуючими рішеннями повнотекстовий пошук i18n.site має невеликий розмір і швидкість, підходить для веб-сайтів невеликого та середнього розміру, таких як документи та блоги, і доступний офлайн.
Після кількох тижнів розробки i18n.site (чисто статичний markdown multilingualtranslation & інструмент створення веб-сайтів) тепер підтримує чистий повнотекстовий пошук.
Ця стаття i18n.site про технічну реалізацію i18n.site
пошуку.
Код з відкритим вихідним кодом : / інтерфейс пошуку ядра
Для суто статичних веб-сайтів невеликого та середнього розміру, таких як документи/особисті блоги, створювати власноруч створений сервер повнотекстового пошуку надто важко, і повнотекстовий пошук без сервісів є більш поширеним вибором.
Безсерверні рішення для повнотекстового пошуку поділяються на дві великі категорії:
По-перше, подібні algolia.com Сторонні постачальники послуг пошуку надають інтерфейсні компоненти для повнотекстового пошуку.
Такі послуги потребують оплати залежно від обсягу пошуку та часто недоступні для користувачів у материковому Китаї через такі проблеми, як відповідність веб-сайту.
Його не можна використовувати офлайн, не можна використовувати в інтрамережі та має великі обмеження. Ця стаття не обговорює багато.
Другий — чистий інтерфейсний повнотекстовий пошук.
На даний момент звичайний повнотекстовий пошук включає lunrjs ElasticLunr.js на основі lunrjs
вторинної розробки).
lunrjs
Існує два способи створення індексів, і обидва мають свої проблеми.
Попередньо створені файли індексу
Оскільки покажчик містить слова з усіх документів, він великий за розміром. Щоразу, коли документ додається або змінюється, необхідно завантажити новий файл індексу. Це збільшить час очікування користувача та споживає велику пропускну здатність.
Завантажуйте документи та створюйте індекси на льоту
Створення індексу є інтенсивним обчислювальним завданням. Перебудова індексу щоразу, коли ви звертаєтеся до нього, призведе до явних затримок і поганої взаємодії з користувачем.
Окрім lunrjs
, існують інші рішення для повнотекстового пошуку, наприклад :
fusejs , обчислити подібність між рядками для пошуку.
Продуктивність цього рішення надзвичайно низька, і його не можна використовувати для повнотекстового пошуку (див Fuse.js Довгий запит займає більше 10 секунд, як це оптимізувати? ).
TinySearch , використовуйте фільтр Bloom для пошуку, не можна використовувати для пошуку за префіксом (наприклад, введіть goo
, шукайте good
, google
) і не можете досягти аналогічного ефекту автоматичного завершення.
Через недоліки існуючих рішень i18n.site
розробив нове рішення для повнотекстового пошуку, яке має такі характеристики :
6.9KB
gzip
для порівняння, розмір lunrjs
дорівнює 25KB
).indexedb
, який займає менше пам’яті та є швидким.Нижче буде детально описано 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()
в:
/\p{P}/
— це регулярний вираз, який відповідає знакам пунктуації. Конкретні відповідні символи включають: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _
{ | } ~. .</p><ul><li>
split('.')тому, що
Firefoxсегментація слова браузера не сегментує
. ` .
5 таблиць зберігання об'єктів було створено в IndexedDB
:
word
: id - слівdoc
: id - url - Номер версії документаdocWord
: Масив документа id - слово idprefix
: Масив префікса - слова idrindex
: Word id - Документ id : Масив номерів рядківПередайте масив документа url
і номер версії ver
і знайдіть, чи існує документ у таблиці doc
Якщо він не існує, створіть інвертований індекс. Водночас видаліть перевернутий індекс для тих документів, які не були передані.
Таким чином можна досягти поступової індексації та зменшити обсяг розрахунку.
Під час взаємодії з інтерфейсом індексу можна відобразити індикатор перебігу завантаження , css уникнути затримки під час / progress + .
Проект 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
кешує всі статті, коли користувач виконує пошук, весь веб-сайт, включаючи пошук, доступний офлайн.
Рішення чистого зовнішнього пошуку i18n.site
оптимізовано для документів MarkDown
.
Під час відображення результатів пошуку відображатиметься назва розділу, а після клацання буде здійснюватися навігація по розділу.
Перевернутий повнотекстовий пошук реалізовано виключно на інтерфейсі, сервер не потрібен. Він дуже підходить для веб-сайтів невеликого та середнього розміру, таких як документи та особисті блоги.
i18n.site
Власнорозроблений інтерфейсний пошук із відкритим кодом, малий розмір і швидка відповідь, усуває недоліки поточного повнотекстового пошуку в інтерфейсі та забезпечує кращий досвід роботи з користувачем.