universal-javascript-runtimes

Explains universal JavaScript deployment, the UnJS ecosystem (Nitro, H3, unenv), WinterCG web standards, and cross-platform runtimes. Use when building apps that deploy anywhere, understanding server adapters, or working with edge functions and serverless platforms.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "universal-javascript-runtimes" with this command: npx skills add farming-labs/fm-skills/farming-labs-fm-skills-universal-javascript-runtimes

Universal JavaScript Runtimes

Overview

Modern JavaScript can run in many environments: Node.js, Deno, Bun, Cloudflare Workers, browsers, and more. Universal JavaScript means writing code once that runs everywhere.

The Runtime Fragmentation Problem

JAVASCRIPT RUNTIMES (2024):

┌─────────────────────────────────────────────────────────────────┐
│                    Traditional Servers                           │
├─────────────────────────────────────────────────────────────────┤
│  Node.js         │ The original server runtime (2009)           │
│  Deno            │ Secure runtime by Node creator (2020)        │
│  Bun             │ Fast all-in-one runtime (2022)               │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Edge/Serverless                               │
├─────────────────────────────────────────────────────────────────┤
│  Cloudflare Workers  │ V8 isolates at the edge                  │
│  Vercel Edge         │ V8 isolates, Next.js optimized           │
│  Deno Deploy         │ Deno at the edge                         │
│  Netlify Edge        │ Deno-based edge functions                │
│  AWS Lambda          │ Node.js/custom runtimes                  │
│  Fastly Compute      │ WebAssembly-based                        │
└─────────────────────────────────────────────────────────────────┘

PROBLEM: Each has different APIs!

// Node.js
const fs = require('fs');
const http = require('http');

// Deno
await Deno.readFile('file.txt');
Deno.serve(handler);

// Cloudflare Workers
export default { fetch(request, env, ctx) {} }

// Bun
Bun.serve({ fetch(req) {} });

Web Standards: The Universal Foundation

The solution is web standard APIs that all runtimes implement:

// WEB STANDARD APIs (work everywhere):

// Fetch API
const response = await fetch('https://api.example.com/data');
const data = await response.json();

// Request/Response
const request = new Request('https://example.com', {
  method: 'POST',
  body: JSON.stringify({ key: 'value' }),
});
const response = new Response('Hello', { status: 200 });

// URL
const url = new URL('https://example.com/path?query=value');
console.log(url.pathname);  // '/path'

// Headers
const headers = new Headers();
headers.set('Content-Type', 'application/json');

// TextEncoder/TextDecoder
const encoder = new TextEncoder();
const bytes = encoder.encode('Hello');

// Crypto
const hash = await crypto.subtle.digest('SHA-256', data);

// Streams
const stream = new ReadableStream({ /* ... */ });

// AbortController
const controller = new AbortController();
fetch(url, { signal: controller.signal });

WinterCG (Web-interoperable Runtimes Community Group)

WinterCG standardizes APIs across server runtimes:

WINTERCG MEMBERS:
- Cloudflare
- Deno
- Node.js
- Vercel
- Shopify
- Bloomberg
- ... and more

STANDARDIZED APIs:
├── fetch(), Request, Response, Headers
├── URL, URLSearchParams, URLPattern
├── TextEncoder, TextDecoder
├── crypto.subtle (Web Crypto API)
├── Streams (ReadableStream, WritableStream)
├── AbortController, AbortSignal
├── setTimeout, setInterval
├── console
├── structuredClone
├── atob, btoa
└── Performance API

RESULT: Code using these APIs works on ANY compliant runtime

The UnJS Ecosystem

UnJS (Universal JavaScript) is a collection of packages for building universal JavaScript:

UNJS ECOSYSTEM:

┌─────────────────────────────────────────────────────────────────┐
│                        FRAMEWORKS                                │
├─────────────────────────────────────────────────────────────────┤
│  Nitro       │ Universal server builder (powers Nuxt)           │
│  H3          │ Minimal HTTP framework                           │
│  Nuxt        │ Full-stack Vue framework (uses Nitro)           │
│  Analog      │ Angular meta-framework (uses Nitro)              │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                      CORE UTILITIES                              │
├─────────────────────────────────────────────────────────────────┤
│  ofetch      │ Better fetch with auto-retry, interceptors       │
│  unenv       │ Runtime environment polyfills                    │
│  unbuild     │ Unified build system for libraries               │
│  unimport    │ Auto-import utilities                            │
│  unstorage   │ Universal storage layer                          │
│  uncrypto    │ Universal crypto utilities                       │
│  unhead      │ Universal document head manager                  │
│  unctx       │ Composable async context                         │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                     INFRASTRUCTURE                               │
├─────────────────────────────────────────────────────────────────┤
│  listhen     │ Universal HTTP server listener                   │
│  ufo         │ URL utilities                                    │
│  pathe       │ Cross-platform path utilities                    │
│  consola     │ Universal logger                                 │
│  defu        │ Deep defaults merging                            │
│  hookable    │ Async hooks system                               │
│  c12         │ Config loading utility                           │
└─────────────────────────────────────────────────────────────────┘

H3: The Universal HTTP Framework

H3 is a minimal, high-performance HTTP framework that runs everywhere:

// H3 BASICS:

import { createApp, createRouter, defineEventHandler } from 'h3';

const app = createApp();
const router = createRouter();

// Define routes
router.get('/hello', defineEventHandler((event) => {
  return { message: 'Hello World' };
}));

router.post('/users', defineEventHandler(async (event) => {
  const body = await readBody(event);
  return { created: body };
}));

app.use(router);

// H3 is runtime-agnostic - it works with:
// - Node.js http server
// - Bun.serve
// - Deno.serve  
// - Cloudflare Workers
// - Any web standard runtime

H3 Event Handler Model

// H3 uses an "event" abstraction over Request/Response:

