Привет, друзья! Недавно (где-то в конце августа) у MDN появился новый (крутой, по заявлению разработчиков) поиск. Речь идет об этом виджете на главной странице: 
 
Основной код виджета находится здесь, а все остальное, кроме данных для поиска, можно найти здесь. Данные для поиска также найти не сложно — для этого достаточно добавить к https://developer.mozilla.org локаль, например, /ru и /search-index.json: 
Код поиска написан на React и TypeScript. Что это означает? Это означает, что мы с вами можем провести его обратную разработку. Если вам это интересно, прошу следовать за мной. Обратите внимание: данная статья рассчитана на разработчиков, которые имеют некоторый опыт работы с React. Желательно также иметь хотя бы общее представление о TypeScript. Для удобства я вынес весь код, связанный с поиском в отдельный проект, исходный код которого находится здесь. Практически каждая строка кода проекта снабжена подробным комментарием. Скриншот проекта: 
 Команды для локального запуска проекта: Общая логика поиска MDN такова: Отличия проекта от оригинала состоят в следующем: Вместе с тем, я сохранил оригинальный код в закомментированном виде (и комментарии разработчиков поиска), кроме кода для prefething. Рассмотрим, как работает поиск. С вашего позволения, я немного упрощу оригинальный код. Сначала мы получаем данные: Такая сигнатура хука включает режим ожидания — useSWR ждет начала инициализации. Другими словами, он запустится автоматически, как только shouldInitialize получит значение true. К слову, для отключения повторной валидации (revalidation) при возвращении фокуса и в некоторых других случаях в последних версиях SWR предназначен хук useSWRImmutable из swr/immutable. Далее с помощью полученных данных инициализируется поиск: Логика инициализации поискового индекса инкапсулирована в хуке useSearchIndex: Как мы видим, хук возвращает массив, состоящий из поискового индекса, ошибки, которая может иметь значение null, и функции для запуска инициализации. Остановимся на том, как работает каждый вид поиска. Для определения того, какой поиск следует использовать (неточный или полнотекстовый) применяется такая функция: Рассмотрим работу fzf и flexsearch на практических примерах. Предположим, что у нас имеются такие данные для поиска: И мы хотим выполнять поиск по url объектов. Для этого отлично подойдет fzf: В документации fzf имеется пример реализации подсветки совпадения: Простой компонент для поиска: Если ввести в инпуте java и нажать Enter, результат будет таким: 
Почему бы для такого поиска не воспользоваться методом String.prototype.includes()? Хороший вопрос. Насколько я понимаю, fzf выигрывает в производительности при большом количестве объектов для поиска и при больших (длинных) значениях строки для поиска. Кроме того, он умеет определять наличие в строке искомых символов, которые находятся в разных местах строки. Например, предположим, что у нас имеется строка aaYbbbbXccc и мы ввели в инпуте yx. Такая строка будет обнаружена fzf, но не методом includes(), поскольку символы y и x не следуют друг за другом. Но, если наши объекты отсортированы по популярности, как в search-index.json, то при длине строки для поиска менее 4 символов (<= 3), как утверждают разработчики поиска, выборка подходящих объектов с помощью метода includes() будет более быстрой, чем исследование каждого объекта с помощью fzf. Вот почему для поиска используется "оптимизирующая" обертка над fzf. Оптимизация заключается в том, что мы сокращаем список объектов, передаваемых в конструктор fzf. Отлично. С fzf разобрались. В остальных случаях (когда строка запроса начинается не с / или содержит пробелы) выполняется поиск по title. Для этого используется полнотекстовый поиск. При создании экземпляра в конструктор Index может передаваться объект с настройками. В данном случае мы определяем режим индексации tokenize: 'forward'. forward означает инкрементальную (частичную) индексацию в прямом направлении, т.е. вперед. Подробнее об этом можно почитать здесь. Затем мы перебираем объекты данных для поиска и записываем их заголовки в индекс под идентификаторами (индексами объектов). Метод flex.search() принимает строку для поиска и опциональный объект с настройками и возвращает массив индексов совпадений. Мы перебираем этот массив и извлекаем из данных для поиска соответствующие объекты. Если ввести в инпуте java и нажать Enter, результат будет таким: 
К слову, в документации flexsearch имеется ссылка на хук useFlexSearch, однако он не совместим с новым синтаксисом flexsearch. Если его немного причесать, получится следующее: Пример использования данного хука: Замечательно. С flexsearch тоже все более-менее понятно. Примеры использования поисков и хука находятся в директории basic-search-examples. В основном файле с кодом поиска происходит еще много интересных вещей. С тем, как работает хук useCombobox, я предоставлю вам возможность разобраться самостоятельно (вот более чем подробная документация). Пожалуй, это все, чем я хотел поделиться с вами в этой статье. Теперь в вашем распоряжении имеется готовый к использованию продвинутый компонент для поиска, который легко приспособить для нужд конкретного приложения. Что касается моего личного мнения по поводу "крутости" поиска, безусловно, он очень хорош. Я бы немного упростил код, поскольку нахожу несколько вещей лишними. Также я убрал бы парочку оптимизаций, поскольку они стоят дороже повторного рендеринга, который в любом случае происходит из-за большого количества постоянно меняющихся зависимостей. Также в оригинале, судя по всему, отключена проверка на неявный тип any ("noImplicitAny": false), что, на мой взгляд, не есть правильно. Надеюсь, вам было также интересно, как и мне. Приветствуется любая форма обратной связи. Благодарю за внимание и хорошего дня!  JavaScript Developer