Backstage Search 深度定制指南:自定义 Search API、索引字段与搜索结果渲染
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
搜索是 Backstage 开发者门户中用户高频使用的基础能力,而开箱即用的默认实现往往无法覆盖所有定制诉求。本指南以仓库内 docs/features/search/how-to-guides.md 为核心骨架,面向新前端系统(New Frontend System),系统讲解四条高级定制路线:自定义SearchApi实现、通过 collator 的entityTransformer/documentTransformer定制 Software Catalog 与 TechDocs 的索引字段、用统一主题定制搜索结果高亮样式,以及用SearchResultListItemBlueprint扩展渲染搜索结果项。读完本文,你将能够在自己的 Backstage 应用中独立完成上述全部定制,并理解其背后的源码实现原理。
本文面向新前端系统(新 Backstage 应用默认启用)。如果你的应用仍在使用旧前端系统,请阅读对应的旧版指南。
一、认识 Search 插件与默认的 SearchApi
Search 插件默认提供并注册了一个核心 API——SearchApi,它的职责是负责与search-backend通信、查询搜索结果。
SearchApi是一个纯 TypeScript 接口,定义在 plugins/search-react/src/api.ts:
export interface SearchApi { query( query: SearchQuery, options?: { signal?: AbortSignal }, ): Promise<SearchResultSet>; }它只要求实现一个query方法:传入SearchQuery(含 term、filters、types、pageCursor 等查询参数,可选AbortSignal用于取消请求),返回SearchResultSet。同文件中还提供了MockSearchApi,可直接用于测试与 Storybook 场景。
默认实现是 plugins/search/src/apis.ts 中的SearchClient,其核心逻辑如下:
export class SearchClient implements SearchApi { private readonly discoveryApi: DiscoveryApi; private readonly fetchApi: FetchApi; constructor(options: { discoveryApi: DiscoveryApi; fetchApi: FetchApi }) { this.discoveryApi = options.discoveryApi; this.fetchApi = options.fetchApi; } async query( query: SearchQuery, options?: { signal?: AbortSignal }, ): Promise<SearchResultSet> { const queryString = qs.stringify(query); const url = `${await this.discoveryApi.getBaseUrl( 'search', )}/query?${queryString}`; const response = await this.fetchApi.fetch(url, { signal: options?.signal, }); if (!response.ok) { throw await ResponseError.fromResponse(response); } return response.json(); } }可以看到,默认实现依赖DiscoveryApi解析 search 后端的 base URL,依赖FetchApi发起带凭据的请求,并通过qs序列化查询参数。这为自定义实现提供了一个清晰的范本:你需要自行处理请求地址、认证与错误转换。
二、自定义 SearchApi 实现
当你需要对接自己的搜索后端、或对默认查询行为做深度改造时,可以按两步实现自己的SearchApi:
第一步:按需实现SearchApi接口。
export class SearchClient implements SearchApi { // your implementation }注意:SearchClient只是一个类名示例,你可以命名为任何你喜欢的名字;关键是实现query方法并满足SearchApi的类型契约。你可以参考默认实现中ResponseError.fromResponse(response)的错误处理方式(来自@backstage/errors),保证接口契约一致。
第二步:用自定义 API 扩展覆盖默认扩展。
默认的searchApi扩展在 plugins/search/src/alpha.tsx 中定义,它通过ApiBlueprint.make注册,使用searchApiRef作为 API 引用,factory里直接new SearchClient({ discoveryApi, fetchApi }):
export const searchApi = ApiBlueprint.make({ params: defineParams => defineParams({ api: searchApiRef, deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef }, factory: ({ discoveryApi, fetchApi }) => new SearchClient({ discoveryApi, fetchApi }), }), });要替换它,只需用createApiExtension创建自定义 API 扩展并安装到应用中。关于如何创建与安装自定义 API 扩展的完整说明,参见 Utility APIs 文档;其中涵盖了创建、消费、配置与测试 Utility API 的全部细节,例如用@backstage/frontend-test-utils的mockApis对核心 Utility API 打桩。
三、定制 Software Catalog 或 TechDocs 索引字段
搜索索引的内容由"collator(采集器)"决定。当你希望控制进入搜索索引的数据、或针对特定kind定制数据时,可以通过向DefaultCatalogCollatorFactory传入entityTransformer回调实现;DefaultTechDocsCollatorFactory也支持同样的行为。你既可以简单修改默认行为,也可以编写一个全新的文档(但需遵循必要的基础结构)。
重要限制:
authorization和location字段无法通过entityTransformer修改,location只能通过locationTemplate修改。
3.1 Catalog collator 的 entityTransformer
以下示例位于packages/backend/src/plugins/search.ts:
const catalogEntityTransformer: CatalogCollatorEntityTransformer = ( entity: Entity, ) => { if (entity.kind === 'SomeKind') { return { // customize here output for 'SomeKind' kind }; } return { // and customize default output ...defaultCatalogCollatorEntityTransformer(entity), text: 'my super cool text', }; }; indexBuilder.addCollator({ collator: DefaultCatalogCollatorFactory.fromConfig(env.config, { discovery: env.discovery, tokenManager: env.tokenManager, /* highlight-add-next-line */ entityTransformer: catalogEntityTransformer, }), });其中defaultCatalogCollatorEntityTransformer是仓库内置的默认转换器,实现在 plugins/search-backend-module-catalog/src/collators/defaultCatalogCollatorEntityTransformer.ts,它输出的默认文档结构为:
{ title: entity.metadata.title ?? entity.metadata.name, text: getDocumentText(entity), // description、用户/组的 displayName、用户 email 拼接 componentType: entity.spec?.type?.toString() || 'other', type: entity.spec?.type?.toString() || 'other', namespace: entity.metadata.namespace || 'default', kind: entity.kind, lifecycle: (entity.spec?.lifecycle as string) || '', owner: (entity.spec?.owner as string) || '', }因此"在原默认结构上做增强"(例如示例中的...defaultCatalogCollatorEntityTransformer(entity), text: 'my super cool text')是最稳妥的写法——你既继承了全部默认字段,又覆盖了text的取值。
从源码看,DefaultCatalogCollatorFactory(见 plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts)的entityTransformer默认值就是defaultCatalogCollatorEntityTransformer,且在execute()中通过catalog.queryEntities分批(batchSize)游标式拉取实体,对每个实体执行:
yield { ...this.entityTransformer(entity), authorization: { resourceRef: stringifyEntityRef(entity), }, location: this.applyArgsToFormat(this.locationTemplate, { namespace: ..., kind: ..., name: ..., }), };这也印证了文档中的限制说明:authorization(基于实体 ref 的权限引用)与location(基于locationTemplate生成)是在 transformer 之外由工厂强制写入的,因此 transformer 无法触碰它们。
3.2 TechDocs collator 的 entity 与 document 转换器
TechDocs collator 更进一步,提供了两个转换钩子:entityTransformer负责将 Catalog 实体转换为文档骨架,documentTransformer负责处理 TechDocs 生成器产出的 mkdocs 搜索索引文档(MkSearchIndexDoc):
const techDocsEntityTransformer: TechDocsCollatorEntityTransformer = ( entity: Entity, ) => { return { // add more fields to the index tags: entity.metadata.tags, }; }; const techDocsDocumentTransformer: TechDocsCollatorDocumentTransformer = ( doc: MkSearchIndexDoc, ) => { return { // add more fields to the index bost: doc.boost, }; }; indexBuilder.addCollator({ collator: DefaultTechDocsCollatorFactory.fromConfig(env.config, { discovery: env.discovery, tokenManager: env.tokenManager, /* highlight-add-next-line */ entityTransformer: techDocsEntityTransformer, /* highlight-add-next-line */ documentTransformer: techDocsDocumentTransformer, }), });(示例中bost: doc.boost为示意写法,实际字段名请以你的索引结构为准。)仓库内置的默认实现见 defaultTechDocsCollatorEntityTransformer.ts(输出kind、namespace、annotations、name、title、text、componentType、type、lifecycle、owner、path等字段)与 defaultTechDocsCollatorDocumentTransformer.ts。DefaultTechDocsCollatorFactory的完整可配置项(见 DefaultTechDocsCollatorFactory.ts)还包括locationTemplate(默认/docs/:namespace/:kind/:name/:path)、parallelismLimit、legacyPathCasing、entityFilterFunction、customCatalogApiFilters等。
3.3 可配置项与默认值
Catalog collator 的运行时配置读取逻辑位于 plugins/search-backend-module-catalog/src/collators/config.ts,配置键为search.collators.catalog,默认值如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
schedule.frequency | { minutes: 10 } | 采集任务执行频率 |
schedule.timeout | { minutes: 15 } | 单次采集超时时间 |
schedule.initialDelay | { seconds: 3 } | 启动后的初始延迟 |
locationTemplate | /catalog/:namespace/:kind/:name | 生成location字段的模板,占位符会被替换并统一转为小写 |
filter | 无(不过滤) | Catalog 实体过滤条件(EntityFilterQuery) |
batchSize | 500 | 每次queryEntities拉取的实体数量 |
需要说明的是,示例代码中使用的env.config/env.discovery/env.tokenManager是旧版后端插件写法;在新后端系统中应改用auth、catalog等核心服务注入(DefaultCatalogCollatorFactoryOptions当前接受auth: AuthService与catalog: CatalogService),实际接入方式以你的 Backstage 版本为准。
四、自定义搜索结果高亮样式
默认情况下,搜索结果中匹配词的高亮样式取自浏览器对<mark>HTML 标签的默认样式。如需定制高亮效果,可以遵循 Backstage 的自定义应用 UI 指南,创建带自定义样式的主题覆盖。
例如,使用统一主题(Unified Theme)方法,以下配置可以让高亮词变为"加粗 + 下划线":
import { createBaseThemeOptions, createUnifiedTheme, palettes, UnifiedTheme, } from '@backstage/theme'; export const myLightTheme: UnifiedTheme = createUnifiedTheme({ ...createBaseThemeOptions({ palette: palettes.light, }), defaultPageTheme: 'home', components: { /** @ts-ignore This is temporarily necessary until MUI V5 transition is completed. */ BackstageHighlightedSearchResultText: { styleOverrides: { highlight: { color: 'inherit', backgroundColor: 'inherit', fontWeight: 'bold', textDecoration: 'underline', }, }, }, }, });关键点是components.BackstageHighlightedSearchResultText.styleOverrides.highlight这一层级——它精确命中搜索结果高亮组件的内部样式槽。color与backgroundColor设为inherit表示沿用当前主题色,而fontWeight与textDecoration决定了高亮词的视觉强调方式,你可以按需替换为任意 CSS 属性。
自定义主题在新前端系统中以扩展(extension)的形式安装。安装自定义主题的详细方法见扩展配置文档。
五、使用扩展渲染搜索结果
搜索结果扩展(Search result extensions)让你能够定制用于渲染搜索结果项的组件。你可以提供自己的搜索结果项扩展,也可以直接使用插件包提供的现成扩展。
5.1 提供搜索结果列表项扩展
在新前端系统中,搜索结果列表项扩展通过@backstage/plugin-search-react/alpha导出的SearchResultListItemBlueprint创建:
import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; export const YourSearchResultListItem = SearchResultListItemBlueprint.make({ name: 'your-result-item', params: { predicate: result => result.type === 'YOUR_RESULT_TYPE', component: async () => { const { YourSearchResultListItem } = await import('./components'); return YourSearchResultListItem; }, }, });从源码看(SearchResultListItemBlueprint.tsx),该 Blueprint 的params支持三个字段:
component(必填):异步返回结果项组件;predicate(可选):判断某条结果是否应由本扩展渲染,默认返回true,即渲染所有类型的结果;icon(可选):结果项的图标。
该 Blueprint 会自动挂载到page:search的items输入上,并支持noTrack配置(默认false,用于控制是否上报分析事件),组件本身由ExtensionBoundary包裹以保证独立性与容错。
扩展创建后,从插件的 alpha 入口导出,插件安装时会被自动发现。仓库内已有现成范例:Catalog 插件在 plugins/catalog/src/alpha/searchResultItems.tsx 中即通过该 Blueprint 提供自己的搜索结果项。
5.2 从应用侧提供扩展
如果你需要在应用侧(而非插件)提供搜索结果列表项扩展,需要将它包在前端模块(frontend module)中,再传给createApp:
import { createFrontendModule } from '@backstage/frontend-plugin-api'; import { YourSearchResultListItem } from './YourSearchResultListItem'; export const searchCustomizations = createFrontendModule({ pluginId: 'search', extensions: [YourSearchResultListItem], });import { createApp } from '@backstage/frontend-defaults'; import { searchCustomizations } from './search/searchModule'; const app = createApp({ features: [searchCustomizations], }); export default app.createRoot();注意这里pluginId: 'search'必须与目标插件(search 插件)的 ID 一致,扩展才会被正确附加到搜索页面上。
5.3 搜索结果项排序规则
当安装了多个搜索结果列表项扩展时,搜索页面会按以下规则渲染:
- 页面依据各扩展的
predicate函数对结果进行匹配,第一个 predicate 命中的扩展负责渲染该条结果; - 没有 predicate 的扩展充当兜底渲染器(fallback renderer),应放在最后,以保证它能接住所有未被特定扩展匹配的结果。
因此,在组织扩展顺序时,应将带predicate的专用结果项放在前面,将无 predicate 的通用兜底项放在最后。
另外,还有其他更细分的搜索结果布局组件同样接受结果项扩展,可参考SearchResultList与SearchResultGroup两个组件的 Storybook 示例(搜索"with result item extensions"相关 story)了解如何在分组列表中注入自定义结果项。
六、小结
本指南围绕 Backstage Search 的四个高级定制点给出了完整实践路径,并提供了源码级依据:
| 定制诉求 | 核心入口 | 源码依据 |
|---|---|---|
| 自定义搜索后端/查询行为 | 实现SearchApi+createApiExtension覆盖默认扩展 | apis.ts、alpha.tsx |
| 定制 Catalog/TechDocs 索引字段 | collator 的entityTransformer/documentTransformer | DefaultCatalogCollatorFactory.ts、DefaultTechDocsCollatorFactory.ts |
| 定制高亮样式 | 统一主题的BackstageHighlightedSearchResultText | theming 文档 |
| 定制结果项渲染 | SearchResultListItemBlueprint+ frontend module | SearchResultListItemBlueprint.tsx |
需要注意的记忆点:authorization与location字段由 collator 工厂在转换器之外强制写入,不可通过entityTransformer修改;location仅可通过locationTemplate调整。希望这篇指南能帮助你在不修改 Backstage 框架代码的前提下,把搜索能力打磨成完全贴合自己门户的产品。
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考