import {
  defineEventHandler,
  getQuery,
  getRouterParams,
  readBody,
  setCookie,
  setHeader,
  createError,
  sendRedirect,
} from 'h3';

// Request utilities
const handler = defineEventHandler(async (event) => {
  // URL query parameters
  const query = getQuery(event);  // ?page=1 → { page: '1' }
  
  // Route parameters
  const params = getRouterParams(event);  // /users/:id → { id: '123' }
  
  // Request body (auto-parsed)
  const body = await readBody(event);  // JSON, FormData, etc.
  
  // Request headers
  const auth = getHeader(event, 'authorization');
  
  // Response utilities
  setHeader(event, 'X-Custom', 'value');
  setCookie(event, 'session', 'abc123');
  
  // Return response (auto-serialized)
  return { success: true };  // Becomes JSON response
});

// Error handling
const protectedHandler = defineEventHandler((event) => {
  const user = getUser(event);
  if (!user) {
    throw createError({
      statusCode: 401,
      message: 'Unauthorized',
    });
  }
  return { user };
});

// Redirects
const redirectHandler = defineEventHandler((event) => {
  return sendRedirect(event, '/new-location', 302);
});

Why H3 Over Express/Fastify?

EXPRESS (Traditional Node.js):
┌─────────────────────────────────────────────────────────────────┐
│  - Built on Node.js http module                                 │
│  - req/res are Node-specific objects                           │
│  - Middleware modifies req/res                                  │
│  - Large ecosystem but Node-only                                │
│  - Callback-based (older pattern)                               │
│                                                                  │
│  LIMITATION: Only works on Node.js                              │
└─────────────────────────────────────────────────────────────────┘

H3 (Universal):
┌─────────────────────────────────────────────────────────────────┐
│  - Built on web standards (Request/Response)                   │
│  - Event abstraction over different runtimes                   │
│  - Composable with defineEventHandler                          │
│  - Tree-shakeable (small bundle)                               │
│  - TypeScript-first                                             │
│                                                                  │
│  ADVANTAGE: Works everywhere via adapters                       │
└─────────────────────────────────────────────────────────────────┘

// Express (Node.js only):
app.get('/api', (req, res) => {
  res.json({ message: 'Hello' });
});

// H3 (Universal):
router.get('/api', defineEventHandler(() => {
  return { message: 'Hello' };  // Works on Node, Deno, Workers, etc.
}));

Nitro: Universal Server Builder

Nitro is the server framework that powers Nuxt. It builds universal server applications:

NITRO OVERVIEW:

┌─────────────────────────────────────────────────────────────────┐
│                      YOUR CODE                                   │
│                                                                  │
│   server/                                                        │
│   ├── api/           API routes (auto-registered)               │
│   │   ├── hello.ts   → /api/hello                               │
│   │   └── users/                                                │
│   │       └── [id].ts → /api/users/:id                          │
│   ├── routes/        Custom routes                              │
│   ├── middleware/    Server middleware                          │
│   ├── plugins/       Server plugins                             │
│   └── utils/         Server utilities                           │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                     NITRO BUILD                                  │
│                                                                  │
│   1. Scan routes and handlers                                   │
│   2. Bundle server code                                         │
│   3. Apply runtime-specific transforms                          │
│   4. Generate optimized output                                  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
              ┌───────────────┴───────────────┐
              │         PRESETS               │
              ├───────────────────────────────┤
              │  node-server    │ Node.js     │
              │  cloudflare     │ Workers     │
              │  vercel         │ Vercel      │
              │  netlify        │ Netlify     │
              │  deno           │ Deno Deploy │
              │  bun            │ Bun         │
              │  aws-lambda     │ Lambda      │
              │  ... 20+ more   │             │
              └───────────────────────────────┘

Nitro File-Based Routing

