Skip to content

node:fs and node:path ​

A small part of Node's standard library is built in, implemented in the runtime with Node's semantics. Import it the usual way, in named, default or namespace form.

ts
// node:fs (sync and promises) and node:path. The path includes the pid so concurrent runs of
// the same program (the test harness runs Node and two native builds at once) do not collide.
import { writeFileSync, readFileSync, existsSync, unlinkSync } from "node:fs";
import { readFile } from "node:fs/promises";
import * as path from "node:path";

const file = path.join("/tmp", `chad-docs-${process.pid}`, "..", `notes-${process.pid}.txt`);
console.log(path.basename(file).startsWith("notes-"), path.extname(file), path.dirname(file));

writeFileSync(file, "line one\nline two\n");
const lines = readFileSync(file, "utf8").trimEnd().split("\n");
console.log(lines.length, lines[1]);

async function main(): Promise<void> {
  const text = await readFile(file, "utf8");
  console.log("async read", text.length, "bytes");
  unlinkSync(file);
  console.log("exists after unlink:", existsSync(file));
}
main();
text
true .txt /tmp
2 line two
async read 18 bytes
exists after unlink: false

What is available ​

The ambient declarations in stdlib/globals.d.ts are the whole surface; importing anything else fails to typecheck (CS0001).

ModuleFunctions
node:fsreadFileSync(path, "utf8"), writeFileSync, appendFileSync, existsSync, unlinkSync
node:fs/promisesreadFile(path, "utf8"), writeFile, appendFile, unlink
node:pathjoin, resolve, normalize, dirname, basename, extname, isAbsolute

A failing file operation (reading a missing file, say) throws, as in Node, so try/catch is how you handle it; existsSync is the non-throwing check.

process ​

The global process provides process.argv.slice(2) (the arguments after the program), process.exit(code) and process.pid. Only the exact expression process.argv.slice(2) is accepted (CS1229): Node's argv[0] and argv[1] are the node binary and the script path, which a compiled binary does not have. With that slice, the same arguments mean the same thing to both.

ts
// @args: ada lin
const args = process.argv.slice(2);
if (args.length === 0) {
  console.log("usage: greet <name>...");
  process.exit(2);
}
for (const name of args) console.log(`hello ${name}`);
sh
$ bin/chad build argv.ts -o greet && ./greet ada lin
text
hello ada
hello lin

Planned: more of fs (directories, stat), node:process as an importable module, and environment variables.

Next: console.log.