AI 摘要
我最近在用 Astro 重构自己的博客网站,本来想给网站添加一个搜索功能,在 Astro 官网搜索一番后,发现官方没有做相关的插件或者教程,只有一条关于 Pagefind 的教程链接,我抱着试一试的心态去看了一下,然后结合 Pagefind 文档,花了一天时间成功搞定,效果如下:
Pagefind
我们先来看下官方的介绍:
Pagefind is a fully static search library that aims to perform well on large sites, while using as little of your users’ bandwidth as possible, and without hosting any infrastructure.
我英语不好,用 ChatGPT 翻译看一下:
Pagefind 是一个完全静态的搜索库,旨在在大型网站上表现良好,同时尽量减少用户的带宽使用,并且无需托管任何基础设施。
其实就是说 Pagefind 是一个静态的全文本搜索库,拥有良好的性能以及多平台支持。多提一句,它是基于 Rust 实现的。
创建索引
首先将 pagefind 作为依赖安装到我们的项目:
pnpm add astro-pagefind pagefind
其次,我们要为 Pagefind 创建索引,它的实现方式是提取构建的静态文件,一般都是在 dist
文件夹创建索引:
pnpx pagefind --site dist
最好把创建索引的命令放在 package.json
中,这样,每次 CI 构建时,都会自动创建最新索引,例如:
{
"scripts": {
"build": "astro build && pagefind --site dist"
}
}
注意,本地开发的时候,我们需要先运行 pnpm build
创建索引,然后再启动开发服务器 pnpm dev
,这样在开发时也会有索引。
搜索 UI
上面的步骤运行完之后,我们可以看到 Pagefind 在 dist
目录下生成的文件。由于 Pagefind 自带默认的搜索 UI,可以直接从 dist
目录下引用。如果你想自己构建搜索 UI,那么只需要引入 pagefind.js
即可,这也是我选择的开发方式。
我们可以把搜索 UI 封装成一个组件,比如 Search.astro
,首先完成我们的 HTML UI 结构:
<a class="font-bold hover:text-blue-500 cursor-pointer transition-colors duration-200" id="search-icon">/search</a>
<div class="hidden" id="search-box">
<div
id="search-backdrop"
class="fixed z-[1000] left-0 top-0 backdrop-blur-3xl h-screen w-screen overflow-hidden flex justify-center items-center bg-zinc-900/20 dark:bg-zinc-500/20"
>
</div>
<div
class="fixed left-1/2 top-32 -translate-x-1/2 z-[1001] w-[560px] bg-zinc-200 rounded-lg overflow-hidden shadow-xl dark:bg-zinc-900"
>
<div class="p-4">
<input
id="search-input"
class="w-full p-2 border-2 border-blue-500 rounded-lg focus:ring-0 focus:border-blue-500 outline-none dark:bg-zinc-950"
type="text"
placeholder="搜索文档"
/>
</div>
<div id="results" class="min-h-52 max-h-96 p-4 pt-0 overflow-auto space-y-2">
<div class="w-full min-h-48 text-5xl text-center flex justify-center items-center select-none">
<div>🤔🔍</div>
</div>
</div>
</div>
</div>
引入 Pagefind JS 文件:
{/* ...html */}
<script type="module">
const pagefind = await import('/pagefind/pagefind.js');
pagefind.init();
</script>
用户输入关键词后,调用 Pagefind API 实现搜索逻辑,并且将搜索结果渲染到页面上,我在这里使用了拼接 DOM 的方式,如果使用内置 UI 的话,就不用拼接。
<script type="module">
const searchInput = document.querySelector('#search-input');
searchInput.addEventListener('input', handleSearch);
async function handleSearch(e) {
const { value } = e.target;
let html = '';
const search = await pagefind.search(value);
const results = search.results.map((item) => item.data());
const data = await Promise.all(results);
data.map(({ meta: { title }, url, excerpt }) => {
html += `
<div class="bg-white dark:bg-zinc-800 p-2 rounded-lg shadow-xl">
<a href="${url}" class="text-blue-500 hover:text-blue-600 transition-colors duration-200">${title}</a>
<p class="text-gray-500 text-sm line-clamp-1 text-ellipsis mt-1 ml-4">${excerpt.replaceAll('<mark', '<mark class="bg-blue-500 text-white"')}</p>
</div>
`;
});
resultEl.innerHTML = html
}
</script>
最后,我把它引入到了 Header 组件,这样保证了搜索功能在全局都是可用的。
---
import Search from "@/components/Search.astro";
---
{/* ... */}
<Search />
{/* ... */}
优化搜索结果
开发完成后,我在本地试了下搜索功能,结果有点糟糕。我们之前创建的索引是包含了 dist
整个目录下的文件,所以会有比较多的冗余信息,所以需要进行过滤。
页面级限制 data-pagefind-body
data-pagefind-body
属性可以可以添加到页面的 body
元素上,如果我们的网站上存在一个此属性,那么在创建索引时,其余没有添加此属性的页面都会被忽略。
如果你只想创建某个页面的索引,就在页面的 body
元素上添加此属性,此时,其它页面的都不会创建索引。
<body data-pagefind-body>
{/* */}
</body>
元素级限制 data-pagefind-ignore
如果你想限制页面上的某些元素不能被索引时,可以添加 data-pagefind-ignore
属性,例如敏感信息等:
<div class="phone" data-pagefind-ignore>
{/* */}
</div>
属性级限制 data-pagefind-index-attrs
如果你想把某些元素的 HTML 的属性也添加到索引里的话,可以使用在指定元素上添加此属性,这在搜索图片时非常有用:
<img src="/hero.png" title="Image Title" alt="Image Alt" data-pagefind-index-attrs="title,alt" />
总结
通过上面这些开发配置,Pagefind 作为我博客的搜索引擎已经可以满足日常使用了。如果你需要其它功能,可以参考官方文档开发和配置。
最后,我想说 Pagefind 目前在 GitHub 上有 3k 多 star,而 Issues 也不多,98 个左右,所以作为一个轻量级的文本搜索库来讲,足够在个人博客里使用,而且它在官网上也说了,能够支撑大型网站。免费又开源,这还不得赶紧去人家的 GitHub 上点个 star 嘛。