server/
├── api/
│   ├── hello.ts              → GET  /api/hello
│   ├── hello.post.ts         → POST /api/hello
│   ├── users/
│   │   ├── index.ts          → GET  /api/users
│   │   ├── index.post.ts     → POST /api/users
│   │   └── [id].ts           → GET  /api/users/:id
│   └── [...slug].ts          → /api/* (catch-all)
├── routes/
│   └── feed.xml.ts           → GET  /feed.xml
└── middleware/
    └── auth.ts               → Runs on all requests
// server/api/users/[id].ts
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id');
  const user = await db.users.findById(id);
  
  if (!user) {
    throw createError({
      statusCode: 404,
      message: 'User not found',
    });
  }
  
  return user;
});

// server/middleware/auth.ts
export default defineEventHandler((event) => {
  const token = getHeader(event, 'authorization');
  if (token) {
    event.context.user = verifyToken(token);
  }
});

Nitro Deployment Presets

// nitro.config.ts
export default defineNitroConfig({
  // Target platform
  preset: 'cloudflare-pages',  // or 'vercel', 'netlify', 'node', etc.
  
  // Preset-specific options
  cloudflare: {
    pages: {
      routes: {
        exclude: ['/static/*'],
      },
    },
  },
});

// BUILD COMMAND:
// npx nitro build

// OUTPUT varies by preset:

// preset: 'node-server'
// → .output/
//   ├── server/
//   │   └── index.mjs     (Node.js server)
//   └── public/           (Static files)

// preset: 'cloudflare-pages'  
// → .output/
//   ├── _worker.js        (Workers script)
//   └── public/           (Static files)

// preset: 'vercel'
// → .vercel/
//   └── output/
//       ├── functions/    (Serverless functions)
//       └── static/       (Static files)

How Nitro Achieves Universality

NITRO'S UNIVERSAL ARCHITECTURE:

┌─────────────────────────────────────────────────────────────────┐
│                     YOUR H3 HANDLERS                             │
│                                                                  │
│   // Same code for all platforms                                │
│   defineEventHandler((event) => {                               │
│     return { message: 'Hello' };                                │
│   });                                                           │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                        H3 LAYER                                  │
│                                                                  │
│   Abstracts Request/Response handling                           │
│   Provides consistent event interface                           │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                       UNENV LAYER                                │
│                                                                  │
│   Polyfills Node.js APIs for non-Node runtimes                 │
│   - process.env → runtime env vars                              │
│   - Buffer → Uint8Array                                         │
│   - fs → unstorage                                              │
│   - crypto → Web Crypto                                         │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    RUNTIME ADAPTER                               │
│                                                                  │
│   Node.js:     http.createServer(toNodeHandler(app))            │
│   Deno:        Deno.serve(toWebHandler(app))                    │
│   Workers:     export default { fetch: toWebHandler(app) }      │
│   Bun:         Bun.serve({ fetch: toWebHandler(app) })          │
└─────────────────────────────────────────────────────────────────┘

unenv: The Environment Polyfill Layer

unenv provides Node.js API compatibility for non-Node runtimes:

// THE PROBLEM:

// Your code uses Node.js APIs:
import { readFileSync } from 'fs';
import { createHash } from 'crypto';

// But Cloudflare Workers doesn't have fs or crypto modules!
// ❌ Error: Cannot find module 'fs'


// UNENV SOLUTION:

// unenv provides mock/polyfill implementations:

// fs → Mock (errors or uses storage abstraction)
// crypto → Web Crypto API wrapper
// process → Minimal process-like object
// Buffer → Uint8Array wrapper

// In nitro.config.ts:
export default defineNitroConfig({
  preset: 'cloudflare',
  // unenv automatically applies polyfills
});

// Now your Node.js code "just works" on Workers
// (with some limitations for truly Node-specific APIs)

What unenv Polyfills

NODE.JS MODULE        → UNENV REPLACEMENT
──────────────────────────────────────────────────────────────────
process              → Minimal process object with env
Buffer               → Uint8Array-based implementation
stream               → Web Streams API
crypto               → Web Crypto API
path                 → Pure JS implementation
url                  → Native URL class
util                 → Pure JS utilities
events               → EventEmitter implementation
assert               → Pure JS assert

NODE.JS MODULE        → UNENV MOCK (no-op or error)
──────────────────────────────────────────────────────────────────
fs                   → Mock (use unstorage instead)
child_process        → Mock (not available in edge)
net                  → Mock (not available in edge)
http                 → Use fetch instead

unstorage: Universal Storage

unstorage provides a unified storage API across different backends:

// UNSTORAGE BASICS:

import { createStorage } from 'unstorage';
import fsDriver from 'unstorage/drivers/fs';
import redisDriver from 'unstorage/drivers/redis';
import cloudflareKVDriver from 'unstorage/drivers/cloudflare-kv-binding';

// Development: File system
const devStorage = createStorage({
  driver: fsDriver({ base: './data' }),
});

// Production (Redis):
const prodStorage = createStorage({
  driver: redisDriver({ url: 'redis://localhost:6379' }),
});

// Cloudflare Workers:
const edgeStorage = createStorage({
  driver: cloudflareKVDriver({ binding: 'MY_KV' }),
});

// SAME API everywhere:
await storage.setItem('user:123', { name: 'John' });
const user = await storage.getItem('user:123');
await storage.removeItem('user:123');
const keys = await storage.getKeys('user:');

Storage Drivers

DRIVER                 USE CASE
──────────────────────────────────────────────────────────────────
memory                Local development, testing
fs                    Node.js file system
redis                 Production caching
cloudflare-kv-binding Cloudflare Workers KV
vercel-kv             Vercel KV storage
netlify-blobs         Netlify Blob storage
planetscale           PlanetScale database
mongodb               MongoDB collections
s3                    AWS S3 buckets
github                GitHub repository files
http                  Remote HTTP storage
lru-cache             In-memory LRU cache

ofetch: Universal Fetch

ofetch is an enhanced fetch that works everywhere:

import { ofetch } from 'ofetch';

// BASIC USAGE (same as fetch):
const data = await ofetch('/api/users');

// AUTO-PARSING:
// JSON responses automatically parsed
const users = await ofetch('/api/users');  // Returns parsed JSON

// BASE URL:
const api = ofetch.create({ baseURL: 'https://api.example.com' });
const user = await api('/users/123');

// RETRY:
const data = await ofetch('/api/data', {
  retry: 3,
  retryDelay: 1000,
});

// INTERCEPTORS:
const api = ofetch.create({
  onRequest({ options }) {
    options.headers.set('Authorization', `Bearer ${token}`);
  },
  onResponse({ response }) {
    console.log('Response:', response.status);
  },
  onResponseError({ response }) {
    if (response.status === 401) {
      logout();
    }
  },
});

// TYPE-SAFE:
interface User {
  id: number;
  name: string;
}
const user = await ofetch<User>('/api/users/123');
// user is typed as User

Comparing Server Approaches

┌────────────┬──────────────┬────────────────┬─────────────────┐
│            │   Express    │    Fastify     │       H3        │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ Runtime    │ Node.js only │ Node.js only   │ Universal       │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ Standards  │ Node http    │ Node http      │ Web standards   │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ Bundle     │ ~200KB       │ ~100KB         │ ~20KB           │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ Edge ready │ ❌           │ ❌             │ ✅              │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ TypeScript │ Manual types │ Built-in       │ Built-in        │
├────────────┼──────────────┼────────────────┼─────────────────┤
│ Ecosystem  │ Huge         │ Growing        │ UnJS + adapters │
└────────────┴──────────────┴────────────────┴─────────────────┘

Deep Dive: Understanding Universal JavaScript

Why Runtime Diversity Exists

THE JAVASCRIPT RUNTIME HISTORY:

2009: Node.js
      └─► JavaScript on servers (V8 engine)
      └─► npm ecosystem explodes
      └─► Everyone uses Node-specific APIs (fs, http, etc.)

2018: Cloudflare Workers
      └─► JavaScript at the edge (V8 isolates)
      └─► No file system, limited APIs
      └─► Web standard APIs only
      └─► Startup in milliseconds (vs seconds for Node)

2020: Deno
      └─► "Fixed" Node.js design mistakes
      └─► Web standards first
      └─► Built-in TypeScript
      └─► Secure by default (permissions)

2022: Bun
      └─► Speed-focused runtime (JavaScriptCore engine)
      └─► Node.js compatible
      └─► Built-in bundler, test runner
      └─► Faster npm, native APIs

2023+: More edge runtimes
      └─► Vercel Edge Runtime
      └─► Netlify Edge Functions
      └─► AWS Lambda@Edge
      └─► Fastly Compute


THE CHALLENGE:
- Code written for Node.js doesn't work on Workers
- Code written for Workers might not use Node.js ecosystem
- Each runtime has unique features/limitations
- Deployment target affects how you write code

THE SOLUTION: Write to web standards + abstract the differences

How V8 Isolates Enable Edge Computing

TRADITIONAL NODE.JS DEPLOYMENT:

┌─────────────────────────────────────────────────────────────────┐
│                      EC2 Instance                                │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                   Node.js Process                        │    │
│  │                                                          │    │
│  │  - Full V8 engine                                       │    │
│  │  - Complete Node.js runtime                             │    │
│  │  - File system access                                   │    │
│  │  - Network access                                       │    │
│  │  - All npm packages available                          │    │
│  │                                                          │    │
│  │  Startup: 500ms - 2s (cold start)                       │    │
│  │  Memory: 128MB - 1GB                                    │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘


CLOUDFLARE WORKERS (V8 Isolates):

┌─────────────────────────────────────────────────────────────────┐
│                   Cloudflare Edge Server                         │
│                                                                  │
│  ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐       │
│  │ Isolate 1 │ │ Isolate 2 │ │ Isolate 3 │ │ Isolate N │       │
│  │  (App A)  │ │  (App B)  │ │  (App A)  │ │  (App X)  │       │
│  │           │ │           │ │           │ │           │       │
│  │  ~128KB   │ │  ~256KB   │ │  ~128KB   │ │  ~512KB   │       │
│  │  5ms cold │ │  5ms cold │ │  0ms warm │ │  5ms cold │       │
│  └───────────┘ └───────────┘ └───────────┘ └───────────┘       │
│                                                                  │
│  Single V8 engine, many isolated contexts                       │
│  No file system (security)                                      │
│  Limited memory per isolate                                     │
│  Millisecond cold starts                                        │
└─────────────────────────────────────────────────────────────────┘


WHY ISOLATES ARE FAST:
1. V8 engine already warm (shared)
2. No process startup overhead
3. Minimal memory per request
4. Snapshot-based initialization
5. Thousands of isolates per machine

The Web Standard Request/Response Model

// THE WEB STANDARD MODEL:

// Everything is Request → Response

// INPUT: Request object
const request = new Request('https://example.com/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token',
  },
  body: JSON.stringify({ name: 'John' }),
});

// Properties available:
request.method       // 'POST'
request.url          // 'https://example.com/api/users'
request.headers      // Headers object
request.body         // ReadableStream
await request.json() // Parse body as JSON
await request.text() // Parse body as text

// OUTPUT: Response object
const response = new Response(
  JSON.stringify({ id: 1, name: 'John' }),
  {
    status: 201,
    headers: {
      'Content-Type': 'application/json',
    },
  }
);

// A UNIVERSAL HANDLER:
async function handler(request: Request): Promise<Response> {
  const url = new URL(request.url);
  
  if (url.pathname === '/api/hello') {
    return new Response('Hello World');
  }
  
  return new Response('Not Found', { status: 404 });
}

// This handler works on:
// - Cloudflare Workers: export default { fetch: handler }
// - Deno: Deno.serve(handler)
// - Bun: Bun.serve({ fetch: handler })
// - Node.js (with adapter): http.createServer(toNodeHandler(handler))

How H3 Abstracts Runtime Differences

// H3's event abstraction layer:

// H3 Event wraps different runtime request types:
interface H3Event {
  node?: {              // Node.js
    req: IncomingMessage;
    res: ServerResponse;
  };
  web?: {               // Web standard
    request: Request;
    url: URL;
  };
  context: Record<string, any>;  // Request context
}

// H3 utilities work regardless of underlying runtime:

export function getHeader(event: H3Event, name: string): string | undefined {
  // Node.js path:
  if (event.node) {
    return event.node.req.headers[name.toLowerCase()];
  }
  // Web standard path:
  if (event.web) {
    return event.web.request.headers.get(name);
  }
}

export function setHeader(event: H3Event, name: string, value: string): void {
  // Node.js path:
  if (event.node) {
    event.node.res.setHeader(name, value);
  }
  // Web standard path:
  if (event._responseHeaders) {
    event._responseHeaders.set(name, value);
  }
}

// YOUR CODE just uses the abstraction:
defineEventHandler((event) => {
  const auth = getHeader(event, 'authorization');
  setHeader(event, 'X-Custom', 'value');
  return { message: 'Hello' };
});
// Works on Node, Deno, Workers, Bun without changes

Nitro's Build Process Deep Dive

NITRO BUILD PIPELINE:

INPUT:
server/
├── api/
│   └── users.ts     // H3 handlers
├── routes/
│   └── index.ts
├── middleware/
│   └── auth.ts
└── plugins/
    └── database.ts

STEP 1: SCAN
─────────────────────────────────────────────────────────────────
- Find all route files
- Parse route patterns from file names
- Discover middleware and plugins
- Build route table

Route Table:
[
  { path: '/api/users', handler: './api/users.ts', method: 'get' },
  { path: '/', handler: './routes/index.ts', method: 'get' },
]


STEP 2: BUNDLE WITH ROLLUP
─────────────────────────────────────────────────────────────────
- Bundle all handlers into single file
- Tree-shake unused code
- Apply preset-specific transforms

rollup.config:
  input: virtual:nitro-entry
  output: format based on preset (esm, cjs, iife)


STEP 3: APPLY UNENV TRANSFORMS
─────────────────────────────────────────────────────────────────
- Replace Node.js imports with polyfills
- Inject runtime-specific code
- Remove unavailable APIs

Example transforms:
  import fs from 'fs'           → import fs from 'unenv/runtime/node/fs'
  import crypto from 'crypto'   → import crypto from 'unenv/runtime/node/crypto'


STEP 4: GENERATE RUNTIME ENTRY
─────────────────────────────────────────────────────────────────

// Node.js preset:
import { createServer } from 'http';
import { toNodeHandler } from 'h3';
import { app } from './app.mjs';
createServer(toNodeHandler(app)).listen(3000);

// Cloudflare Workers preset:
import { toWebHandler } from 'h3';
import { app } from './app.mjs';
export default { fetch: toWebHandler(app) };

// Deno preset:
import { toWebHandler } from 'h3';
import { app } from './app.mjs';
Deno.serve(toWebHandler(app));


STEP 5: OUTPUT
─────────────────────────────────────────────────────────────────
.output/
├── server/
│   ├── index.mjs        // Entry point
│   └── chunks/          // Code-split chunks
└── public/              // Static assets

Edge vs Serverless vs Traditional

┌─────────────────────────────────────────────────────────────────┐
│                    TRADITIONAL SERVER                            │
├─────────────────────────────────────────────────────────────────┤
│  Location:    Single region (us-east-1)                         │
│  Runtime:     Node.js process (long-running)                    │
│  Cold start:  0ms (always warm)                                 │
│  Scaling:     Manual or auto-scaling groups                     │
│  Cost:        Pay for uptime                                    │
│  APIs:        Full Node.js (fs, net, child_process)            │
│                                                                  │
│  Best for:    WebSocket, long computations, file processing    │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                      SERVERLESS                                  │
├─────────────────────────────────────────────────────────────────┤
│  Location:    Single or few regions                             │
│  Runtime:     Node.js container (ephemeral)                     │
│  Cold start:  100ms - 3s                                        │
│  Scaling:     Automatic, per-request                            │
│  Cost:        Pay per invocation                                │
│  APIs:        Full Node.js (fs, net, etc.)                     │
│                                                                  │
│  Best for:    Infrequent traffic, variable load, APIs          │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                        EDGE                                      │
├─────────────────────────────────────────────────────────────────┤
│  Location:    Global (200+ locations)                           │
│  Runtime:     V8 isolate (lightweight)                          │
│  Cold start:  < 10ms                                            │
│  Scaling:     Automatic, instant                                │
│  Cost:        Pay per request (very cheap)                      │
│  APIs:        Web standards only (limited)                      │
│                                                                  │
│  Best for:    Low latency, personalization, A/B tests, auth    │
└─────────────────────────────────────────────────────────────────┘

HYBRID APPROACH (Modern best practice):
┌─────────────────────────────────────────────────────────────────┐
│  Edge Layer:           Auth, routing, personalization           │
│       ↓                                                         │
│  Serverless Layer:     API routes, database queries             │
│       ↓                                                         │
│  Traditional Layer:    WebSocket, heavy computation             │
└─────────────────────────────────────────────────────────────────┘

Universal Storage Patterns

// THE STORAGE ABSTRACTION PATTERN:

// Different platforms have different storage:
// - Node.js: File system, Redis, PostgreSQL
// - Workers: KV, D1, R2
// - Vercel: Vercel KV, Postgres
// - AWS: S3, DynamoDB

// UNSTORAGE provides one API:

import { createStorage } from 'unstorage';

// Configuration varies by environment:
function createAppStorage() {
  if (process.env.CLOUDFLARE) {
    return createStorage({
      driver: cloudflareKVBindingDriver({ binding: 'CACHE' }),
    });
  }
  
  if (process.env.VERCEL) {
    return createStorage({
      driver: vercelKVDriver({ /* ... */ }),
    });
  }
  
  // Development / Node.js
  return createStorage({
    driver: fsDriver({ base: './.data' }),
  });
}

