Skip to content

perf(ui): precompute search result sort fields#8

Open
trivikr wants to merge 1 commit intomainfrom
precompute-fields-per-response
Open

perf(ui): precompute search result sort fields#8
trivikr wants to merge 1 commit intomainfrom
precompute-fields-per-response

Conversation

@trivikr
Copy link
Copy Markdown
Owner

@trivikr trivikr commented Mar 25, 2026

🔗 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.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 EnrichedSearchResult and enrichedResults to 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 NpmSearchResult for rendering.

Comment thread app/pages/search.vue
Comment on lines +97 to +102
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,
}))
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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
},
}
})

Copilot uses AI. Check for mistakes.
Comment thread app/pages/search.vue
Comment on lines +138 to 147
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,
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants