VuePress slimsearch 搜索框三项修复
VuePress slimsearch 搜索框三项修复
这次给博客搜索框做了三件事:从默认 search: true 切到 slimsearch 插件、把搜索结果数量收敛回来、修掉拼音输入法回车的误跳转。下面按问题逐条记录根因、改法、验证和踩过的坑。
环境是 VuePress 2 rc.30 + vuepress-theme-hope rc.107 + @vuepress/[email protected] + slimsearch 2.3.0,中英双语(zh-CN 在 /,en-US 在 /en/)。
一、customFields formatter 崩溃(split is not a function)
现象
启用 slimsearch 并配上 customFields(分类/标签)后,搜索时控制台报:
n[f.value].split is not a function搜索结果根本出不来。
根因
@vuepress/plugin-slimsearch 在 node 端把 customFields 的 formatter 通过 getFullLocaleConfig / deepAssign 合并默认 locale。当你写成对象形式时:
formatter: {
"/": "分类: $content",
"/en/": "Category: $content",
}deepAssign 会把这个字符串当成可枚举对象遍历,每个字符的下标变成 key,值变成单字符。结果 "分类: $content" 被破坏成 {0:"分", 1:"类", ...} 这样的 char-index 对象。客户端再对它调 .split("$content") 就抛 split is not a function。
这不是配置写错,是插件 + @vuepress/helper 那套 locale 合并对字符串值的处理 bug。
改法
在 theme.ts 里把 search: true 换成显式的 slimsearch 配置,formatter 用显式字符串对象形式(绕开被破坏的合并路径):
slimsearch: {
indexContent: true, // 后面改成 false,见第三节
customFields: [
{
getter: (page) => page.frontmatter.category,
formatter: {
"/": "分类: $content",
"/en/": "Category: $content",
},
},
{
getter: (page) => page.frontmatter.tag,
formatter: {
"/": "标签: $content",
"/en/": "Tag: $content",
},
},
],
},关键点:formatter 走的是 Record<string,string>(locale → 字符串),$content 会被替换成 getter 返回值。客户端 SearchResult 组件按当前 routeLocale 取对应字符串,再 split("$content") 拼前后缀。
验证
中文站搜 证书 / 运维:结果正常,customField 显示「标签: 证书」「分类: 运维」,控制台无 error。
二、搜索结果太多
现象
搜一个词出来一大堆:the → 31 页 438 行,证书 → 3 页 35 行。
根因
indexContent: true 会把整篇正文都建索引。而 slimsearch 插件的 SearchResult 组件对每个命中页面会列出所有命中段落,源码里渲染循环 n.map(...) 没有 cap(数量上限)。所以正文里出现过的词每个段落都会刷出来。
theme-hope 直接透传 SlimSearchPluginOptions,不暴露 combineWith / 每页命中上限 / prefix / fuzzy 这些 slimsearch 原生参数(SlimSearchPluginOptions 只有 indexContent/sortStrategy/indexOptions(tokenize/processTerm)/customFields/filter 等)。所以没有「既要正文能搜又要结果少」的中间档。
改法
slimsearch: {
indexContent: false, // 只索引标题、各级小标题、摘要、分类/标签
...
}实测对比(英文 /en/)
| 搜索词 | indexContent:true | indexContent:false |
|---|---|---|
the | 31 页 / 438 行 | 17 页 / 30 行 |
server | 18 页 / 68 行 | 9 页 / 13 行 |
Docker | 4 页 / 42 行 | 3 页 / 7 行 |
Nginx | 2 页 / 3 行 | 0 页(正文独有词会搜不到) |
中文 /:证书 3/35 → 2/9,的 32/223 → 10/13。命中行数降约 80%。
代价
只出现在正文、不在标题/摘要里的词(如 Nginx)会搜不到。这是 precision vs recall 的权衡,得按博客内容自己定。默认值就是 false,也是 VuePress 博客的常规做法。
若以后想要「正文可搜 + 结果收敛」,要么自己 fork 插件加 cap,要么换搜索方案,当前插件配置做不到。
三、拼音输入法回车误跳转
现象
用拼音输入法输入 aws,不想切英文键盘,直接按回车把字母 aws 作为原文提交。结果直接跳转到第一条搜索结果。
根因
@vuepress/plugin-slimsearch 客户端三处 e.key === "Enter" 处理(SearchResult-*.js 一处、client/config.js 两处)都没有判断输入法组合状态:
// 命中回车就跳到当前选中结果
if (e.key === "Enter") {
const result = K.value.contents[U.value];
...
}输入法组合中按回车,本意是「结束候选提交原文」,但浏览器会同时发出一个 keydown,且该事件 isComposing 为 true(老浏览器用 keyCode === 229)。插件没拦,就把这个「提交原文的回车」当成「确认导航的回车」了。
改法
在 client.ts 的 defineClientConfig 里加 setup() 钩子,在 window 捕获阶段拦组合中的按键:
export default defineClientConfig({
enhance({ app }) {
app.component("VisitorCounter", VisitorCounter);
},
setup() {
// 输入法(如拼音)组合期间,阻止 Enter / 方向键等触发搜索导航或表单提交,
// 这样用拼音输入 "aws" 后直接回车是把字母作为原文提交,而不是跳转到第一条结果。
window.addEventListener(
"keydown",
(event) => {
if (event.isComposing || event.keyCode === 229) {
event.stopPropagation();
event.stopImmediatePropagation();
}
},
true, // 捕获阶段,抢在插件的冒泡监听器之前
);
},
rootComponents: [VisitorCounterInjector],
});要点
- 必须用捕获阶段(第三个参数
true)。插件的回车监听在冒泡阶段,只有捕获阶段先于它执行,stopImmediatePropagation才能把事件拦死在 window 层、根本不让它传播到插件。 - 同时
stopPropagation+stopImmediatePropagation:前者阻止冒泡/捕获继续,后者阻止同一目标上其它监听器。 - 判断条件
event.isComposing || event.keyCode === 229:前者是现代标准,后者是老 Safari/iOS 的兼容写法。
验证
- 正常回车(非组合)实测仍能跳转:
证书→ 跳到文章页,说明守卫没有误伤正常搜索导航。 - 组合态那一路(
isComposing: true)按代码逻辑确认:守卫命中后stopPropagation,插件导航 handler 拿不到事件。isComposing守卫是这类输入法问题的通行做法。
总结
三件事对应三个层次的问题:插件 locale 合并的字符串处理 bug、插件不暴露命中上限导致结果爆炸、客户端不判断输入法组合状态。前两个靠改配置绕开,第三个靠在 client 捕获阶段加一道 IME 守卫兜住。改完之后中文、英文两套站点的搜索都正常了,拼音输入法也不会再误跳转。
- 配置:
src/.vuepress/theme.ts(slimsearch块) - 客户端钩子:
src/.vuepress/client.ts(setup()IME 守卫) - 插件源(只读参考):
node_modules/@vuepress/plugin-slimsearch/dist/client/config.js:SearchBox / SearchModal,两处 EnterSearchResult-*.js:结果渲染 + 一处 Enter 导航node/index.d.ts:SlimSearchPluginOptions字段定义
如果以后想要正文可搜且结果收敛,考虑 fork 插件在 SearchResult 渲染循环加 n.slice(0, maxPerRow) 之类 cap,或接入 combineWith: "AND"。indexContent: false 下正文独有词搜不到,留意读者反馈,必要时再权衡改回。