// YOUR CODE uses the same API everywhere:
const storage = createAppStorage();

export async function getUser(id: string) {
  // Check cache first
  const cached = await storage.getItem(`user:${id}`);
  if (cached) return cached;
  
  // Fetch from database
  const user = await db.users.findById(id);
  
  // Cache for 5 minutes
  await storage.setItem(`user:${id}`, user, { ttl: 300 });
  
  return user;
}

Practical: Building a Universal API

// A complete universal API example:

// server/api/posts/[id].ts
export default defineEventHandler(async (event) => {
  // Works on any runtime
  const id = getRouterParam(event, 'id');
  const method = event.method;
  
  // Universal storage
  const storage = useStorage('cache');
  
  if (method === 'GET') {
    // Check cache
    const cached = await storage.getItem(`post:${id}`);
    if (cached) {
      setHeader(event, 'X-Cache', 'HIT');
      return cached;
    }
    
    // Fetch from database (works on edge via fetch)
    const post = await ofetch(`https://api.example.com/posts/${id}`);
    
    // Cache for 1 hour
    await storage.setItem(`post:${id}`, post, { ttl: 3600 });
    
    return post;
  }
  
  if (method === 'PUT') {
    const body = await readBody(event);
    
    // Update via API
    const updated = await ofetch(`https://api.example.com/posts/${id}`, {
      method: 'PUT',
      body,
    });
    
    // Invalidate cache
    await storage.removeItem(`post:${id}`);
    
    return updated;
  }
  
  throw createError({ statusCode: 405, message: 'Method not allowed' });
});

