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

Configure Feed

Select the types of activity you want to include in your feed.

chore: enable type-aware linting

Mary 48b6c513 af43c5be

+130 -23
+5 -1
.oxlintrc.json
··· 1 1 { 2 2 "$schema": "https://unpkg.com/oxlint/configuration_schema.json", 3 + "options": { 4 + "typeAware": true 5 + }, 3 6 "categories": { 4 7 "correctness": "warn", 5 8 "suspicious": "warn", ··· 9 12 "no-shadow": "off", 10 13 "unicorn/no-array-sort": "off", 11 14 "unicorn/prefer-add-event-listener": "off", 12 - "unicorn/require-post-message-target-origin": "off" 15 + "unicorn/require-post-message-target-origin": "off", 16 + "typescript/require-array-sort-compare": "off" 13 17 } 14 18 }
+1
package.json
··· 31 31 "@types/semver": "^7.7.1", 32 32 "oxfmt": "^0.40.0", 33 33 "oxlint": "^1.55.0", 34 + "oxlint-tsgolint": "^0.16.0", 34 35 "semver": "^7.7.4", 35 36 "tailwindcss": "^4.2.1", 36 37 "typescript": "~5.9.3",
+67 -2
pnpm-lock.yaml
··· 64 64 version: 0.40.0 65 65 oxlint: 66 66 specifier: ^1.55.0 67 - version: 1.55.0 67 + version: 1.55.0(oxlint-tsgolint@0.16.0) 68 + oxlint-tsgolint: 69 + specifier: ^0.16.0 70 + version: 0.16.0 68 71 semver: 69 72 specifier: ^7.7.4 70 73 version: 7.7.4 ··· 983 986 '@oxfmt/binding-win32-x64-msvc@0.40.0': 984 987 resolution: {integrity: sha512-/Zmj0yTYSvmha6TG1QnoLqVT7ZMRDqXvFXXBQpIjteEwx9qvUYMBH2xbiOFhDeMUJkGwC3D6fdKsFtaqUvkwNA==} 985 988 engines: {node: ^20.19.0 || >=22.12.0} 989 + cpu: [x64] 990 + os: [win32] 991 + 992 + '@oxlint-tsgolint/darwin-arm64@0.16.0': 993 + resolution: {integrity: sha512-WQt5lGwRPJBw7q2KNR0mSPDAaMmZmVvDlEEti96xLO7ONhyomQc6fBZxxwZ4qTFedjJnrHX94sFelZ4OKzS7UQ==} 994 + cpu: [arm64] 995 + os: [darwin] 996 + 997 + '@oxlint-tsgolint/darwin-x64@0.16.0': 998 + resolution: {integrity: sha512-VJo29XOzdkalvCTiE2v6FU3qZlgHaM8x8hUEVJGPU2i5W+FlocPpmn00+Ld2n7Q0pqIjyD5EyvZ5UmoIEJMfqg==} 999 + cpu: [x64] 1000 + os: [darwin] 1001 + 1002 + '@oxlint-tsgolint/linux-arm64@0.16.0': 1003 + resolution: {integrity: sha512-MPfqRt1+XRHv9oHomcBMQ3KpTE+CSkZz14wUxDQoqTNdUlV0HWdzwIE9q65I3D9YyxEnqpM7j4qtDQ3apqVvbQ==} 1004 + cpu: [arm64] 1005 + os: [linux] 1006 + 1007 + '@oxlint-tsgolint/linux-x64@0.16.0': 1008 + resolution: {integrity: sha512-XQSwVUsnwLokMhe1TD6IjgvW5WMTPzOGGkdFDtXWQmlN2YeTw94s/NN0KgDrn2agM1WIgAenEkvnm0u7NgwEyw==} 1009 + cpu: [x64] 1010 + os: [linux] 1011 + 1012 + '@oxlint-tsgolint/win32-arm64@0.16.0': 1013 + resolution: {integrity: sha512-EWdlspQiiFGsP2AiCYdhg5dTYyAlj6y1nRyNI2dQWq4Q/LITFHiSRVPe+7m7K7lcsZCEz2icN/bCeSkZaORqIg==} 1014 + cpu: [arm64] 1015 + os: [win32] 1016 + 1017 + '@oxlint-tsgolint/win32-x64@0.16.0': 1018 + resolution: {integrity: sha512-1ufk8cgktXJuJZHKF63zCHAkaLMwZrEXnZ89H2y6NO85PtOXqu4zbdNl0VBpPP3fCUuUBu9RvNqMFiv0VsbXWA==} 986 1019 cpu: [x64] 987 1020 os: [win32] 988 1021 ··· 1735 1768 engines: {node: ^20.19.0 || >=22.12.0} 1736 1769 hasBin: true 1737 1770 1771 + oxlint-tsgolint@0.16.0: 1772 + resolution: {integrity: sha512-4RuJK2jP08XwqtUu+5yhCbxEauCm6tv2MFHKEMsjbosK2+vy5us82oI3VLuHwbNyZG7ekZA26U2LLHnGR4frIA==} 1773 + hasBin: true 1774 + 1738 1775 oxlint@1.55.0: 1739 1776 resolution: {integrity: sha512-T+FjepiyWpaZMhekqRpH8Z3I4vNM610p6w+Vjfqgj5TZUxHXl7N8N5IPvmOU8U4XdTRxqtNNTh9Y4hLtr7yvFg==} 1740 1777 engines: {node: ^20.19.0 || >=22.12.0} ··· 2669 2706 '@oxfmt/binding-win32-x64-msvc@0.40.0': 2670 2707 optional: true 2671 2708 2709 + '@oxlint-tsgolint/darwin-arm64@0.16.0': 2710 + optional: true 2711 + 2712 + '@oxlint-tsgolint/darwin-x64@0.16.0': 2713 + optional: true 2714 + 2715 + '@oxlint-tsgolint/linux-arm64@0.16.0': 2716 + optional: true 2717 + 2718 + '@oxlint-tsgolint/linux-x64@0.16.0': 2719 + optional: true 2720 + 2721 + '@oxlint-tsgolint/win32-arm64@0.16.0': 2722 + optional: true 2723 + 2724 + '@oxlint-tsgolint/win32-x64@0.16.0': 2725 + optional: true 2726 + 2672 2727 '@oxlint/binding-android-arm-eabi@1.55.0': 2673 2728 optional: true 2674 2729 ··· 3278 3333 '@oxfmt/binding-win32-ia32-msvc': 0.40.0 3279 3334 '@oxfmt/binding-win32-x64-msvc': 0.40.0 3280 3335 3281 - oxlint@1.55.0: 3336 + oxlint-tsgolint@0.16.0: 3337 + optionalDependencies: 3338 + '@oxlint-tsgolint/darwin-arm64': 0.16.0 3339 + '@oxlint-tsgolint/darwin-x64': 0.16.0 3340 + '@oxlint-tsgolint/linux-arm64': 0.16.0 3341 + '@oxlint-tsgolint/linux-x64': 0.16.0 3342 + '@oxlint-tsgolint/win32-arm64': 0.16.0 3343 + '@oxlint-tsgolint/win32-x64': 0.16.0 3344 + 3345 + oxlint@1.55.0(oxlint-tsgolint@0.16.0): 3282 3346 optionalDependencies: 3283 3347 '@oxlint/binding-android-arm-eabi': 1.55.0 3284 3348 '@oxlint/binding-android-arm64': 1.55.0 ··· 3299 3363 '@oxlint/binding-win32-arm64-msvc': 1.55.0 3300 3364 '@oxlint/binding-win32-ia32-msvc': 1.55.0 3301 3365 '@oxlint/binding-win32-x64-msvc': 1.55.0 3366 + oxlint-tsgolint: 0.16.0 3302 3367 3303 3368 parse5@7.3.0: 3304 3369 dependencies:
+6 -6
src/components/package-dependencies.tsx
··· 149 149 } 150 150 } 151 151 152 - // oxlint-disable-next-line typescript/no-confusing-non-null-assertion 153 - curr[c]! = cost + bestPrevCost; 154 - // oxlint-disable-next-line typescript/no-confusing-non-null-assertion 155 - parentRow[c]! = bestPrevColor; 152 + curr[c] = cost + bestPrevCost; 153 + parentRow[c] = bestPrevColor; 156 154 } 157 155 158 156 parent.push(parentRow); ··· 345 343 /> 346 344 347 345 {/* sort dropdown */} 348 - <Dropdown.Root value={sortBy()} onValueChange={(v) => setSortBy(v as SortOption)}> 346 + {/* oxlint-disable-next-line typescript/no-unsafe-type-assertion */} 347 + <Dropdown.Root value={sortBy()} onValueChange={(v) => setSortBy(v as SortOption)}> 349 348 <Dropdown.Trigger class="sm:flex-3"> 350 349 <span class="whitespace-pre text-neutral-foreground-3">Sort by </span> 351 350 <span>{SORT_OPTIONS[sortBy()].label}</span> 352 351 </Dropdown.Trigger> 353 352 354 353 <Dropdown.Listbox> 355 - <For each={Object.entries(SORT_OPTIONS) as [SortOption, SortConfig][]}> 354 + {/* oxlint-disable-next-line typescript/no-unsafe-type-assertion */} 355 + <For each={Object.entries(SORT_OPTIONS) as [SortOption, SortConfig][]}> 356 356 {([key, config]) => <Dropdown.Option value={key}>{config.label}</Dropdown.Option>} 357 357 </For> 358 358 </Dropdown.Listbox>
+2
src/components/package-search-input.tsx
··· 48 48 return []; 49 49 } 50 50 51 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 51 52 const data = (await response.json()) as NpmSearchResponse; 52 53 53 54 return data.objects.map((obj) => ({ ··· 65 66 return []; 66 67 } 67 68 69 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 68 70 const data = (await response.json()) as JsrSearchResponse; 69 71 70 72 return data.items.map((item) => ({
+1
src/lib/package-name.ts
··· 24 24 return null; 25 25 } 26 26 return { 27 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 27 28 registry: (match[1] as Registry) ?? 'npm', 28 29 name: match[2]!, 29 30 range: match[3] ?? 'latest',
+8 -2
src/lib/query.ts
··· 79 79 80 80 // normalize arguments 81 81 if (typeof pFetcher === 'function') { 82 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 82 83 source = pSource as QuerySource<S>; 83 84 fetcher = pFetcher; 84 85 options = pOptions || {}; 85 86 } else { 87 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 86 88 source = true as QuerySource<S>; 89 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 87 90 fetcher = pSource as QueryFetcher<S, T, R>; 88 - options = (pFetcher || {}) as QueryOptions<T>; 91 + options = pFetcher || {}; 89 92 } 90 93 91 94 let pr: Promise<T> | null = null; 92 95 let scheduled = false; 93 96 let resolved = 'initialValue' in options; 94 97 98 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 95 99 const dynamic = typeof source === 'function' && createMemo(source as () => S | false | null | undefined); 96 100 const [value, setValue] = createSignal<T | undefined>(options.initialValue); 97 101 const [error, setError] = createSignal<Error | undefined>(undefined); ··· 121 125 } 122 126 scheduled = false; 123 127 128 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 124 129 const lookup = dynamic ? dynamic() : (source as S); 125 130 126 131 if (lookup == null || lookup === false) { ··· 185 190 if (dynamic) { 186 191 createComputed(() => load(false)); 187 192 } else { 188 - load(false); 193 + void load(false); 189 194 } 190 195 196 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 191 197 return [read as QueryResource<T>, { refetch: load, mutate: setValue }]; 192 198 } 193 199
+1
src/lib/signals.ts
··· 11 11 const computable = createMemo(() => createSignal(accessor())); 12 12 13 13 // @ts-expect-error: setter type mismatch is fine 14 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 14 15 return [() => computable()[0](), (next) => computable()[1](next)] as Signal<T>; 15 16 } 16 17
+3 -1
src/lib/use-search-params.ts
··· 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 } 65 65 66 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 66 67 return result as InferSearchParamsOutput<T>; 67 68 }; 68 69 ··· 107 108 } 108 109 } 109 110 111 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 110 112 const validated = result as InferSearchParamsOutput<T>; 111 113 112 114 if (dequal(current, validated)) {
+5 -3
src/npm/lib/bundler.ts
··· 22 22 23 23 { 24 24 const writer = writable.getWriter(); 25 - writer.write(data as Uint8Array<ArrayBuffer>); 26 - writer.close(); 25 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 26 + void writer.write(data as Uint8Array<ArrayBuffer>); 27 + void writer.close(); 27 28 } 28 29 29 30 let size = 0; ··· 212 213 // read the source file 213 214 let source: string; 214 215 try { 215 - source = volume.readFileSync(resolved.id, 'utf8') as string; 216 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 217 + source = volume.readFileSync(resolved.id, 'utf8') as string; 216 218 } catch { 217 219 throw new BundleError(`failed to read entry module: ${resolved.id}`); 218 220 }
+4
src/npm/lib/fetch.test.ts
··· 17 17 18 18 // verify is-odd 19 19 const isOddPackageJson = volume.readFileSync('/node_modules/is-odd/package.json', 'utf8'); 20 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 20 21 const json = JSON.parse(isOddPackageJson as string); 21 22 expect(json.name).toBe('is-odd'); 22 23 ··· 43 44 await fetchPackagesToVolume(hoisted, volume); 44 45 45 46 const packageJson = volume.readFileSync('/node_modules/@babel/parser/package.json', 'utf8'); 47 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 46 48 const json = JSON.parse(packageJson as string); 47 49 expect(json.name).toBe('@babel/parser'); 48 50 }); ··· 58 60 59 61 // is-number@7 at root (explicitly requested) 60 62 const rootIsNumber = volume.readFileSync('/node_modules/is-number/package.json', 'utf8'); 63 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 61 64 const rootJson = JSON.parse(rootIsNumber as string); 62 65 expect(rootJson.version).toBe('7.0.0'); 63 66 ··· 146 149 147 150 const extractedFiles = volume.toJSON(); 148 151 const extractedSize = Object.values(extractedFiles).reduce( 152 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 149 153 (sum, content) => sum + (content as string).length, 150 154 0, 151 155 );
+11 -2
src/npm/lib/module-type.ts
··· 31 31 return ( 32 32 typeof node === 'object' && 33 33 node !== null && 34 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 34 35 (node as { type: string }).type === 'Literal' && 36 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 35 37 typeof (node as { value: unknown }).value === 'string' 36 38 ); 37 39 } ··· 57 59 58 60 // module.exports 59 61 if (node.type === 'MemberExpression') { 62 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 60 63 const memberExpr = node as StaticMemberExpression; 61 64 if (!memberExpr.computed) { 62 65 const obj = memberExpr.object; ··· 74 77 function getStaticPropertyName(node: StaticMemberExpression): string | null { 75 78 if (node.computed) { 76 79 // computed property like exports["foo"] 80 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 77 81 const prop = node.property as unknown as Expression; 78 82 if (isStringLiteral(prop)) { 79 83 return prop.value; ··· 125 129 * checks if an expression is a require() call. 126 130 */ 127 131 function isRequireCall(expr: Expression): boolean { 128 - return expr.type === 'CallExpression' && isIdentifier(expr.callee as Expression, 'require'); 132 + return expr.type === 'CallExpression' && isIdentifier(expr.callee, 'require'); 129 133 } 130 134 131 135 /** ··· 139 143 140 144 // exports.foo = ... or module.exports.foo = ... 141 145 if (left.type === 'MemberExpression') { 146 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 142 147 const memberExpr = left as unknown as StaticMemberExpression; 143 148 const obj = memberExpr.object; 144 149 ··· 152 157 } 153 158 154 159 // module.exports = require('...') - CJS re-export 160 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 155 161 if (isExportsObject(left as unknown as Expression)) { 156 162 if (isRequireCall(expr.right)) { 157 163 // re-export, we can't know the exports statically ··· 168 174 const callee = expr.callee; 169 175 170 176 if (callee.type === 'MemberExpression') { 177 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 171 178 const memberCallee = callee as StaticMemberExpression; 172 179 if (!memberCallee.computed && isIdentifier(memberCallee.object, 'Object')) { 173 180 const prop = memberCallee.property; ··· 357 364 } 358 365 359 366 if (expr.type === 'MemberExpression') { 367 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 360 368 const memberExpr = expr as StaticMemberExpression; 361 369 return containsImportMeta(memberExpr.object); 362 370 } 363 371 364 372 if (expr.type === 'CallExpression') { 365 373 // check callee and arguments 366 - if (containsImportMeta(expr.callee as Expression)) { 374 + if (containsImportMeta(expr.callee)) { 367 375 return true; 368 376 } 369 377 for (const arg of expr.arguments) { ··· 374 382 } 375 383 376 384 if (expr.type === 'BinaryExpression' || expr.type === 'LogicalExpression') { 385 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 377 386 return containsImportMeta(expr.left as Expression) || containsImportMeta(expr.right); 378 387 } 379 388
+4 -3
src/npm/lib/subpaths.ts
··· 50 50 let bestPriority = -1; 51 51 52 52 for (const [condition, target] of Object.entries(value)) { 53 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 53 54 const priority = CONDITION_PRIORITY.indexOf(condition as (typeof CONDITION_PRIORITY)[number]); 54 55 55 56 if (priority > bestPriority) { 56 - const resolved = resolveCondition(target as PackageExports); 57 + const resolved = resolveCondition(target); 57 58 if (resolved) { 58 59 bestMatch = resolved; 59 60 bestPriority = priority; ··· 205 206 206 207 if (subpath.includes('*')) { 207 208 // wildcard pattern 208 - const target = resolveCondition(value as PackageExports); 209 + const target = resolveCondition(value); 209 210 if (target && target.includes('*')) { 210 211 const expanded = expandWildcard(subpath, target, packagePath, volume); 211 212 entries.push(...expanded); 212 213 } 213 214 } else { 214 215 // regular subpath 215 - const target = resolveCondition(value as PackageExports); 216 + const target = resolveCondition(value); 216 217 if (target) { 217 218 entries.push({ 218 219 subpath,
+5 -3
src/npm/lib/worker-entry.ts
··· 54 54 55 55 const mainPackage = resolution.roots[0]!; 56 56 const pkgJsonPath = `/node_modules/${mainPackage.name}/package.json`; 57 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 57 58 const pkgJsonContent = volume.readFileSync(pkgJsonPath, 'utf8') as string; 59 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 58 60 const manifest = JSON.parse(pkgJsonContent) as PackageJson; 59 61 60 62 packageName = mainPackage.name; ··· 65 67 const peerDependencies = Object.keys(manifest.peerDependencies ?? {}); 66 68 const peerDepNames = new Set(peerDependencies); 67 69 68 - const packages = buildInstalledPackages(mainPackage!, peerDepNames); 70 + const packages = buildInstalledPackages(mainPackage, peerDepNames); 69 71 const installSize = packages.reduce((sum, pkg) => sum + pkg.size, 0); 70 72 71 73 initResult = { ··· 174 176 175 177 switch (request.type) { 176 178 case 'init': 177 - handleInit(request.id, request.packageSpec, request.options); 179 + void handleInit(request.id, request.packageSpec, request.options); 178 180 break; 179 181 case 'bundle': 180 - handleBundle(request.id, request.subpath, request.selectedExports, request.options); 182 + void handleBundle(request.id, request.subpath, request.selectedExports, request.options); 181 183 break; 182 184 } 183 185 };
+1
src/npm/worker-client.ts
··· 92 92 await this.ready; 93 93 94 94 const deferred = Promise.withResolvers<T>(); 95 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 95 96 this.pending.set(message.id, deferred as PromiseWithResolvers<unknown>); 96 97 console.log('[worker-client] posting message:', message); 97 98 this.worker.postMessage(message);
+2
src/primitives/dropdown/dropdown-listbox.tsx
··· 70 70 const handleClickOutside = (ev: MouseEvent) => { 71 71 const currentTrigger = ctx.triggerRef(); 72 72 if ( 73 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 73 74 !el.contains(ev.target as Node) && 74 75 currentTrigger && 76 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 75 77 !currentTrigger.contains(ev.target as Node) 76 78 ) { 77 79 ctx.setOpen(false);
+2
src/primitives/menu/menu-popover.tsx
··· 103 103 const handleClickOutside = (ev: MouseEvent) => { 104 104 const currentTrigger = ctx.triggerRef(); 105 105 if ( 106 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 106 107 !el.contains(ev.target as Node) && 107 108 currentTrigger && 109 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 108 110 !currentTrigger.contains(ev.target as Node) 109 111 ) { 110 112 ctx.setOpen(false);
+2
src/primitives/popover/popover-surface.tsx
··· 77 77 78 78 const currentTrigger = ctx.triggerRef(); 79 79 if ( 80 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 80 81 !el.contains(ev.target as Node) && 81 82 currentTrigger && 83 + // oxlint-disable-next-line typescript/no-unsafe-type-assertion 82 84 !currentTrigger.contains(ev.target as Node) 83 85 ) { 84 86 ctx.setOpen(false, 'clickoutside');