node-compat modules
The built-in modules Lifo implements — fs, http, crypto, stream, child_process, process, and more — and the behaviors that matter.
require("node:*") (and the bare aliases) resolve to Lifo's own implementations,
written against the kernel. They aren't the real Node modules and
they aren't WASM — they're TypeScript that reproduces the observable behavior
real packages depend on.
What's implemented
- Filesystem —
fsandfs/promisesover the VFS:readFile/writeFile,readdir(withwithFileTypes),stat/lstat,mkdir,rm/rmdir,openflags,createReadStream/createWriteStream, andwatch/watchFile(returning a realStatWatcher) wired to VFS watch events. - Networking —
http/https(servers that register on the port registry, and a client that goes through the proxying fetch), plusnet/tlssockets and adnsresolver.dgramis a no-op shim (enough for tools that probe mDNS). - Process & OS —
process(argv, env, cwd,exit, stdio,hrtime,nextTick),os(platform()returns"lifo",cpus()returns 1 so worker-forking tools run in-band),child_process(exec/execFile/spawnbridged to real VM commands viaexecuteCapture). - Streams & encoding —
stream(Readable/Writable/Transform,finished,pipeline, web-stream interop),stream/promises,string_decoder,buffer(aBufferwhosesubarray/sliceshare memory like Node's),zlib. - Crypto — hashing (md5, sha family),
randomBytes, and digest encodings includingbase64url(which ubiquitous tools likewrite-file-atomicdepend on for temp filenames). - Runtime plumbing —
events(a function-constructorEventEmitter, soEventEmitter.call(this)works),util(+types,debuglog),readline(with real keypress parsing),tty,timers(returningTimeoutobjects withref/unref/refresh),module(a constructableModuleclass),async_hooks,diagnostics_channel,console,assert,url,path.
Why the small behaviors matter
Most of the effort in the compat layer is in the details that a real package trips over. A few representative examples:
Buffer#subarraymust share memory (like Node), ormsgpackr'sencodeIntowrites into a throwaway buffer and Metro's transform cache decodes garbage.cryptodigests must supportbase64url, or atomic file writes name their temp file with binary garbage and every writeENOENTs.child_processstdout must emitcloseafterend, or Metro's file crawler waits forever and the first bundle hangs.os.cpus()returning 1 makes jest-worker/Metro run in-band, avoiding a fakechild_processfork that would hang.
These aren't hacks so much as the actual contract of the Node API. Getting them
right is what lets create-expo-app, Vite, and Metro run unmodified.
Networking specifics
Because a browser tab can't make arbitrary cross-origin requests, the compat
layer routes outbound HTTP through a CORS proxy for known
non-CORS hosts (e.g. api.expo.dev). This is injected as the fetch a module
receives, and require("fetch-nodeshim") is served from the same stack, so a
package's networking is transparently proxied without overriding
globalThis.fetch. See Networking and
Tunnelling.
Note
The surface grows as real tools demand it. The roadmap extends it toward serverless handlers and broader ecosystem coverage, and adds pluggable WASM runtimes for the cases that genuinely need a native binary (ffmpeg, Python).