// This single file works on:
// - Node.js server
// - Cloudflare Workers/Pages
// - Vercel Serverless/Edge
// - Netlify Functions/Edge
// - Deno Deploy
// - AWS Lambda
// - And more...

// Just change the Nitro preset in config!

When to Use What Runtime

DECISION MATRIX:

Use EDGE when:
├─► Request latency is critical (< 50ms)
├─► Doing auth, redirects, header manipulation
├─► A/B testing, personalization
├─► Simple data transformations
└─► Global user base

Use SERVERLESS when:
├─► Database connections needed
├─► Moderate computation
├─► Node.js ecosystem required
├─► Moderate traffic with spikes
└─► Cost optimization for variable load

Use TRADITIONAL when:
├─► WebSocket connections
├─► Long-running processes
├─► Heavy computation (ML, video)
├─► Predictable high traffic
└─► Full Node.js APIs needed

Use HYBRID when:
├─► Different parts have different needs
├─► Global reach with complex backend
├─► Optimizing for both latency and capability
└─► Most production applications!

For Framework Authors: Building Universal Runtime Systems

Implementation Note: The patterns and code examples below represent one proven approach to building universal runtime systems. The adapter pattern shown here is inspired by Nitro/H3 but other approaches exist—SvelteKit uses a similar adapter system, while Remix uses a different deployment abstraction. The direction shown here provides portable patterns based on web standards. Adapt based on which runtimes you need to support and your polyfill strategy.

