Conversation
There was a problem hiding this comment.
Pull request overview
This PR optimizes the /search page’s client-side result processing by introducing an enriched result layer that caches derived fields (normalized name, parsed update timestamp, download counts) and reuses them across exact-match promotion, filtering, and sorting paths.
Changes:
- Added
EnrichedSearchResultandenrichedResultsto precompute comparison fields once per fetched result set. - Refactored exact-match promotion + platform-package filtering to operate on enriched entries.
- Updated client-side sorting to use cached derived fields and then map back to
NpmSearchResultfor rendering.
| return (rawVisibleResults.value?.objects ?? []).map(result => ({ | ||
| result, | ||
| normalizedName: result.package.name.toLowerCase(), | ||
| updatedAt: result.package.date ? Date.parse(result.package.date) : 0, | ||
| weeklyDownloads: result.downloads?.weekly ?? 0, | ||
| })) |
There was a problem hiding this comment.
enrichedResults eagerly parses package.date and reads download counts for every fetched result, even when the current sort is relevance/name and those fields are never used. This adds work on the default (relevance) path; consider computing updatedAt/download-derived fields conditionally based on the active sort key (or splitting into separate computeds so unused derived fields don’t run).
| return (rawVisibleResults.value?.objects ?? []).map(result => ({ | |
| result, | |
| normalizedName: result.package.name.toLowerCase(), | |
| updatedAt: result.package.date ? Date.parse(result.package.date) : 0, | |
| weeklyDownloads: result.downloads?.weekly ?? 0, | |
| })) | |
| return (rawVisibleResults.value?.objects ?? []).map((result): EnrichedSearchResult => { | |
| let cachedUpdatedAt: number | undefined | |
| let cachedWeeklyDownloads: number | undefined | |
| return { | |
| result, | |
| normalizedName: result.package.name.toLowerCase(), | |
| get updatedAt() { | |
| if (cachedUpdatedAt === undefined) { | |
| cachedUpdatedAt = result.package.date ? Date.parse(result.package.date) : 0 | |
| } | |
| return cachedUpdatedAt | |
| }, | |
| get weeklyDownloads() { | |
| if (cachedWeeklyDownloads === undefined) { | |
| cachedWeeklyDownloads = result.downloads?.weekly ?? 0 | |
| } | |
| return cachedWeeklyDownloads | |
| }, | |
| } | |
| }) |
| const resultsArray = computed(() => visibleResultEntries.value.map(entry => entry.result)) | ||
|
|
||
| const visibleResults = computed(() => { | ||
| const raw = rawVisibleResults.value | ||
| if (!raw) return raw | ||
|
|
||
| return { | ||
| ...raw, | ||
| objects: reordered, | ||
| objects: resultsArray.value, | ||
| } |
There was a problem hiding this comment.
resultsArray + visibleResults always allocate a new array (and a new wrapper object) on each result-set update, even when no platform filtering or exact-match reordering is applied. The previous version could return the original raw response unchanged in those cases; consider preserving that fast path (e.g., return raw directly and reuse raw.objects when entries are unmodified) to avoid extra allocations/render churn.
🔗 Linked issue
N/A
🧭 Context
The search page does several client-side transformations on fetched results before rendering them: filtering platform-specific packages, promoting exact name matches, and sorting larger fetched batches when the selected sort is not relevance-based. Those paths were repeatedly lowercasing package names and parsing package dates inside computed logic, which adds avoidable CPU work as result sets grow.
📚 Description
This change introduces a lightweight enriched search-result layer on the search page that precomputes the expensive comparison fields once per fetched result set.
Each result now carries cached derived values such as normalized package name, parsed update timestamp, and weekly download count. The existing exact-match handling, visibility pipeline, and client-side sorting logic now reuse those derived fields instead of recomputing them in multiple places. The rendered result objects and user-visible behavior stay the same; this only reduces repeated transformation work in the search page’s hot path.