[READ-ONLY] a fast, modern browser for the npm registry
at main 107 lines 3.2 kB view raw
1import type { ModuleReplacement } from 'module-replacements' 2 3export interface ReplacementSuggestion { 4 forPackage: string 5 replacement: ModuleReplacement 6} 7 8/** 9 * Replacement types that suggest "no dependency" (can be replaced with native code or inline). 10 */ 11const NO_DEP_REPLACEMENT_TYPES = ['native', 'simple'] as const 12 13/** 14 * Replacement types that are informational only. 15 * These suggest alternative packages exist but don't fit the "no dependency" pattern. 16 */ 17const INFO_REPLACEMENT_TYPES = ['documented'] as const 18 19/** 20 * Composable for fetching module replacement suggestions for packages in comparison. 21 * Returns replacements split into "no dep" (actionable) and informational categories. 22 */ 23export function useCompareReplacements(packageNames: MaybeRefOrGetter<string[]>) { 24 const packages = computed(() => toValue(packageNames)) 25 26 // Cache replacement data by package name 27 const replacements = shallowRef(new Map<string, ModuleReplacement | null>()) 28 const loading = shallowRef(false) 29 30 // Fetch replacements for all packages 31 async function fetchReplacements(names: string[]) { 32 if (names.length === 0) return 33 34 // Filter out packages we've already checked 35 const namesToCheck = names.filter(name => !replacements.value.has(name)) 36 if (namesToCheck.length === 0) return 37 38 loading.value = true 39 40 try { 41 const results = await Promise.all( 42 namesToCheck.map(async name => { 43 try { 44 const replacement = await $fetch<ModuleReplacement | null>(`/api/replacements/${name}`) 45 return { name, replacement } 46 } catch { 47 return { name, replacement: null } 48 } 49 }), 50 ) 51 52 const newReplacements = new Map(replacements.value) 53 for (const { name, replacement } of results) { 54 newReplacements.set(name, replacement) 55 } 56 replacements.value = newReplacements 57 } finally { 58 loading.value = false 59 } 60 } 61 62 // Watch for package changes and fetch replacements 63 if (import.meta.client) { 64 watch( 65 packages, 66 newPackages => { 67 fetchReplacements(newPackages) 68 }, 69 { immediate: true }, 70 ) 71 } 72 73 // Build suggestions from replacements 74 const allSuggestions = computed(() => { 75 const result: ReplacementSuggestion[] = [] 76 77 for (const pkg of packages.value) { 78 const replacement = replacements.value.get(pkg) 79 if (!replacement) continue 80 81 result.push({ forPackage: pkg, replacement }) 82 } 83 84 return result 85 }) 86 87 // Suggestions that prompt adding the "no dep" column (native, simple) 88 const noDepSuggestions = computed(() => 89 allSuggestions.value.filter(s => 90 (NO_DEP_REPLACEMENT_TYPES as readonly string[]).includes(s.replacement.type), 91 ), 92 ) 93 94 // Informational suggestions that don't prompt "no dep" (documented) 95 const infoSuggestions = computed(() => 96 allSuggestions.value.filter(s => 97 (INFO_REPLACEMENT_TYPES as readonly string[]).includes(s.replacement.type), 98 ), 99 ) 100 101 return { 102 replacements: readonly(replacements), 103 noDepSuggestions: readonly(noDepSuggestions), 104 infoSuggestions: readonly(infoSuggestions), 105 loading: readonly(loading), 106 } 107}