Creating Runtime Adapters

// NITRO-STYLE ADAPTER PATTERN

class RuntimeAdapter {
  constructor(name, options = {}) {
    this.name = name;
    this.options = options;
  }
  
  // Transform the universal handler for target runtime
  async adapt(handler, buildOutput) {
    throw new Error('Must implement adapt()');
  }
  
  // Generate entry point for runtime
  generateEntry() {
    throw new Error('Must implement generateEntry()');
  }
  
  // Get runtime-specific build config
  getBuildConfig() {
    return {};
  }
}

// Node.js Adapter
class NodeAdapter extends RuntimeAdapter {
  constructor(options = {}) {
    super('node', options);
    this.port = options.port || 3000;
  }
  
  generateEntry() {
    return `
import { createServer } from 'node:http';
import { toNodeHandler } from 'h3';
import { app } from '#internal/app';

const handler = toNodeHandler(app);
const server = createServer(handler);

server.listen(${this.port}, () => {
  console.log(\`Server running on http://localhost:${this.port}\`);
});
    `.trim();
  }
  
  getBuildConfig() {
    return {
      target: 'node',
      external: ['node:*'],
      minify: false,
    };
  }
}

// Cloudflare Workers Adapter
class CloudflareAdapter extends RuntimeAdapter {
  constructor(options = {}) {
    super('cloudflare', options);
  }
  
  generateEntry() {
    return `
import { toWebHandler } from 'h3';
import { app } from '#internal/app';

const handler = toWebHandler(app);

export default {
  async fetch(request, env, ctx) {
    // Inject environment bindings
    globalThis.__env__ = env;
    
    return handler(request, {
      waitUntil: (p) => ctx.waitUntil(p),
      passThroughOnException: () => ctx.passThroughOnException(),
    });
  },
};
    `.trim();
  }
  
  getBuildConfig() {
    return {
      target: 'webworker',
      format: 'esm',
      external: [],
      minify: true,
      define: {
        'process.env.NODE_ENV': '"production"',
      },
    };
  }
}

// Vercel Edge Adapter
class VercelEdgeAdapter extends RuntimeAdapter {
  constructor(options = {}) {
    super('vercel-edge', options);
  }
  
  generateEntry() {
    return `
import { toWebHandler } from 'h3';
import { app } from '#internal/app';

const handler = toWebHandler(app);

export const config = { runtime: 'edge' };

export default function (request) {
  return handler(request);
}
    `.trim();
  }
  
  // Generate vercel.json config
  generateConfig(routes) {
    return {
      version: 3,
      routes: routes.map(r => ({
        src: r.pattern,
        dest: r.handler,
      })),
    };
  }
}

// AWS Lambda Adapter
class LambdaAdapter extends RuntimeAdapter {
  constructor(options = {}) {
    super('lambda', options);
  }
  
  generateEntry() {
    return `
import { toWebHandler } from 'h3';
import { app } from '#internal/app';

const handler = toWebHandler(app);

export async function handler(event, context) {
  // Convert Lambda event to Web Request
  const request = lambdaToRequest(event);
  
  // Handle request
  const response = await handler(request);
  
  // Convert Web Response to Lambda response
  return responseToLambda(response);
}

function lambdaToRequest(event) {
  const url = \`https://\${event.headers.host}\${event.rawPath}\`;
  return new Request(url, {
    method: event.requestContext.http.method,
    headers: event.headers,
    body: event.body,
  });
}

async function responseToLambda(response) {
  return {
    statusCode: response.status,
    headers: Object.fromEntries(response.headers),
    body: await response.text(),
    isBase64Encoded: false,
  };
}
    `.trim();
  }
}

Implementing Polyfill Presets

// UNENV-STYLE POLYFILL SYSTEM

class EnvironmentPreset {
  constructor(name) {
    this.name = name;
    this.polyfills = new Map();
    this.aliases = new Map();
    this.globals = new Map();
    this.external = new Set();
  }
  
  // Register a polyfill for a module
  polyfill(module, implementation) {
    this.polyfills.set(module, implementation);
    return this;
  }
  
  // Alias one module to another
  alias(from, to) {
    this.aliases.set(from, to);
    return this;
  }
  
