chore: improve plugin-search code comments

This commit is contained in:
pengzhanbo 2026-03-08 16:16:04 +08:00
parent 7751e4c798
commit 552f0f5c32
19 changed files with 553 additions and 10 deletions

View File

@ -1,2 +1,9 @@
/**
* Composables Entry Point
*
*
*
* @module plugin-search/client/composables
*/
export * from './locale.js'
export * from './searchIndex.js'

View File

@ -1,8 +1,20 @@
/**
* Locale Composable for Search Plugin
*
*
*
* @module plugin-search/client/composables/locale
*/
import type { ComputedRef, MaybeRef } from 'vue'
import type { SearchBoxLocales, SearchLocaleOptions } from '../../shared/index.js'
import { computed, toRef } from 'vue'
import { useRouteLocale } from 'vuepress/client'
/**
* Default locale configuration for search.
*
*
*/
const defaultLocales: SearchBoxLocales = {
'/': {
placeholder: 'Search',
@ -21,6 +33,17 @@ const defaultLocales: SearchBoxLocales = {
},
}
/**
* Get locale configuration for the current route.
*
*
*
* @param locales - Locale configuration object /
* @returns Computed ref to the current locale settings /
* @example
* const locale = useLocale(locales)
* console.log(locale.value.placeholder) // 'Search' or localized value
*/
export function useLocale(locales: MaybeRef<SearchBoxLocales>): ComputedRef<Partial<SearchLocaleOptions>> {
const localesRef = toRef(locales)
const routeLocale = useRouteLocale()

View File

@ -1,13 +1,44 @@
/**
* Search Index Composable for Search Plugin
*
*
*
* @module plugin-search/client/composables/searchIndex
*/
import type { ShallowRef } from 'vue'
import { searchIndex } from '@internal/minisearchIndex'
import { shallowRef } from 'vue'
declare const __VUE_HMR_RUNTIME__: Record<string, any>
/**
* Type definition for search index data.
*
*
*
* Maps locale paths to functions that load the corresponding search index.
*
*
*/
type SearchIndexData = Record<string, () => Promise<{ default: string }>>
/**
* Reactive reference to the search index data.
*
*
*/
const searchIndexData = shallowRef<SearchIndexData>(searchIndex)
/**
* Get the search index data for all locales.
*
*
*
* @returns Shallow ref to the search index data /
* @example
* const indexData = useSearchIndex()
* const localeIndex = await indexData.value['/zh/']()
*/
export function useSearchIndex(): ShallowRef<SearchIndexData> {
return searchIndexData
}

View File

@ -1,3 +1,15 @@
/**
* VuePress Client Configuration for Search Plugin
*
* VuePress
*
* Registers the SearchBox component globally and provides locale and option
* configuration to the search component.
*
* SearchBox
*
* @module plugin-search/client/config
*/
import type { ClientConfig } from 'vuepress/client'
import type { SearchBoxLocales, SearchOptions } from '../shared/index.js'
import { h } from 'vue'

View File

@ -1,3 +1,15 @@
/**
* VuePress Search Plugin - Client Side Entry
*
* VuePress -
*
* Exports the SearchBox component and search index composable for use in
* VuePress theme components.
*
* SearchBox VuePress 使
*
* @module plugin-search/client
*/
import SearchBox from './components/Search.vue'
import { useSearchIndex } from './composables/index.js'

View File

@ -1 +1,8 @@
/**
* Utilities Entry Point
*
*
*
* @module plugin-search/client/utils
*/
export * from './lru.js'

View File

@ -1,14 +1,63 @@
// adapted from https://stackoverflow.com/a/46432113/11613622
/**
* LRU (Least Recently Used) Cache Implementation
*
* LRU使
*
* Adapted from https://stackoverflow.com/a/46432113/11613622
*
* @module plugin-search/client/utils/lru
*/
/**
* Generic LRU Cache implementation using Map.
*
* 使 Map LRU
*
* Automatically evicts the least recently used item when the cache reaches
* its maximum size.
*
* 使
*
* @template K - Key type /
* @template V - Value type /
* @example
* const cache = new LRUCache<string, number>(3)
* cache.set('a', 1)
* cache.set('b', 2)
* cache.set('c', 3)
* cache.set('d', 4) // 'a' is evicted
* cache.get('b') // returns 2, 'b' becomes most recent
*/
export class LRUCache<K, V> {
/** Maximum number of items in the cache / 缓存中的最大项数 */
private max: number
/** Internal Map storage / 内部 Map 存储 */
private cache: Map<K, V>
/**
* Create a new LRU Cache instance.
*
* LRU
*
* @param max - Maximum cache size (default: 10) / 10
*/
constructor(max: number = 10) {
this.max = max
this.cache = new Map<K, V>()
}
/**
* Get a value from the cache.
*
*
*
* Accessing an item moves it to the end (most recently used position).
*
* 访使
*
* @param key - Cache key /
* @returns Cached value or undefined if not found / undefined
*/
get(key: K): V | undefined {
const item = this.cache.get(key)
if (item !== undefined) {
@ -19,6 +68,19 @@ export class LRUCache<K, V> {
return item
}
/**
* Set a value in the cache.
*
*
*
* If the key already exists, it is moved to the end. If the cache is full,
* the oldest item is evicted.
*
*
*
* @param key - Cache key /
* @param val - Value to cache /
*/
set(key: K, val: V): void {
// refresh key
if (this.cache.has(key))
@ -29,10 +91,22 @@ export class LRUCache<K, V> {
this.cache.set(key, val)
}
/**
* Get the first (oldest) key in the cache.
*
*
*
* @returns The oldest key or undefined if cache is empty / undefined
*/
first(): K | undefined {
return this.cache.keys().next().value
}
/**
* Clear all items from the cache.
*
*
*/
clear(): void {
this.cache.clear()
}

View File

@ -1,3 +1,14 @@
/**
* VuePress Search Plugin - Node Side Entry
*
* VuePress - Node
*
* Exports the search plugin and search index preparation utilities.
*
*
*
* @module plugin-search/node
*/
export * from '../shared/index.js'
export { prepareSearchIndex } from './prepareSearchIndex.js'
export { searchPlugin } from './searchPlugin.js'

View File

@ -1,6 +1,17 @@
/** 德语 */
/**
* German locale configuration for search plugin.
*
*
*
* @module plugin-search/node/locales/de
*/
import type { SearchLocaleOptions } from '../../shared/index.js'
/**
* German search locale strings.
*
*
*/
export const deSearchLocale: Partial<SearchLocaleOptions> = {
placeholder: 'Dokumente durchsuchen',
resetButtonTitle: 'Suche zurücksetzen',

View File

@ -1,5 +1,17 @@
/**
* English locale configuration for search plugin.
*
*
*
* @module plugin-search/node/locales/en
*/
import type { SearchLocaleOptions } from '../../shared/index.js'
/**
* English search locale strings.
*
*
*/
export const enSearchLocale: Partial<SearchLocaleOptions> = {
placeholder: 'Search',
resetButtonTitle: 'Reset search',

View File

@ -1,6 +1,17 @@
/** 法语 */
/**
* French locale configuration for search plugin.
*
*
*
* @module plugin-search/node/locales/fr
*/
import type { SearchLocaleOptions } from '../../shared/index.js'
/**
* French search locale strings.
*
*
*/
export const frSearchLocale: Partial<SearchLocaleOptions> = {
placeholder: 'Rechercher dans la documentation',
resetButtonTitle: 'Réinitialiser la recherche',

View File

@ -1,7 +1,14 @@
/**
*
* /zh/ /en/ AI
* issue
* Multi-language presets for search plugin.
*
*
*
* Language presets other than /zh/ and /en/ are generated by AI and may not be accurate.
* If you find any errors, please submit an issue.
*
* /zh/ /en/ AI issue
*
* @module plugin-search/node/locales
*/
import type { DefaultLocaleInfo } from '@vuepress/helper'
import type { SearchLocaleOptions } from '../../shared/index.js'
@ -13,6 +20,15 @@ import { ruSearchLocale } from './ru.js'
import { zhTwSearchLocale } from './zh-tw.js'
import { zhSearchLocale } from './zh.js'
/**
* Default locale configurations for search plugin.
*
*
*
* Maps language codes to their respective locale strings.
*
*
*/
export const SEARCH_LOCALES: DefaultLocaleInfo<Partial<SearchLocaleOptions>> = [
[['en', 'en-US'], enSearchLocale],
[['zh', 'zh-CN', 'zh-Hans', 'zh-Hant'], zhSearchLocale],

View File

@ -1,6 +1,17 @@
/** 日语 */
/**
* Japanese locale configuration for search plugin.
*
*
*
* @module plugin-search/node/locales/ja
*/
import type { SearchLocaleOptions } from '../../shared/index.js'
/**
* Japanese search locale strings.
*
*
*/
export const jaSearchLocale: Partial<SearchLocaleOptions> = {
placeholder: 'ドキュメントを検索',
resetButtonTitle: '検索をリセット',

View File

@ -1,6 +1,17 @@
/** 俄语 */
/**
* Russian locale configuration for search plugin.
*
*
*
* @module plugin-search/node/locales/ru
*/
import type { SearchLocaleOptions } from '../../shared/index.js'
/**
* Russian search locale strings.
*
*
*/
export const ruSearchLocale: Partial<SearchLocaleOptions> = {
placeholder: 'Поиск по документации',
resetButtonTitle: 'Сбросить поиск',

View File

@ -1,6 +1,17 @@
/** 繁体中文 */
/**
* Traditional Chinese (Taiwan) locale configuration for search plugin.
*
*
*
* @module plugin-search/node/locales/zh-tw
*/
import type { SearchLocaleOptions } from '../../shared/index.js'
/**
* Traditional Chinese (Taiwan) search locale strings.
*
*
*/
export const zhTwSearchLocale: Partial<SearchLocaleOptions> = {
placeholder: '搜尋文件',
resetButtonTitle: '重設搜尋',

View File

@ -1,5 +1,17 @@
/**
* Simplified Chinese locale configuration for search plugin.
*
*
*
* @module plugin-search/node/locales/zh
*/
import type { SearchLocaleOptions } from '../../shared/index.js'
/**
* Simplified Chinese search locale strings.
*
*
*/
export const zhSearchLocale: Partial<SearchLocaleOptions> = {
placeholder: '搜索文档',
resetButtonTitle: '重置搜索',

View File

@ -1,26 +1,71 @@
/**
* Search Index Preparation Module
*
*
*
* This module handles the creation, update, and management of MiniSearch indexes
* for VuePress pages, supporting multiple locales and hot module replacement.
*
* VuePress MiniSearch
*
*
* @module plugin-search/node/prepareSearchIndex
*/
import type { App, Page } from 'vuepress/core'
import type { SearchOptions, SearchPluginOptions } from '../shared/index.js'
import MiniSearch from 'minisearch'
import pMap from 'p-map'
import { colors, logger } from 'vuepress/utils'
/**
* Options for search index preparation.
*
*
*/
export interface SearchIndexOptions {
/** VuePress application instance / VuePress 应用实例 */
app: App
/** MiniSearch configuration options / MiniSearch 配置选项 */
searchOptions: SearchOptions
/** Function to filter searchable pages / 过滤可搜索页面的函数 */
isSearchable: SearchPluginOptions['isSearchable']
}
/**
* Internal index object structure for MiniSearch.
*
* MiniSearch
*/
interface IndexObject {
/** Unique identifier for the indexed item / 索引项的唯一标识符 */
id: string
/** Text content for searching / 用于搜索的文本内容 */
text: string
/** Title of the section / 章节标题 */
title: string
/** Parent titles hierarchy / 父级标题层级 */
titles: string[]
}
/** Directory path for storing search index files / 存储搜索索引文件的目录路径 */
const SEARCH_INDEX_DIR = 'internal/minisearchIndex/'
/** Map of locale paths to their MiniSearch instances / 语言路径到 MiniSearch 实例的映射 */
const indexByLocales = new Map<string, MiniSearch<IndexObject>>()
/** Cache for index objects by file path / 按文件路径缓存索引对象 */
const indexCache = new Map<string, IndexObject[]>()
/**
* Get or create a MiniSearch index for a specific locale.
*
* MiniSearch
*
* @param locale - Locale path (e.g., '/', '/zh/') /
* @param lang - Language code for tokenization /
* @param options - Search index options /
* @returns MiniSearch instance for the locale / MiniSearch
*/
function getIndexByLocale(locale: string, lang: string, options: SearchIndexOptions['searchOptions']) {
let index = indexByLocales.get(locale)
if (!index) {
@ -38,6 +83,14 @@ function getIndexByLocale(locale: string, lang: string, options: SearchIndexOpti
return index
}
/**
* Get or create an index cache for a specific file path.
*
*
*
* @param filepath - File path to get cache for /
* @returns Array of index objects for the file /
*/
function getIndexCache(filepath: string) {
let index = indexCache.get(filepath)
if (!index) {
@ -47,10 +100,39 @@ function getIndexCache(filepath: string) {
return index
}
/**
* Write a placeholder search index file for development mode.
*
*
*
* This is used to provide an empty index before the actual index is prepared,
* preventing errors when the search component loads before indexing completes.
*
*
*
*
* @param app - VuePress application instance / VuePress
*/
export async function prepareSearchIndexPlaceholder(app: App) {
await app.writeTemp(`${SEARCH_INDEX_DIR}index.js`, 'export const searchIndex = {}')
}
/**
* Prepare search indexes for all pages in the VuePress application.
*
* VuePress
*
* This function indexes all pages concurrently, creating separate MiniSearch
* instances for each locale, and writes the indexes to temporary files.
*
* MiniSearch
*
*
* @param options - Search index preparation options /
* @param options.app - VuePress application instance / VuePress
* @param options.isSearchable - Function to filter searchable pages /
* @param options.searchOptions - MiniSearch configuration / MiniSearch
*/
export async function prepareSearchIndex({
app,
isSearchable,
@ -78,6 +160,17 @@ export async function prepareSearchIndex({
}
}
/**
* Handle search index update when a page is modified.
*
*
*
* @param filepath - Path of the modified file relative to project root /
* @param options - Search index preparation options /
* @param options.app - VuePress application instance / VuePress
* @param options.isSearchable - Function to filter searchable pages /
* @param options.searchOptions - MiniSearch configuration / MiniSearch
*/
export async function onSearchIndexUpdated(
filepath: string,
{
@ -97,6 +190,17 @@ export async function onSearchIndexUpdated(
}
}
/**
* Handle search index update when a page is removed.
*
*
*
* @param filepath - Path of the removed file relative to project root /
* @param options - Search index preparation options /
* @param options.app - VuePress application instance / VuePress
* @param options.isSearchable - Function to filter searchable pages /
* @param options.searchOptions - MiniSearch configuration / MiniSearch
*/
export async function onSearchIndexRemoved(
filepath: string,
{
@ -119,6 +223,19 @@ export async function onSearchIndexRemoved(
}
}
/**
* Write all search indexes to temporary files.
*
*
*
* Creates separate JavaScript files for each locale's search index,
* enabling lazy loading of indexes in the client.
*
* JavaScript
*
*
* @param app - VuePress application instance / VuePress
*/
async function writeTemp(app: App) {
const records: string[] = []
const promises: Promise<string>[] = []
@ -145,6 +262,21 @@ async function writeTemp(app: App) {
await Promise.all(promises)
}
/**
* Index a single page for search.
*
*
*
* Extracts content from the page, splits it into sections based on headings,
* and adds each section to the appropriate locale's MiniSearch index.
*
*
* MiniSearch
*
* @param page - VuePress page object / VuePress
* @param options - MiniSearch options / MiniSearch
* @param isSearchable - Function to check if page should be indexed /
*/
async function indexFile(page: Page, options: SearchIndexOptions['searchOptions'], isSearchable: SearchPluginOptions['isSearchable']) {
if (!page.filePath || page.frontmatter?.search === false)
return
@ -194,13 +326,27 @@ ${page.contentRendered}`
}
}
/** Regex pattern for matching heading tags / 匹配标题标签的正则表达式 */
// eslint-disable-next-line regexp/no-super-linear-backtracking
const headingRegex = /<h(\d*).*?>(<a.*? href="#.*?".*?>[\s\S]*?<\/a>)<\/h\1>/gi
/** Regex pattern for extracting heading content / 提取标题内容的正则表达式 */
// eslint-disable-next-line regexp/no-super-linear-backtracking
const headingContentRegex = /<a.*? href="#(.*?)".*?><span>([\s\S]*?)<\/span><\/a>/i
/** Regex pattern for ignoring template content / 忽略模板内容的正则表达式 */
const ignoreHeadingRegex = /<template[^>]*>[\s\S]*<\/template>/gi
/**
* Splits HTML into sections based on headings
* Split HTML content into sections based on heading elements.
*
* HTML
*
* This generator function parses HTML content and yields section objects
* containing anchor, titles hierarchy, and searchable text.
*
* HTML
*
* @param html - HTML content to split / HTML
* @yields Section objects with anchor, titles, and text /
*/
function* splitPageIntoSections(html: string) {
const result = html.split(headingRegex)
@ -228,17 +374,41 @@ function* splitPageIntoSections(html: string) {
}
}
/**
* Extract searchable text from HTML content by removing tags.
*
* HTML
*
* @param content - HTML content / HTML
* @returns Plain text content /
*/
function getSearchableText(content: string) {
content = clearHtmlTags(content)
return content
}
/**
* Remove all HTML tags from a string.
*
* HTML
*
* @param str - String containing HTML / HTML
* @returns String with HTML tags removed / HTML
*/
function clearHtmlTags(str: string) {
str = str.replace(ignoreHeadingRegex, '')
// 移除其他所有HTML标签
return str.replace(/<[^>]*>/g, '')
}
/**
* Generate HMR (Hot Module Replacement) code for the search index.
*
* HMR
*
* @param m - Module name /
* @returns HMR code string / HMR
*/
function genHmrCode(m: string) {
const func = `update${m[0].toUpperCase()}${m.slice(1)}`
return `

View File

@ -8,6 +8,36 @@ import { onSearchIndexRemoved, onSearchIndexUpdated, prepareSearchIndex, prepare
const __dirname = getDirname(import.meta.url)
/**
* Create a VuePress search plugin instance.
*
* VuePress
*
* @param options - Plugin configuration options /
* @param options.locales - Locale-specific search configurations /
* @param options.isSearchable - Function to determine if a page should be indexed /
* @param options.searchOptions - MiniSearch options / MiniSearch
* @returns VuePress plugin object / VuePress
* @example
* // Basic usage
* export default {
* plugins: [
* searchPlugin()
* ]
* }
*
* // With custom options
* export default {
* plugins: [
* searchPlugin({
* locales: {
* '/zh/': { placeholder: '搜索文档' }
* },
* isSearchable: (page) => page.path !== '/secret/'
* })
* ]
* }
*/
export function searchPlugin({
locales = {},
isSearchable,

View File

@ -1,40 +1,107 @@
/**
* Shared Types for VuePress Search Plugin
*
* VuePress
*
* This module contains type definitions shared between the node and client
* sides of the search plugin.
*
* Node
*
* @module plugin-search/shared
*/
import type { Options as MiniSearchOptions } from 'minisearch'
import type { LocaleConfig, Page } from 'vuepress/core'
/**
* Locale options for search UI strings.
*
* UI
*/
export interface SearchLocaleOptions {
/** Placeholder text for search input / 搜索输入框的占位文本 */
placeholder: string
/** Text for search button / 搜索按钮的文本 */
buttonText: string
/** Title for reset button / 重置按钮的标题 */
resetButtonTitle: string
/** Title for back/close button / 返回/关闭按钮的标题 */
backButtonTitle: string
/** Text shown when no results found / 无搜索结果时显示的文本 */
noResultsText: string
/** Footer keyboard shortcut hints / 底部键盘快捷键提示 */
footer: {
/** Text for select action / 选择操作的文本 */
selectText: string
/** ARIA label for select key / 选择键的 ARIA 标签 */
selectKeyAriaLabel: string
/** Text for navigate action / 导航操作的文本 */
navigateText: string
/** ARIA label for navigate up key / 向上导航键的 ARIA 标签 */
navigateUpKeyAriaLabel: string
/** ARIA label for navigate down key / 向下导航键的 ARIA 标签 */
navigateDownKeyAriaLabel: string
/** Text for close action / 关闭操作的文本 */
closeText: string
/** ARIA label for close key / 关闭键的 ARIA 标签 */
closeKeyAriaLabel: string
}
}
/**
* Locale configuration type for search box.
*
*
*/
export type SearchBoxLocales = LocaleConfig<SearchLocaleOptions>
/**
* Options for the search plugin.
*
*
*/
export interface SearchPluginOptions extends SearchOptions {
/** Locale-specific search configurations / 特定语言的搜索配置 */
locales?: SearchBoxLocales
/**
* Function to determine if a page should be indexed.
*
*
*
* @param page - VuePress page object / VuePress
* @returns Whether the page should be searchable /
*/
isSearchable?: (page: Page) => boolean
}
/**
* Search configuration options.
*
*
*/
export interface SearchOptions {
/**
* Whether to disable query persistence in session storage.
*
*
*
* @default false
*/
disableQueryPersistence?: boolean
/**
* MiniSearch configuration options.
*
* MiniSearch
*/
miniSearch?: {
/**
* MiniSearch instance options.
*
* MiniSearch
*
* @see https://lucaong.github.io/minisearch/modules/_minisearch_.html#options
*/
options?: Pick<
@ -42,6 +109,10 @@ export interface SearchOptions {
'extractField' | 'tokenize' | 'processTerm'
>
/**
* MiniSearch search options.
*
* MiniSearch
*
* @see https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchoptions-1
*/
searchOptions?: MiniSearchOptions['searchOptions']