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.

feat: zstd compression when possible

Mary 3b809f38 b88e9ba6

+55 -1
+10
src/components/package-bundle.tsx
··· 212 212 </span> 213 213 </div> 214 214 </Show> 215 + 216 + <Show when={bundleData().zstdSize !== undefined}> 217 + <div class="w-px bg-neutral-stroke-3" /> 218 + <div class="flex flex-col gap-1"> 219 + <span class="text-base-300 text-neutral-foreground-3">Zstd</span> 220 + <span class="text-base-500 font-semibold text-neutral-foreground-1"> 221 + {formatBytes(bundleData().zstdSize!)} 222 + </span> 223 + </div> 224 + </Show> 215 225 <Show when={bundle.state === 'refreshing'}> 216 226 <div class="flex items-center"> 217 227 <LucideLoader class="size-5 animate-spin-linear text-neutral-foreground-3" />
+43 -1
src/npm/lib/bundler.ts
··· 79 79 return getCompressedSize(code, 'brotli'); 80 80 } 81 81 82 + /** 83 + * whether zstd compression is supported. 84 + * - `undefined`: not yet checked 85 + * - `true`: supported 86 + * - `false`: not supported 87 + */ 88 + export let isZstdSupported: boolean | undefined; 89 + 90 + /** 91 + * get zstd size using compression stream, if supported. 92 + * returns `undefined` if zstd is not supported by the browser. 93 + */ 94 + export async function getZstdSize(code: string): Promise<number | undefined> { 95 + if (isZstdSupported === false) { 96 + return undefined; 97 + } 98 + 99 + if (isZstdSupported === undefined) { 100 + try { 101 + // @ts-expect-error 'zstd' is not in the type definition yet 102 + const size = await getCompressedSize(code, 'zstd'); 103 + console.log(`[worker] zstd supported`); 104 + isZstdSupported = true; 105 + return size; 106 + } catch { 107 + console.log(`[worker] zstd not supported`); 108 + isZstdSupported = false; 109 + return undefined; 110 + } 111 + } 112 + 113 + // @ts-expect-error 'zstd' is not in the type definition yet 114 + return getCompressedSize(code, 'zstd'); 115 + } 116 + 82 117 // #endregion 83 118 84 119 // #region core ··· 194 229 rawChunks.map(async (chunk) => { 195 230 const code = chunk.code; 196 231 const size = getUtf8Length(code); 197 - const [gzipSize, brotliSize] = await Promise.all([getGzipSize(code), getBrotliSize(code)]); 232 + const [gzipSize, brotliSize, zstdSize] = await Promise.all([ 233 + getGzipSize(code), 234 + getBrotliSize(code), 235 + getZstdSize(code), 236 + ]); 198 237 199 238 return { 200 239 fileName: chunk.fileName, ··· 202 241 size, 203 242 gzipSize, 204 243 brotliSize, 244 + zstdSize, 205 245 isEntry: chunk.isEntry, 206 246 exports: chunk.exports || [], 207 247 }; ··· 218 258 const totalSize = chunks.reduce((acc, c) => acc + c.size, 0); 219 259 const totalGzipSize = chunks.reduce((acc, c) => acc + c.gzipSize, 0); 220 260 const totalBrotliSize = isBrotliSupported ? chunks.reduce((acc, c) => acc + c.brotliSize!, 0) : undefined; 261 + const totalZstdSize = isZstdSupported ? chunks.reduce((acc, c) => acc + c.zstdSize!, 0) : undefined; 221 262 222 263 await bundle.close(); 223 264 ··· 226 267 size: totalSize, 227 268 gzipSize: totalGzipSize, 228 269 brotliSize: totalBrotliSize, 270 + zstdSize: totalZstdSize, 229 271 exports: entryChunk.exports, 230 272 isCjs, 231 273 };
+2
src/npm/types.ts
··· 88 88 size: v.number(), 89 89 gzipSize: v.number(), 90 90 brotliSize: v.optional(v.number()), 91 + zstdSize: v.optional(v.number()), 91 92 isEntry: v.boolean(), 92 93 exports: v.array(v.string()), 93 94 }); ··· 99 100 size: v.number(), 100 101 gzipSize: v.number(), 101 102 brotliSize: v.optional(v.number()), 103 + zstdSize: v.optional(v.number()), 102 104 exports: v.array(v.string()), 103 105 isCjs: v.boolean(), 104 106 });