universal-javascript-runtimes
npx skills add https://github.com/farming-labs/fm-skills --skill universal-javascript-runtimes
Agent 安装分布
Skill 文档
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
- See meta-frameworks-overview for framework deployment options
- See rendering-patterns for SSR on edge
- See build-pipelines-bundling for build output formats