  // Define global injection
  global(name, value) {
    this.globals.set(name, value);
    return this;
  }
  
  // Mark module as external
  external(module) {
    this.external.add(module);
    return this;
  }
  
  // Generate bundler config
  toBundlerConfig() {
    return {
      alias: Object.fromEntries(this.aliases),
      define: Object.fromEntries(
        [...this.globals].map(([k, v]) => [`globalThis.${k}`, v])
      ),
      external: [...this.external],
    };
  }
}

// Node preset for edge runtimes
const nodePresetForEdge = new EnvironmentPreset('node-edge')
  // Polyfill Node.js built-ins with web-compatible versions
  .polyfill('node:buffer', 'unenv/runtime/node/buffer')
  .polyfill('node:crypto', 'unenv/runtime/node/crypto')
  .polyfill('node:stream', 'unenv/runtime/node/stream')
  .polyfill('node:events', 'unenv/runtime/node/events')
  .polyfill('node:util', 'unenv/runtime/node/util')
  .polyfill('node:path', 'unenv/runtime/node/path')
  .polyfill('node:url', 'unenv/runtime/node/url')
  
  // Stub Node.js modules not available on edge
  .polyfill('node:fs', 'unenv/runtime/mock/empty')
  .polyfill('node:child_process', 'unenv/runtime/mock/empty')
  .polyfill('node:net', 'unenv/runtime/mock/empty')
  .polyfill('node:tls', 'unenv/runtime/mock/empty')
  
  // Global injections
  .global('process', 'unenv/runtime/node/process')
  .global('Buffer', 'unenv/runtime/node/buffer')
  
  // Aliases
  .alias('buffer', 'node:buffer')
  .alias('stream', 'node:stream')
  .alias('events', 'node:events');

// Create polyfill bundle
function createPolyfillBundle(preset) {
  const imports = [];
  const exports = [];
  
  for (const [module, impl] of preset.polyfills) {
    const safeName = module.replace(/[^a-z0-9]/gi, '_');
    imports.push(`import * as ${safeName} from '${impl}';`);
    exports.push(`'${module}': ${safeName},`);
  }
  
  return `
${imports.join('\n')}

export const polyfills = {
  ${exports.join('\n  ')}
};

// Auto-inject polyfills
for (const [name, impl] of Object.entries(polyfills)) {
  globalThis.__modules__ = globalThis.__modules__ || {};
  globalThis.__modules__[name] = impl;
}
  `.trim();
}

Building a Universal Storage Adapter

// UNSTORAGE-STYLE DRIVER SYSTEM

class StorageDriver {
  constructor(options = {}) {
    this.options = options;
  }
  
  // Required methods
  async hasItem(key) { throw new Error('Not implemented'); }
  async getItem(key) { throw new Error('Not implemented'); }
  async setItem(key, value) { throw new Error('Not implemented'); }
  async removeItem(key) { throw new Error('Not implemented'); }
  async getKeys(base) { throw new Error('Not implemented'); }
  async clear() { throw new Error('Not implemented'); }
  
  // Optional methods
  async getMeta(key) { return {}; }
  async setMeta(key, meta) {}
}

// Memory driver (works everywhere)
class MemoryDriver extends StorageDriver {
  constructor(options = {}) {
    super(options);
    this.data = new Map();
    this.meta = new Map();
  }
  
  async hasItem(key) {
    return this.data.has(key);
  }
  
  async getItem(key) {
    return this.data.get(key) ?? null;
  }
  
  async setItem(key, value) {
    this.data.set(key, value);
    this.meta.set(key, { mtime: Date.now() });
  }
  
  async removeItem(key) {
    this.data.delete(key);
    this.meta.delete(key);
  }
  
  async getKeys(base = '') {
    return [...this.data.keys()].filter(k => k.startsWith(base));
  }
  
  async clear() {
    this.data.clear();
    this.meta.clear();
  }
}

// Cloudflare KV driver
class CloudflareKVDriver extends StorageDriver {
  constructor(options) {
    super(options);
    this.binding = options.binding; // KV namespace binding name
  }
  
  getKV() {
    // Access from Cloudflare env
    return globalThis.__env__?.[this.binding];
  }
  
  async hasItem(key) {
    return (await this.getKV().get(key)) !== null;
  }
  
  async getItem(key) {
    const value = await this.getKV().get(key, 'text');
    return value ? JSON.parse(value) : null;
  }
  
  async setItem(key, value) {
    await this.getKV().put(key, JSON.stringify(value), {
      expirationTtl: this.options.ttl,
    });
  }
  
  async removeItem(key) {
    await this.getKV().delete(key);
  }
  
  async getKeys(base = '') {
    const list = await this.getKV().list({ prefix: base });
    return list.keys.map(k => k.name);
  }
}

// Universal storage factory
function createStorage(options = {}) {
  const drivers = new Map();
  
  return {
    // Mount driver at prefix
    mount(prefix, driver) {
      drivers.set(prefix, driver);
    },
    
    // Get driver for key
    getDriver(key) {
      for (const [prefix, driver] of drivers) {
        if (key.startsWith(prefix)) {
          return { driver, key: key.slice(prefix.length) };
        }
      }
      return { driver: drivers.get('') || new MemoryDriver(), key };
    },
    
    // Unified interface
    async getItem(key) {
      const { driver, key: k } = this.getDriver(key);
      return driver.getItem(k);
    },
    
    async setItem(key, value, opts) {
      const { driver, key: k } = this.getDriver(key);
      return driver.setItem(k, value, opts);
    },
    
    async removeItem(key) {
      const { driver, key: k } = this.getDriver(key);
      return driver.removeItem(k);
    },
  };
}

Implementing Cross-Runtime Crypto

// WEB CRYPTO ABSTRACTION (UNCRYPTO-STYLE)

// Universal crypto that works on Node, Deno, Bun, Edge
const crypto = globalThis.crypto || (await import('node:crypto')).webcrypto;

