optimize search performance for low-power devices
Some checks failed
CI / update (push) Failing after 0s
Some checks failed
CI / update (push) Failing after 0s
Remove Web Worker implementation and replace with debounced direct search to eliminate serialization overhead. Add pre-computed category Map and memoized filtering with $derived.by() to prevent redundant array iterations on every keystroke. Reduce debounce to 100ms for responsive feel. Performance improvements: - 100ms input debounce (was: instant on every keystroke) - No worker serialization overhead (was: ~5-10ms per search) - O(1) category lookups via Map (was: O(n) filter × 15 categories) - Memoized search filtering (was: recomputed on every render) Expected 5-10x performance improvement on low-power devices like old iPads.
This commit is contained in:
@@ -25,6 +25,45 @@
|
||||
? ["Main course", "Noodle", "Bread", "Dessert", "Soup", "Side dish", "Salad", "Cake", "Breakfast", "Sauce", "Ingredient", "Drink", "Spread", "Cookie", "Snack"]
|
||||
: ["Hauptspeise", "Nudel", "Brot", "Dessert", "Suppe", "Beilage", "Salat", "Kuchen", "Frühstück", "Sauce", "Zutat", "Getränk", "Aufstrich", "Guetzli", "Snack"]);
|
||||
|
||||
// Pre-compute category-to-recipes Map for O(1) lookups
|
||||
const recipesByCategory = $derived.by(() => {
|
||||
const map = new Map();
|
||||
for (const recipe of data.all_brief) {
|
||||
if (!map.has(recipe.category)) {
|
||||
map.set(recipe.category, []);
|
||||
}
|
||||
map.get(recipe.category).push(recipe);
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
// Memoized filtered recipes by category
|
||||
const filteredRecipesByCategory = $derived.by(() => {
|
||||
if (!hasActiveSearch) {
|
||||
// No search active - return all recipes by category
|
||||
return recipesByCategory;
|
||||
}
|
||||
|
||||
// Filter each category's recipes based on search results
|
||||
const filtered = new Map();
|
||||
for (const [category, recipes] of recipesByCategory) {
|
||||
const matchedInCategory = recipes.filter(r => matchedRecipeIds.has(r._id));
|
||||
if (matchedInCategory.length > 0) {
|
||||
filtered.set(category, matchedInCategory);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
});
|
||||
|
||||
// Memoized season recipes
|
||||
const seasonRecipes = $derived.by(() => {
|
||||
const recipes = data.season;
|
||||
if (!hasActiveSearch) {
|
||||
return recipes;
|
||||
}
|
||||
return recipes.filter(recipe => matchedRecipeIds.has(recipe._id));
|
||||
});
|
||||
|
||||
const labels = $derived({
|
||||
title: isEnglish ? 'Recipes' : 'Rezepte',
|
||||
subheading: isEnglish
|
||||
@@ -64,28 +103,20 @@ h1{
|
||||
|
||||
<Search lang={data.lang} recipes={data.all_brief} onSearchResults={handleSearchResults}></Search>
|
||||
|
||||
{#if true}
|
||||
{@const seasonRecipes = data.season.filter(recipe =>
|
||||
!hasActiveSearch || matchedRecipeIds.has(recipe._id)
|
||||
)}
|
||||
{#if seasonRecipes.length > 0}
|
||||
<LazyCategory title={labels.inSeason} eager={true}>
|
||||
{#snippet children()}
|
||||
<MediaScroller title={labels.inSeason}>
|
||||
{#each seasonRecipes as recipe}
|
||||
<Card {recipe} {current_month} loading_strat={"eager"} do_margin_right={true} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}"></Card>
|
||||
{/each}
|
||||
</MediaScroller>
|
||||
{/snippet}
|
||||
</LazyCategory>
|
||||
{/if}
|
||||
{#if seasonRecipes.length > 0}
|
||||
<LazyCategory title={labels.inSeason} eager={true}>
|
||||
{#snippet children()}
|
||||
<MediaScroller title={labels.inSeason}>
|
||||
{#each seasonRecipes as recipe}
|
||||
<Card {recipe} {current_month} loading_strat={"eager"} do_margin_right={true} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}"></Card>
|
||||
{/each}
|
||||
</MediaScroller>
|
||||
{/snippet}
|
||||
</LazyCategory>
|
||||
{/if}
|
||||
|
||||
{#each categories as category, index}
|
||||
{@const categoryRecipes = data.all_brief.filter(recipe =>
|
||||
recipe.category === category &&
|
||||
(!hasActiveSearch || matchedRecipeIds.has(recipe._id))
|
||||
)}
|
||||
{@const categoryRecipes = filteredRecipesByCategory.get(category) || []}
|
||||
{#if categoryRecipes.length > 0}
|
||||
<LazyCategory
|
||||
title={category}
|
||||
|
||||
Reference in New Issue
Block a user