使用FZF和预先反应来模糊搜索Astro
#javascript #教程 #搜索 #astro

注意:该帖子最初发表在my blog

静态站点发电机(SSG),例如Astro,非常适合构建静态页面。但是,它们很少包含网站搜索功能。通常,没有内容管理系统(CMS)或数据库可容纳内容,并且您可以查询搜索词。

在本教程中,我们将对Astro进行简单的搜索,使我们可以查询Markdown/MDX帖子的前磨。我们将创建一个执行搜索的先性挂钩。对于UI,我们将创建一个提前组件,以使用户输入搜索术语以及一个preact Component以在列表中显示搜索结果。

工具

在命令行上,我一直在使用fzffd结合使用了一段时间。我用它来搜索目录,外壳历史记录中的先前命令以及其他搜索/过滤器相关的任务。幸运的是,还有FZF for JavaScript,它是浏览器FZF的非正式港口。我们将使用此模块为搜索供电。

对于用户交互,我们需要一些HTML和客户端JavaScript。为了使我们变得更容易,我们将使用Astro's Preact integration,如果您还没有这样做,则需要setup

安装FZF

要将FZF添加到我们的Astro项目中,我们需要将其添加为package.json中的依赖性:

{
  "dependencies": {
    "fzf": "0.5.1"
  }
}

之后,我们使用纱线安装它 - 或您选择的任何包装管理器:

yarn install

# If you use npm, use this command instead
npm install

useFuzzySearch

要执行实际搜索,我们创建了一个挂钩,该挂钩可以通过我们的Markdown/MDX帖子的前牙字段进行搜索。我们记住搜索结果以免一遍又一遍地执行相同的搜索,但仅在我们想要搜索更改的帖子,搜索词或前磨牙字段时才搜索。

import { useMemo } from 'preact/hooks';
import { Fzf } from 'fzf';

/**
 * Search for search term in post's frontmatter
 *
 * @param {array} posts List of posts to search in
 * @param {string} searchTerm Term to search for
 * @param {string} field Frontmatter item in which to search
 * @param {object} [fzfOptions={ }] Additional options to pass to fzf
 */
function useFuzzySearch(posts, searchTerm, field, fzfOptions = { }) {
  const fzf = useMemo(() => {
    return new Fzf(posts, {
      selector: post => post.frontmatter[field],
      ...fzfOptions
    }, [ posts, field, fzfOptions]);
  });

  const searchResults = useMemo(() => {
    return fzf.find(searchTerm);
  }, [ fzf, searchTerm ]);

  return searchResults;
}

用户界面

对于本教程,我们将保持UI非常简单。我们需要一个具有输入元素的组件,供用户输入搜索词以及显示实际搜索结果的组件。

注意:我故意遗漏了所有CSS样式,错误处理等,以使理解本教程尽可能容易。

Search组件

搜索组件期望 - 作为prop-将要搜索前材的帖子,以及应搜索的字段。每当searchTerm更改时,都会执行useFuzzySearch钩以检索搜索结果。如果有搜索结果,则将这些结果传递给我们将在下一步中创建的SearchResults组件。

import { useState } from 'preact/hooks';
import useFuzzySearch from './useFuzzySearch';
import SearchResults from './SearchResults';

const DEFAULT_SEARCH_TERM = '';

function Search(props) {
  // Initialize state variables
  const [ searchTerm, setSearchTerm ] = useState(DEFAULT_SEARCH_TERM);
  const searchResults = useFuzzySearch(
    props.posts,
    searchTerm,
    props.field,
    { fuzzy: false }  // We only search for full word matches. See
                      // https://fzf.netlify.app/docs/latest#api
                      // for available options
  );

  // Called whenever the value of the input field changes.
  function onInput(e) {
    const searchTerm = e?.target?.value;
    if (searchTerm && searchTerm.length > 0) {
      setSearchTerm(searchTerm);
    } else {
      setSearchTerm(DEFAULT_SEARCH_TERM);
    }
  }

  return (
    <div>
      <input
        onInput={ onInput }
        type="text"
      />
      {
        searchResults &&
          <SearchResults
            searchResults={ searchResults }
          />
      }
    </div>
  )
}

SearchResults组件

SearchResults组件将显示一个简单的列表,其中帖子的前后字段与searchTerm匹配。

function SearchResults(props) {
  <div>
    {
      props.noOfSearchResults === 0 &&
        <span>No search results</span>
    }
    {
      props.noOfSearchResults > 0 &&
        props.searchResults.map(result => (
          <p>
            { result.item.frontmatter.title }
          </p>
        ))
    }
  </div>
}

如何使用

现在,我们已经准备好了搜索的逻辑和UI,我们需要做的就是将Search组件添加到任何Astro页面。我们添加client:visible directive,一旦用户可见搜索后,我们都可以补充我们的预性组件。

---
import Search from `Search.jsx`;

const posts = await Astro.glob('@posts/**/*.mdx');
---
<Search
  field="title"
  posts={ posts }
  client:visible
/>

最后一句话

这只是如何将搜索功能添加到Astro供电的网站上的一个基本示例。如前所述,我忽略了所有CSS样式,错误处理等,以使本教程易于理解。但是,如果您想自己在网站上实施搜索,那应该是一个不错的起点。

添加了一些CSS,最终结果可能看起来像下面的图像。要进行互动演示,请转到我博客的archive page

Example of a fuzzy search with CSS applied