class UniversalCrypto {
  // Random bytes
  static getRandomBytes(length) {
    const bytes = new Uint8Array(length);
    crypto.getRandomValues(bytes);
    return bytes;
  }
  
  // Generate UUID
  static randomUUID() {
    return crypto.randomUUID();
  }
  
  // Hash with SHA-256
  static async sha256(data) {
    const encoder = new TextEncoder();
    const buffer = typeof data === 'string' ? encoder.encode(data) : data;
    const hash = await crypto.subtle.digest('SHA-256', buffer);
    return new Uint8Array(hash);
  }
  
  // Hash to hex string
  static async sha256Hex(data) {
    const hash = await this.sha256(data);
    return Array.from(hash)
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  }
  
  // HMAC signing
  static async hmacSign(key, data) {
    const encoder = new TextEncoder();
    const keyMaterial = await crypto.subtle.importKey(
      'raw',
      encoder.encode(key),
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign']
    );
    
    const signature = await crypto.subtle.sign(
      'HMAC',
      keyMaterial,
      encoder.encode(data)
    );
    
    return new Uint8Array(signature);
  }
  
  // AES-GCM encryption
  static async encrypt(key, data) {
    const encoder = new TextEncoder();
    const iv = this.getRandomBytes(12);
    
    const keyMaterial = await crypto.subtle.importKey(
      'raw',
      await this.sha256(key),
      'AES-GCM',
      false,
      ['encrypt']
    );
    
    const encrypted = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv },
      keyMaterial,
      encoder.encode(data)
    );
    
    // Combine IV + encrypted data
    const result = new Uint8Array(iv.length + encrypted.byteLength);
    result.set(iv);
    result.set(new Uint8Array(encrypted), iv.length);
    
    return result;
  }
  
  // AES-GCM decryption
  static async decrypt(key, encryptedData) {
    const iv = encryptedData.slice(0, 12);
    const data = encryptedData.slice(12);
    
    const keyMaterial = await crypto.subtle.importKey(
      'raw',
      await this.sha256(key),
      'AES-GCM',
      false,
      ['decrypt']
    );
    
    const decrypted = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv },
      keyMaterial,
      data
    );
    
    return new TextDecoder().decode(decrypted);
  }
}

// Base64 URL encoding (works everywhere)
function base64UrlEncode(data) {
  const bytes = typeof data === 'string' 
    ? new TextEncoder().encode(data) 
    : data;
  
  const base64 = btoa(String.fromCharCode(...bytes));
  return base64
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

function base64UrlDecode(str) {
  const base64 = str
    .replace(/-/g, '+')
    .replace(/_/g, '/');
  
  const binary = atob(base64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  
  return bytes;
}

Building Universal Fetch Wrapper

// OFETCH-STYLE UNIVERSAL FETCH

function createFetch(defaults = {}) {
  return async function $fetch(url, options = {}) {
    const config = {
      ...defaults,
      ...options,
      headers: {
        ...defaults.headers,
        ...options.headers,
      },
    };
    
    // Resolve URL
    const resolvedUrl = config.baseURL 
      ? new URL(url, config.baseURL).toString() 
      : url;
    
    // Prepare request
    const fetchOptions = {
      method: config.method || 'GET',
      headers: config.headers,
      signal: config.signal,
    };
    
    // Handle body
    if (config.body !== undefined) {
      if (typeof config.body === 'object' && 
          !(config.body instanceof FormData) &&
          !(config.body instanceof ReadableStream)) {
        fetchOptions.body = JSON.stringify(config.body);
        fetchOptions.headers['Content-Type'] = 'application/json';
      } else {
        fetchOptions.body = config.body;
      }
    }
    
    // Add query params
    if (config.query) {
      const separator = resolvedUrl.includes('?') ? '&' : '?';
      const params = new URLSearchParams(config.query).toString();
      url = resolvedUrl + separator + params;
    }
    
    // Retry logic
    const maxRetries = config.retry ?? 1;
    let lastError;
    
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        // Interceptors - request
        if (config.onRequest) {
          await config.onRequest({ request: fetchOptions, options: config });
        }
        
        const response = await fetch(resolvedUrl, fetchOptions);
        
        // Interceptors - response
        if (config.onResponse) {
          await config.onResponse({ response, options: config });
        }
        
        // Handle errors
        if (!response.ok) {
          const error = new FetchError(response.statusText);
          error.response = response;
          error.status = response.status;
          
          if (config.onResponseError) {
            await config.onResponseError({ error, response, options: config });
          }
          
          throw error;
        }
        
        // Parse response
        const contentType = response.headers.get('content-type') || '';
        
        if (config.responseType === 'stream') {
          return response.body;
        }
        
        if (config.responseType === 'blob') {
          return response.blob();
        }
        
        if (config.responseType === 'arrayBuffer') {
          return response.arrayBuffer();
        }
        
        if (contentType.includes('application/json')) {
          return response.json();
        }
        
        return response.text();
        
      } catch (error) {
        lastError = error;
        
        // Don't retry on certain errors
        if (error.status && error.status < 500) {
          throw error;
        }
        
        // Wait before retry
        if (attempt < maxRetries) {
          await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
        }
      }
    }
    
    throw lastError;
  };
}

class FetchError extends Error {
  constructor(message) {
    super(message);
    this.name = 'FetchError';
  }
}

// Create default instance
const $fetch = createFetch();

// Create with defaults
const api = createFetch({
  baseURL: 'https://api.example.com',
  headers: {
    'Authorization': 'Bearer token',
  },
  retry: 2,
});

Related Skills

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

web-app-architectures

No summary provided by upstream source.

Repository SourceNeeds Review
General

build-pipelines-bundling

No summary provided by upstream source.

Repository SourceNeeds Review
General

seo-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review