Migrating from Node.js

1. Environment and process globals

In Node.js process is ambient. In esrun, env, args, cwd and exit are imported from runtime:process, and reading any of them needs --allow-env.

JavaScript
// ❌ Node.js
const port = process.env.PORT || 8080;
const cliArgs = process.argv.slice(2);
const currentDir = process.cwd();
process.exit(0);

// ✅ esrun
import { env, args, cwd, exit } from "runtime:process";

const port = env.PORT || 8080;
const cliArgs = args; // Script arguments (excluding binary/script names)
const currentDir = cwd();
exit(0);

2. Filesystem operations (node:fs)

Synchronous and callback node:fs calls become the promise-based, Blob-like surface in runtime:fs. Every path is resolved inside the project root jail, and reads and writes are two separate grants.

JavaScript
// ❌ Node.js
import { readFileSync, writeFileSync, appendFileSync, existsSync } from "node:fs";

if (existsSync("./config.json")) {
  const raw = readFileSync("./config.json", "utf8");
  writeFileSync("./out.txt", "processed");
  appendFileSync("./app.log", "done\n");
}

// ✅ esrun
import { file, write } from "runtime:fs";

const cfg = file("./config.json");
if (await cfg.exists()) {
  const text = await cfg.text();       // UTF-8 text
  const json = await cfg.json();       // Parsed JSON
  const bytes = await cfg.bytes();     // Uint8Array

  await write("./out.txt", "processed");
  await write("./app.log", "done\n", { append: true });
}

3. Path resolution (__dirname, __filename, node:path)

__dirname and __filename are CommonJS, and import.meta here carries url and resolve only. Derive them from import.meta.url with runtime:path.

JavaScript
// ❌ Node.js (CommonJS)
const path = require("path");
const file = path.join(__dirname, "data", "app.db");

// ✅ esrun
import { join, dirname, fromFileURL } from "runtime:path";

const __filename = fromFileURL(import.meta.url);
const __dirname = dirname(__filename);
const dbPath = join(__dirname, "data", "app.db");

4. Subprocesses (node:child_process)

Replace child_process.spawn() or exec() with Command from runtime:system.

JavaScript
// ❌ Node.js
import { spawn } from "node:child_process";
const child = spawn("git", ["status"]);

// ✅ esrun (Collect output)
import { Command } from "runtime:system";

const { code, stdout, stderr } = await new Command("git", {
  args: ["status"],
}).output();

const outputText = new TextDecoder().decode(stdout);
JavaScript
// ✅ esrun (Stream stdin / stdout with Web Streams)
import { Command } from "runtime:system";

const child = await new Command("ffmpeg", {
  args: ["-i", "pipe:0", "-f", "mp3", "pipe:1"],
  stdin: request.body, // Web ReadableStream / Response body streams directly in
}).spawn();

return new Response(child.stdout); // Web ReadableStream streams directly out

5. Binary data and buffers (node:buffer)

Buffer becomes Uint8Array, with TextEncoder/TextDecoder for text and btoa()/atob() for base64.

JavaScript
// ❌ Node.js
const buf = Buffer.from("Hello World", "utf8");
const b64 = buf.toString("base64");
const back = Buffer.from(b64, "base64").toString("utf8");

// ✅ esrun (Standard Web APIs)
const encoder = new TextEncoder();
const decoder = new TextDecoder();

const bytes = encoder.encode("Hello World"); // Uint8Array
const b64 = btoa(decoder.decode(bytes));
const back = decoder.decode(encoder.encode(atob(b64)));

6. Cryptography and hashing (node:crypto)

createHash, createHmac and timingSafeEqual become runtime:hashing; keys, signatures and ciphers become crypto.subtle; randomUUID and randomBytes become crypto.

JavaScript
// ❌ Node.js
import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
const id = randomUUID();
const digest = createHash("sha256").update("data").digest("hex");
const mac = createHmac("sha256", secret).update(body).digest("hex");

// ✅ esrun
import { hash, Hasher, hmac, timingSafeEqual } from "runtime:hashing";
const id = crypto.randomUUID();
const digest = hash("sha256", "data", "hex");
const mac = hmac("sha256", secret, body, "hex");

// Chunked, for anything too large to hold at once.
const h = new Hasher("sha256");
for await (const chunk of stream) h.update(chunk);
h.digest("hex");
Nodeesrun
createHash(alg)hash(alg, data, "hex") or new Hasher(alg)
createHmac(alg, key)hmac(alg, key, data, "hex")
timingSafeEqual(a, b)timingSafeEqual(a, b)
scrypt, pbkdf2password.hash() / crypto.subtle.deriveBits
bcrypt, argon2 (npm)password.hash() / password.verify()
randomUUID, randomBytescrypto.randomUUID(), crypto.getRandomValues()
createSign, createCipheriv, generateKeyPaircrypto.subtle
JavaScript
// Passwords: no npm package, and the parameters travel with the hash.
import { password } from "runtime:hashing";

const stored = await password.hash(input);   // argon2id
if (await password.verify(input, user.hash) && password.needsRehash(user.hash)) {
  user.hash = await password.hash(input);
}
Last updated on
Edit this page