DEV Community

Cover image for How I Cut 40KB from My Edge Bundle with Zero-Dep Utils
Michael
Michael

Posted on

How I Cut 40KB from My Edge Bundle with Zero-Dep Utils

When I started building edge services on Cloudflare Workers, I kept hitting the same problem: bundle size.

Every "lightweight" utility pulled in dependencies. zod (1.1 MB), p-retry (7 KB + deps), axios-retry (requires axios). On Workers where cold starts matter, every KB counts.

So I built three zero-dependency replacements:

1. fetch-retry-jitter (1.6 KB gzip)

Replaces: p-retry, axios-retry, custom retry loops

import { fetchRetry } from 'fetch-retry-jitter';

const response = await fetchRetry('https://api.example.com/data', {
  retries: 3,
  baseDelayMs: 300,
  maxDelayMs: 5000,
  circuitBreaker: { failureThreshold: 5, resetTimeoutMs: 30000 },
  timeoutMs: 5000,
  deduplicate: true,
});
Enter fullscreen mode Exit fullscreen mode

Features:

  • Exponential backoff with full jitter (AWS recommended)
  • Circuit breaker (closed/open/half-open states)
  • Request deduplication — coalesces identical in-flight requests
  • Retry-After header support (seconds + HTTP-date)
  • Per-attempt timeoutMs + retryIf predicate
  • AbortSignal support + stream body protection
  • 1.6 KB gzip, zero dependencies

2. env-assert-typed (528 B gzip)

Replaces: Zod (1.1 MB), Valibot (188 KB), Joi (50 KB+)

import { envAssert } from 'env-assert-typed';

const env = envAssert({
  PORT: { type: 'number', required: true },
  HOST: { type: 'string', default: '0.0.0.0' },
  DEBUG: { type: 'boolean' },
  REGION: { type: 'string', required: true, choices: ['us', 'eu'] },
  API_KEY: { type: 'string', required: true, secret: true },
});
Enter fullscreen mode Exit fullscreen mode

Features:

  • Automatic TypeScript inference — required → T, default → T, optional → T | undefined
  • Discriminated union schema — compile errors on mismatched default/choices types
  • coerce — custom string→type parsing
  • refine — cross-field validation with access to all env
  • refineAsync — async refinement hook
  • transform — post-coercion transformation
  • secret — marks sensitive values for error masking
  • Factory functions for defaults
  • 528 B gzip, zero dependencies

3. ci-diff-changed (Action, ~80 lines bash)

Replaces tj-actions/changed-files (Node container + API calls)

- uses: Yearnless/ci-diff-changed@v1
  id: diff
  with:
    base-ref: origin/main
    pattern: 'packages/(api|web)/'
Enter fullscreen mode Exit fullscreen mode

Outputs JSON array of changed files — perfect for conditional CI in monorepos.


Results

Before After Savings
zod (1.1 MB) env-assert-typed 99.9% smaller
p-retry (7 KB + deps) fetch-retry-jitter ~75% smaller
tj-actions/changed-files (container) ci-diff-changed (bash) No container

Total bundle reduction: ~40 KB → ~2 KB (95% reduction)


Why Zero-Dep Matters on the Edge

  1. Cold starts — Every KB adds milliseconds to cold start
  2. Bundle limits — Workers free tier: 1 MB, paid: 10 MB
  3. Supply chain — Zero deps = zero supply chain risk
  4. Auditability — Read the entire source in 5 minutes

Try Them

npm i fetch-retry-jitter env-assert-typed
# GitHub Action:
# uses: Yearnless/ci-diff-changed@v1
Enter fullscreen mode Exit fullscreen mode

All three include:

  • ✅ Provenance-signed npm packages
  • ✅ Full TypeScript inference
  • ✅ 100% test coverage
  • ✅ Zero dependencies
  • ✅ Works on Node, Bun, Deno, Cloudflare Workers, browsers

Repos:

Starter templates using them:


Built while learning in public. Feedback welcome!


Top comments (0)