一直以来,Node.js 的文档 没有搜索功能,只是单纯的文本页面,不像其他文档比如 Vue.js,React 等接入了 algolia 搜索框,能根据关键字快速定位到具体的页面或者方法。
试下这个 chrome 插件,给 Node.js 文档页面加上搜索框,完美嵌入页面之中,就像原本就有一样。
项目地址:github.com/sunxen/docs...
插件下载:Chrome Web Store
预览效果
如何实现
如果是开源项目文档或者个人博客,其实可以免费接入 Algolia 的搜索服务。如果你也想自己折腾一下,可以参考。
搜索框UI
docsearch/packages/docsearch-react
是搜索框 UI,从 docsearch 分支出来,并修改以支持本地搜索。原项目是依赖 algolia 的后端服务的,修改了 DocSearchModal.tsx
将搜索结果改成本地生成。
localSearch.ts
生成对应的数据结构,每一个 item 如下
ts
{
url: link.url,
content: null,
type: 'lvl' + typeNumber,
hierarchy: {
lvl0: link.levels[0],
lvl1: subTitle ? subTitle : title,
lvl2: title,
},
objectID: String(link.id),
_highlightResult: {
hierarchy: {
lvl0: {
value: link.levels[0],
matchLevel: 'none',
matchedWords: [],
},
lvl1: {
value: subTitle ? subTitle : title,
matchLevel: 'none',
matchedWords: [],
},
lvl2: {
value: title,
matchLevel: 'none',
matchedWords: [],
},
},
},
}
group
对应 lvl0title
对应最大的 lvlsubtitle
对应中间的 lvl- 框架会自动处理各个 item 之间的分组和层级关系
为了节省开发环境的配置时间,可以在 Github Codespace 进行开发。
数据源
docsearch/crawler
是一个 Node.js 脚本,它爬取 Node.js 文档网站,并生成一个包含所有文档的 json 文件,保存所有的标题和链接
json
[
...,
{
"id": 365,
"levels": [
"Buffer",
"Class: Blob",
"blob.size"
],
"url": "https://nodejs.org/api/buffer.html#blobsize"
},
...
]
本地搜索
Fuse.js
是一个 JavaScript 实现的搜索框架,支持模糊搜索。
ts
const fuse = new Fuse(
links.map((item) => item.levels.join(' ')),
{
threshold: 0.3,
ignoreLocation: true,
useExtendedSearch: true,
}
);
const fuseResult = fuse.search(query, { limit: 5 });
item.levels.join(' ')
将所有层级拼接在一起,除了简单,还可以进行整体的模糊搜索呢threshold
合适的模糊匹配程度ignoreLocation
因为将所有层级作为一个整体处理,所以这里忽略匹配的开始位置useExtendedSearch
扩展搜索模式,支持正则表达式,参考 fusejs.io
chrome 插件
如果是自己的网站,可以直接引入对应的 JavaScript,如果修改第三方网站,可以通过 chrome 插件的方式将搜索框 UI 嵌入到页面的相应位置。
tsx
import './App.css';
export default function App() {
return (
<div>
<DocSearch
indexName="nodejs-docs"
appId="local"
apiKey="local"
/>
</div>
);
}
export function renderSearchButton() {
// add div to top right of page, then add button to div
const div = document.createElement('div');
div.id = 'nodejs-docsearch-container';
(document.querySelector('header') || document.body).appendChild(div);
const root = createRoot(div);
root.render(<App />);
}