Find the cost of adding an npm package to your app's bundle size teardown.kelinci.dev

refactor: enable noUncheckedIndexedAccess

mary.my.id 519f26a7 f8888bd8

verified
+71 -68
+12 -12
src/components/package-dependencies.tsx
··· 106 106 return []; 107 107 } 108 108 if (numSegments === 1) { 109 - return [canonicalIndices[0]]; 109 + return [canonicalIndices[0]!]; 110 110 } 111 111 112 112 // fall back to greedy for very large inputs 113 113 if (numSegments > COLOR_RESOLUTION_DP_LIMIT) { 114 - const resolved: number[] = [canonicalIndices[0]]; 114 + const resolved: number[] = [canonicalIndices[0]!]; 115 115 for (let i = 1; i < numSegments; i++) { 116 - const canonical = canonicalIndices[i]; 116 + const canonical = canonicalIndices[i]!; 117 117 resolved.push(canonical === resolved[i - 1] ? (canonical + 1) % numColors : canonical); 118 118 } 119 119 return resolved; ··· 128 128 129 129 // base case: position 0 130 130 for (let c = 0; c < numColors; c++) { 131 - prev[c] = c === canonicalIndices[0] ? 0 : 1; 131 + prev[c] = c === canonicalIndices[0]! ? 0 : 1; 132 132 } 133 133 134 134 // fill DP table 135 135 for (let i = 1; i < numSegments; i++) { 136 - const canonical = canonicalIndices[i]; 136 + const canonical = canonicalIndices[i]!; 137 137 const parentRow = Array.from({ length: numColors }, () => 0); 138 138 139 139 for (let c = 0; c < numColors; c++) { ··· 143 143 let bestPrevCost = Infinity; 144 144 let bestPrevColor = 0; 145 145 for (let cp = 0; cp < numColors; cp++) { 146 - if (cp !== c && prev[cp] < bestPrevCost) { 147 - bestPrevCost = prev[cp]; 146 + if (cp !== c && prev[cp]! < bestPrevCost) { 147 + bestPrevCost = prev[cp]!; 148 148 bestPrevColor = cp; 149 149 } 150 150 } 151 151 152 - curr[c] = cost + bestPrevCost; 153 - parentRow[c] = bestPrevColor; 152 + curr[c]! = cost + bestPrevCost; 153 + parentRow[c]! = bestPrevColor; 154 154 } 155 155 156 156 parent.push(parentRow); ··· 160 160 // find best final color 161 161 let bestFinal = 0; 162 162 for (let c = 1; c < numColors; c++) { 163 - if (prev[c] < prev[bestFinal]) { 163 + if (prev[c]! < prev[bestFinal]!) { 164 164 bestFinal = c; 165 165 } 166 166 } ··· 169 169 let color = bestFinal; 170 170 const reversed = [color]; 171 171 for (let i = parent.length - 1; i >= 0; i--) { 172 - color = parent[i][color]; 172 + color = parent[i]![color]!; 173 173 reversed.push(color); 174 174 } 175 175 return reversed.reverse(); ··· 192 192 return sorted.map((pkg, i) => ({ 193 193 pkg, 194 194 percent: (pkg.size / props.installSize) * 100, 195 - color: SEGMENT_COLORS[resolvedIndices[i]], 195 + color: SEGMENT_COLORS[resolvedIndices[i]!]!, 196 196 })); 197 197 }); 198 198
+1 -1
src/components/package-search-input.tsx
··· 184 184 ev.preventDefault(); 185 185 const idx = activeIndex(); 186 186 if (idx >= 0 && idx < items.length) { 187 - handleSelect(items[idx]); 187 + handleSelect(items[idx]!); 188 188 } else { 189 189 const parsed = parsePackageSpecifier(props.value.trim()); 190 190 if (parsed) {
+1 -1
src/lib/emitter.ts
··· 87 87 listener.apply(this, args); 88 88 } else { 89 89 for (let idx = 0, len = listener.length; idx < len; idx++) { 90 - listener[idx].apply(this, args); 90 + listener[idx]!.apply(this, args); 91 91 } 92 92 } 93 93 },
+1 -1
src/lib/package-name.ts
··· 25 25 } 26 26 return { 27 27 registry: (match[1] as Registry) ?? 'npm', 28 - name: match[2], 28 + name: match[2]!, 29 29 range: match[3] ?? 'latest', 30 30 }; 31 31 };
+3 -3
src/lib/use-search-params.ts
··· 45 45 const result: Record<string, unknown> = {}; 46 46 47 47 for (const key in definition) { 48 - const schema = definition[key]; 48 + const schema = definition[key]!; 49 49 50 50 let raw: string | string[] | undefined; 51 51 if (schema.type === 'array') { ··· 58 58 if (raw === undefined) { 59 59 result[key] = undefined; 60 60 } else { 61 - const parsed = v.safeParse(schema, raw); 61 + const parsed = v.safeParse(schema!, raw); 62 62 result[key] = parsed.success ? parsed.output : undefined; 63 63 } 64 64 } ··· 91 91 continue; 92 92 } 93 93 94 - const parsed = v.safeParse(definition[key], value); 94 + const parsed = v.safeParse(definition[key]!, value); 95 95 if (!parsed.success) { 96 96 result[key] = undefined; 97 97 continue;
+1 -1
src/npm/lib/fetch.ts
··· 182 182 break; 183 183 } 184 184 185 - const { node, basePath } = queue[i]; 185 + const { node, basePath } = queue[i]!; 186 186 const packagePath = `${basePath}/${node.name}`; 187 187 188 188 const extractedSize = await fetchTarballToVolume(node.tarball, packagePath, volume, exclude);
+9 -9
src/npm/lib/installed-packages.test.ts
··· 6 6 describe('buildInstalledPackages', () => { 7 7 it('builds packages from a simple dependency tree', async () => { 8 8 const result = await resolve(['is-odd@3.0.1']); 9 - const packages = buildInstalledPackages(result.roots[0], new Set()); 9 + const packages = buildInstalledPackages(result.roots[0]!, new Set()); 10 10 11 11 // should have is-odd and is-number 12 12 const names = packages.map((p) => p.name); ··· 25 25 26 26 it('correctly sets dependents', async () => { 27 27 const result = await resolve(['is-odd@3.0.1']); 28 - const packages = buildInstalledPackages(result.roots[0], new Set()); 28 + const packages = buildInstalledPackages(result.roots[0]!, new Set()); 29 29 30 30 // is-odd is the root, no dependents 31 31 const isOdd = packages.find((p) => p.name === 'is-odd')!; ··· 34 34 // is-number is depended on by is-odd 35 35 const isNumber = packages.find((p) => p.name === 'is-number')!; 36 36 expect(isNumber.dependents.length).toBe(1); 37 - expect(isNumber.dependents[0].name).toBe('is-odd'); 37 + expect(isNumber.dependents[0]!.name).toBe('is-odd'); 38 38 }); 39 39 40 40 it('correctly sets dependencies', async () => { 41 41 const result = await resolve(['is-odd@3.0.1']); 42 - const packages = buildInstalledPackages(result.roots[0], new Set()); 42 + const packages = buildInstalledPackages(result.roots[0]!, new Set()); 43 43 44 44 // is-odd has 1 dependency (is-number) 45 45 const isOdd = packages.find((p) => p.name === 'is-odd')!; 46 46 expect(isOdd.dependencies.length).toBe(1); 47 - expect(isOdd.dependencies[0].name).toBe('is-number'); 47 + expect(isOdd.dependencies[0]!.name).toBe('is-number'); 48 48 }); 49 49 50 50 it('marks peer dependencies correctly', async () => { 51 51 // use-sync-external-store has react as a peer dependency 52 52 const result = await resolve(['use-sync-external-store@1.2.0']); 53 53 const peerDepNames = new Set(['react']); 54 - const packages = buildInstalledPackages(result.roots[0], peerDepNames); 54 + const packages = buildInstalledPackages(result.roots[0]!, peerDepNames); 55 55 56 56 // react and its deps should be marked as peer 57 57 const react = packages.find((p) => p.name === 'react'); ··· 74 74 // loose-envify should be marked as peer (only reachable through react) 75 75 const result = await resolve(['use-sync-external-store@1.2.0']); 76 76 const peerDepNames = new Set(['react']); 77 - const packages = buildInstalledPackages(result.roots[0], peerDepNames); 77 + const packages = buildInstalledPackages(result.roots[0]!, peerDepNames); 78 78 79 79 const looseEnvify = packages.find((p) => p.name === 'loose-envify'); 80 80 // loose-envify is a dep of react, which is peer-only ··· 90 90 91 91 // pretend is-number is also a peer dep (but it's already a regular dep) 92 92 const peerDepNames = new Set(['is-number']); 93 - const packages = buildInstalledPackages(result.roots[0], peerDepNames); 93 + const packages = buildInstalledPackages(result.roots[0]!, peerDepNames); 94 94 95 95 // is-number should be marked as peer because it's a direct peer dep of root 96 96 const isNumber = packages.find((p) => p.name === 'is-number')!; ··· 103 103 // graphql should still be marked as peer since it's a direct peer dep of root 104 104 const result = await resolve(['graphql-request@7.4.0']); 105 105 const peerDepNames = new Set(['graphql']); 106 - const packages = buildInstalledPackages(result.roots[0], peerDepNames); 106 + const packages = buildInstalledPackages(result.roots[0]!, peerDepNames); 107 107 108 108 // graphql should be marked as peer 109 109 const graphql = packages.find((p) => p.name === 'graphql');
+2 -2
src/npm/lib/module-type.ts
··· 174 174 if (prop.type === 'Identifier' && prop.name === 'defineProperty') { 175 175 const args = expr.arguments; 176 176 if (args.length >= 2) { 177 - const target = args[0]; 178 - const propArg = args[1]; 177 + const target = args[0]!; 178 + const propArg = args[1]!; 179 179 180 180 if ( 181 181 target.type !== 'SpreadElement' &&
+14 -14
src/npm/lib/resolve.test.ts
··· 415 415 const result = await resolve(['is-odd@3.0.1']); 416 416 417 417 expect(result.roots).toHaveLength(1); 418 - expect(result.roots[0].name).toBe('is-odd'); 419 - expect(result.roots[0].version).toBe('3.0.1'); 420 - expect(result.roots[0].dependencies.has('is-number')).toBe(true); 418 + expect(result.roots[0]!.name).toBe('is-odd'); 419 + expect(result.roots[0]!.version).toBe('3.0.1'); 420 + expect(result.roots[0]!.dependencies.has('is-number')).toBe(true); 421 421 }); 422 422 423 423 it('resolves multiple packages', async () => { 424 424 const result = await resolve(['is-odd@3.0.1', 'is-even@1.0.0']); 425 425 426 426 expect(result.roots).toHaveLength(2); 427 - expect(result.roots[0].name).toBe('is-odd'); 428 - expect(result.roots[1].name).toBe('is-even'); 427 + expect(result.roots[0]!.name).toBe('is-odd'); 428 + expect(result.roots[1]!.name).toBe('is-even'); 429 429 }); 430 430 431 431 it('deduplicates shared dependencies', async () => { ··· 446 446 const result = await resolve(['jsr:@luca/flag@1.0.1']); 447 447 448 448 expect(result.roots).toHaveLength(1); 449 - expect(result.roots[0].name).toBe('@luca/flag'); 450 - expect(result.roots[0].version).toBe('1.0.1'); 451 - expect(result.roots[0].tarball).toContain('npm.jsr.io'); 449 + expect(result.roots[0]!.name).toBe('@luca/flag'); 450 + expect(result.roots[0]!.version).toBe('1.0.1'); 451 + expect(result.roots[0]!.tarball).toContain('npm.jsr.io'); 452 452 }); 453 453 454 454 it('resolves JSR package with JSR dependencies', async () => { 455 455 const result = await resolve(['jsr:@std/path@1.1.4']); 456 456 457 - expect(result.roots[0].name).toBe('@std/path'); 457 + expect(result.roots[0]!.name).toBe('@std/path'); 458 458 // dependency stored under npm-compatible name, resolved to canonical 459 - expect(result.roots[0].dependencies.has('@jsr/std__internal')).toBe(true); 460 - const internal = result.roots[0].dependencies.get('@jsr/std__internal')!; 459 + expect(result.roots[0]!.dependencies.has('@jsr/std__internal')).toBe(true); 460 + const internal = result.roots[0]!.dependencies.get('@jsr/std__internal')!; 461 461 expect(internal.name).toBe('@std/internal'); 462 462 expect(internal.tarball).toContain('npm.jsr.io'); 463 463 }); ··· 467 467 it('auto-installs required peer dependencies', async () => { 468 468 const result = await resolve(['use-sync-external-store@1.2.0']); 469 469 470 - const mainPkg = result.roots[0]; 470 + const mainPkg = result.roots[0]!; 471 471 expect(mainPkg.dependencies.has('react')).toBe(true); 472 472 expect(Array.from(result.packages.values()).some((p) => p.name === 'react')).toBe(true); 473 473 }); ··· 476 476 const result = await resolve(['use-sync-external-store@1.2.0']); 477 477 478 478 // react is required, should be present 479 - const mainPkg = result.roots[0]; 479 + const mainPkg = result.roots[0]!; 480 480 expect(mainPkg.dependencies.has('react')).toBe(true); 481 481 }); 482 482 ··· 484 484 const result = await resolve(['use-sync-external-store@1.2.0'], { installPeers: false }); 485 485 486 486 expect(result.roots).toHaveLength(1); 487 - expect(result.roots[0].name).toBe('use-sync-external-store'); 487 + expect(result.roots[0]!.name).toBe('use-sync-external-store'); 488 488 }); 489 489 }); 490 490 });
+5 -5
src/npm/lib/resolve.ts
··· 85 85 ): AbbreviatedManifest | null { 86 86 // empty range means latest 87 87 if (range === '') { 88 - return versions[distTags.latest] ?? null; 88 + return versions[distTags.latest!] ?? null; 89 89 } 90 90 91 91 // check if range is a dist-tag 92 92 if (range in distTags) { 93 - const taggedVersion = distTags[range]; 93 + const taggedVersion = distTags[range]!; 94 94 return versions[taggedVersion] ?? null; 95 95 } 96 96 ··· 131 131 } 132 132 133 133 // prefer non-deprecated versions (pnpm behavior) 134 - const nonDeprecated = validVersions.find((v) => !versions[v].deprecated); 134 + const nonDeprecated = validVersions.find((v) => !versions[v]!.deprecated); 135 135 if (nonDeprecated !== undefined) { 136 - return versions[nonDeprecated]; 136 + return versions[nonDeprecated]!; 137 137 } 138 138 139 139 // fall back to deprecated if no alternatives 140 - return versions[validVersions[0]]; 140 + return versions[validVersions[0]!]!; 141 141 } 142 142 143 143 /**
+5 -3
src/npm/lib/subpaths.ts
··· 115 115 return entries; 116 116 } 117 117 118 - const [prefix, suffix] = targetParts; 118 + const prefix = targetParts[0]!; 119 + const suffix = targetParts[1]!; 119 120 const subpathParts = subpath.split('*'); 120 121 if (subpathParts.length !== 2) { 121 122 return entries; 122 123 } 123 124 124 - const [subpathPrefix, subpathSuffix] = subpathParts; 125 + const subpathPrefix = subpathParts[0]!; 126 + const subpathSuffix = subpathParts[1]!; 125 127 126 128 // normalize the prefix to match volume paths 127 129 // target like "./src/*.js" becomes "/node_modules/pkg/src" ··· 270 272 } else if (entries.length > 0) { 271 273 // otherwise, pick first alphabetically 272 274 entries.sort((a, b) => a.subpath.localeCompare(b.subpath)); 273 - defaultSubpath = entries[0].subpath; 275 + defaultSubpath = entries[0]!.subpath; 274 276 } 275 277 276 278 return {
+2 -2
src/npm/lib/worker-entry.ts
··· 58 58 59 59 await fetchPackagesToVolume(hoisted, volume, options.fetch); 60 60 61 - const mainPackage = resolution.roots[0]; 61 + const mainPackage = resolution.roots[0]!; 62 62 const pkgJsonPath = `/node_modules/${mainPackage.name}/package.json`; 63 63 const pkgJsonContent = volume.readFileSync(pkgJsonPath, 'utf8') as string; 64 64 const manifest = JSON.parse(pkgJsonContent) as PackageJson; ··· 71 71 const peerDependencies = Object.keys(manifest.peerDependencies ?? {}); 72 72 const peerDepNames = new Set(peerDependencies); 73 73 74 - const packages = buildInstalledPackages(mainPackage, peerDepNames); 74 + const packages = buildInstalledPackages(mainPackage!, peerDepNames); 75 75 const installSize = packages.reduce((sum, pkg) => sum + pkg.size, 0); 76 76 77 77 initResult = {
+5 -5
src/primitives/lib/create-active-descendant.ts
··· 82 82 if (enabled.length === 0) { 83 83 return null; 84 84 } 85 - const id = enabled[0].id; 85 + const id = enabled[0]!.id; 86 86 setActiveId(id); 87 87 return id; 88 88 }; ··· 92 92 if (enabled.length === 0) { 93 93 return null; 94 94 } 95 - const id = enabled[enabled.length - 1].id; 95 + const id = enabled[enabled.length - 1]!.id; 96 96 setActiveId(id); 97 97 return id; 98 98 }; ··· 106 106 const currentIndex = getActiveIndex(); 107 107 // circular: wrap to first if at end or no current 108 108 const nextIndex = currentIndex === -1 || currentIndex >= enabled.length - 1 ? 0 : currentIndex + 1; 109 - const id = enabled[nextIndex].id; 109 + const id = enabled[nextIndex]!.id; 110 110 setActiveId(id); 111 111 return id; 112 112 }; ··· 120 120 const currentIndex = getActiveIndex(); 121 121 // circular: wrap to last if at start or no current 122 122 const prevIndex = currentIndex <= 0 ? enabled.length - 1 : currentIndex - 1; 123 - const id = enabled[prevIndex].id; 123 + const id = enabled[prevIndex]!.id; 124 124 setActiveId(id); 125 125 return id; 126 126 }; ··· 146 146 147 147 for (let i = 0; i < enabled.length; i++) { 148 148 const index = (startIndex + i) % enabled.length; 149 - const item = enabled[index]; 149 + const item = enabled[index]!; 150 150 const textValue = item.textValue?.toLowerCase() ?? ''; 151 151 152 152 if (textValue.startsWith(searchBuffer)) {
+8 -8
src/primitives/lib/create-roving-tabindex.ts
··· 67 67 options?.onFocusChange?.(index); 68 68 69 69 if (focus && index >= 0 && index < items.length) { 70 - items[index].el.focus(); 70 + items[index]!.el.focus(); 71 71 } 72 72 }; 73 73 74 74 const getEnabledIndices = (): number[] => { 75 75 const indices: number[] = []; 76 76 for (let i = 0; i < items.length; i++) { 77 - if (!items[i].disabled) { 77 + if (!items[i]!.disabled) { 78 78 indices.push(i); 79 79 } 80 80 } ··· 86 86 if (enabled.length === 0) { 87 87 return; 88 88 } 89 - setFocusedIndex(enabled[0]); 89 + setFocusedIndex(enabled[0]!); 90 90 }; 91 91 92 92 const last = () => { ··· 94 94 if (enabled.length === 0) { 95 95 return; 96 96 } 97 - setFocusedIndex(enabled[enabled.length - 1]); 97 + setFocusedIndex(enabled[enabled.length - 1]!); 98 98 }; 99 99 100 100 const next = () => { ··· 108 108 const currentPos = enabled.indexOf(current); 109 109 // circular: wrap to first if at end or not found 110 110 const nextPos = currentPos === -1 || currentPos >= enabled.length - 1 ? 0 : currentPos + 1; 111 - setFocusedIndex(enabled[nextPos]); 111 + setFocusedIndex(enabled[nextPos]!); 112 112 }; 113 113 114 114 const prev = () => { ··· 122 122 const currentPos = enabled.indexOf(current); 123 123 // circular: wrap to last if at start or not found 124 124 const prevPos = currentPos <= 0 ? enabled.length - 1 : currentPos - 1; 125 - setFocusedIndex(enabled[prevPos]); 125 + setFocusedIndex(enabled[prevPos]!); 126 126 }; 127 127 128 128 const search = (char: string) => { ··· 147 147 148 148 for (let i = 0; i < enabled.length; i++) { 149 149 const pos = (startPos + i) % enabled.length; 150 - const index = enabled[pos]; 151 - const item = items[index]; 150 + const index = enabled[pos]!; 151 + const item = items[index]!; 152 152 const textValue = item.textValue?.toLowerCase() ?? item.el.textContent?.toLowerCase() ?? ''; 153 153 154 154 if (textValue.startsWith(searchBuffer)) {
+2 -1
tsconfig.app.json
··· 23 23 "noUnusedParameters": true, 24 24 "erasableSyntaxOnly": true, 25 25 "noFallthroughCasesInSwitch": true, 26 - "noUncheckedSideEffectImports": true 26 + "noUncheckedSideEffectImports": true, 27 + "noUncheckedIndexedAccess": true 27 28 }, 28 29 "include": ["src"] 29 30 }