Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1049fac
Fix browser polyfills: bind process/Buffer, util and process bugs, na…
robobun Sep 6, 2026
776cab9
test: check the AssertionError by instanceof, not by constructor name
robobun Sep 6, 2026
5331c88
node-fallbacks: shorten comments
robobun Sep 6, 2026
29b536c
node-fallbacks: add promises to the timers default export
robobun Sep 6, 2026
bf699b0
node-fallbacks: build events as CommonJS, add default exports to os, …
robobun Sep 8, 2026
a52540c
node-fallbacks: keep util and path free of node globals, lint the pol…
robobun Sep 22, 2026
24605c9
node-fallbacks: pick the CommonJS format from the source, not from a …
robobun Sep 22, 2026
1e8792a
node-fallbacks: drain nextTick in batches so a finished tick can be c…
robobun Sep 22, 2026
30db02e
node-fallbacks: shorten a comment in process.js
robobun Sep 22, 2026
b502c71
node-fallbacks: give the nextTick TypeError a code; the chain test fo…
robobun Sep 22, 2026
59851bb
node-fallbacks: describe a bad nextTick callback without coercing it
robobun Sep 22, 2026
24725ef
node-fallbacks: add the polyfills to the files that `bun lint` checks
robobun Nov 9, 2026
fdcaeb9
test: a stream's ticks use the queue of the process polyfill
robobun Nov 9, 2026
5afaca2
node-fallbacks: the timers polyfill does not pass its own `this` to t…
robobun Nov 9, 2026
fb4e39e
node-fallbacks: give the timers polyfill's setTimeout and setImmediat…
robobun Nov 9, 2026
345c9cd
node-fallbacks: add `constants` to the zlib polyfill
robobun Nov 10, 2026
53dd2e6
Merge #41524 (bundle the polyfills for the browser target)
robobun Nov 10, 2026
fa96952
test: a stream's ticks no longer run on the host process
robobun Nov 10, 2026
67b3573
node-fallbacks: process.emitWarning prints an Error with its own name
robobun Nov 10, 2026
cb72bc9
Merge #41524 (fill shared memory in randomFill and randomFillSync)
robobun Nov 10, 2026
b00eb3c
node-fallbacks: keep the host's process, no binding named after a glo…
robobun Nov 11, 2026
eedb9c6
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 11, 2026
84096bc
test: a dev server client module uses the stream, assert and zlib pol…
robobun Nov 11, 2026
c33f838
node-fallbacks: a host process needs nextTick, querystring and util k…
robobun Nov 11, 2026
3516579
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 11, 2026
6141181
Merge #41524 (node's argument rules for the hand-written crypto funct…
robobun Nov 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bunfig.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ ignorePatterns = [
]

[lint]
# `bun lint` lints src/js.
# `bun lint` lints src/js and the browser polyfills in src/node-fallbacks.
config = "oxlint.json"
ignorePatterns = ["/*", "!/src", "/src/*", "!/src/js"]
ignorePatterns = ["/*", "!/src", "/src/*", "!/src/js", "!/src/node-fallbacks"]
17 changes: 17 additions & 0 deletions oxlint.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,23 @@
"rules": {
"bun/no-unused-expressions": "off"
}
},
{
// Browser polyfills that `bun build --target=browser` inlines into the
// user's bundle. They are plain JS, not JSC builtins: a `$intrinsic`,
// a node global such as `process` or `Buffer`, or any other undeclared
// name is a ReferenceError in the browser. Read `globalThis.process`.
"files": ["src/node-fallbacks/*.js"],
"env": { "browser": true, "es2024": true },
"rules": {
"no-undef": "error",
"no-unused-vars": "off",
"no-useless-rename": "off"
}
},
{
"files": ["src/node-fallbacks/assert.js", "src/node-fallbacks/events.js", "src/node-fallbacks/stream.js"],
"env": { "commonjs": true }
}
]
}
27 changes: 2 additions & 25 deletions src/node-fallbacks/assert.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,2 @@
import * as assert from "./node_modules/assert";

export const {
AssertionError,
CallTracker,
deepEqual,
deepStrictEqual,
doesNotMatch,
doesNotReject,
doesNotThrow,
equal,
fail,
ifError,
match,
notDeepEqual,
notDeepStrictEqual,
notEqual,
notStrictEqual,
ok,
rejects,
strict,
strictEqual,
throws,
} = assert;
export default assert;
// CommonJS so that `require("assert")` returns the callable function.
module.exports = require("./node_modules/assert");
40 changes: 30 additions & 10 deletions src/node-fallbacks/build-fallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,59 @@ for (const name of allFiles) {

for (let fileIndex = 0; fileIndex < allFiles.length; fileIndex++) {
const name = allFiles[fileIndex];
const mod = basename(name, extname(name)).replaceAll(".", "/");
const file = allFiles.find(f => f.startsWith(mod));
const externals = [...builtins];
const i = externals.indexOf(name);
if (i !== -1) {
externals.splice(i, 1);
}

// Build all files at once with specific options
const externalModules = builtins
.concat(moduleFiles.filter(f => f !== name))
.flatMap(b => [`--external:node:${b}`, `--external:${b}`])
.join(" ");

// A source that sets `module.exports` stays CommonJS, so `require()` of the polyfill returns that value.
const format = /^module\.exports\s*=/m.test(fs.readFileSync(name, "utf8")) ? "cjs" : "esm";

// Free `process` / `Buffer` in the npm packages: rename, then import from the sibling polyfill.
const injectedGlobals = [
{
defines: ["process", "global.process"],
id: "__bun_process",
skip: ["process.js"],
esm: 'import __bun_process from "process";',
cjs: 'var __bun_process = require("process").default;',
},
{
defines: ["Buffer", "global.Buffer"],
id: "__bun_Buffer",
// assert.js: only npm util's never-called isBuffer() names Buffer; the import would cost 31 KB.
skip: ["buffer.js", "assert.js"],
esm: 'import { Buffer as __bun_Buffer } from "buffer";',
cjs: 'var __bun_Buffer = require("buffer").Buffer;',
},
].filter(g => !g.skip.includes(name));
const defineGlobals = injectedGlobals.flatMap(g => g.defines.map(d => `--define=${d}:${g.id}`)).join(" ");

// Create the build command with all the specified options
const buildCommand =
Bun.$`bun build --define=process.env.NODE_DEBUG:"false" --define=process.env.READABLE_STREAM="'enable'" --define=global:globalThis --outdir=${outdir} ${name} --minify-syntax --minify-whitespace --format=${name.includes("stream") ? "cjs" : "esm"} --target=node ${{ raw: externalModules }}`.text();
Bun.$`bun build --define=process.env.NODE_DEBUG:"false" --define=process.env.READABLE_STREAM="'enable'" --define=global:globalThis ${{ raw: defineGlobals }} --outdir=${outdir} ${name} --minify-syntax --minify-whitespace --format=${format} --target=node ${{ raw: externalModules }}`.text();

commands.push(
buildCommand.then(async text => {
buildCommand.then(async () => {
// This is very brittle. But that should be okay for our usecase
let outfile = fs
.readFileSync(`${outdir}/${name}`, "utf8")
.replaceAll("__require(", "require(")
.replaceAll("import.meta.url", "''")
.replaceAll("createRequire", "")
.replaceAll("global.process", "require('process')")
.trim();

while (outfile.startsWith("import{")) {
outfile = outfile.slice(outfile.indexOf(";") + 1);
}

for (const { id, esm, cjs } of injectedGlobals) {
if (!outfile.includes(id)) continue;
outfile = (format === "cjs" ? cjs : esm) + outfile;
}

if (outfile.includes('"node:module"')) {
console.log(outfile);
throw new Error("Unexpected import in " + name);
Expand Down
23 changes: 23 additions & 0 deletions src/node-fallbacks/console.js
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
// Hardcoded module "node:console"
export const {
assert,
clear,
count,
countReset,
debug,
dir,
dirxml,
error,
group,
groupCollapsed,
groupEnd,
info,
log,
table,
time,
timeEnd,
timeLog,
timeStamp,
trace,
warn,
} = console;
export default console;
210 changes: 210 additions & 0 deletions src/node-fallbacks/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,213 @@ export const R_OK = 4;
export const W_OK = 2;
export const X_OK = 1;
export const UV_UDP_REUSEADDR = 4;

export default {
O_RDONLY,
O_WRONLY,
O_RDWR,
S_IFMT,
S_IFREG,
S_IFDIR,
S_IFCHR,
S_IFBLK,
S_IFIFO,
S_IFLNK,
S_IFSOCK,
O_CREAT,
O_EXCL,
O_NOCTTY,
O_TRUNC,
O_APPEND,
O_DIRECTORY,
O_NOFOLLOW,
O_SYNC,
O_SYMLINK,
O_NONBLOCK,
S_IRWXU,
S_IRUSR,
S_IWUSR,
S_IXUSR,
S_IRWXG,
S_IRGRP,
S_IWGRP,
S_IXGRP,
S_IRWXO,
S_IROTH,
S_IWOTH,
S_IXOTH,
E2BIG,
EACCES,
EADDRINUSE,
EADDRNOTAVAIL,
EAFNOSUPPORT,
EAGAIN,
EALREADY,
EBADF,
EBADMSG,
EBUSY,
ECANCELED,
ECHILD,
ECONNABORTED,
ECONNREFUSED,
ECONNRESET,
EDEADLK,
EDESTADDRREQ,
EDOM,
EDQUOT,
EEXIST,
EFAULT,
EFBIG,
EHOSTUNREACH,
EIDRM,
EILSEQ,
EINPROGRESS,
EINTR,
EINVAL,
EIO,
EISCONN,
EISDIR,
ELOOP,
EMFILE,
EMLINK,
EMSGSIZE,
EMULTIHOP,
ENAMETOOLONG,
ENETDOWN,
ENETRESET,
ENETUNREACH,
ENFILE,
ENOBUFS,
ENODATA,
ENODEV,
ENOENT,
ENOEXEC,
ENOLCK,
ENOLINK,
ENOMEM,
ENOMSG,
ENOPROTOOPT,
ENOSPC,
ENOSR,
ENOSTR,
ENOSYS,
ENOTCONN,
ENOTDIR,
ENOTEMPTY,
ENOTSOCK,
ENOTSUP,
ENOTTY,
ENXIO,
EOPNOTSUPP,
EOVERFLOW,
EPERM,
EPIPE,
EPROTO,
EPROTONOSUPPORT,
EPROTOTYPE,
ERANGE,
EROFS,
ESPIPE,
ESRCH,
ESTALE,
ETIME,
ETIMEDOUT,
ETXTBSY,
EWOULDBLOCK,
EXDEV,
SIGHUP,
SIGINT,
SIGQUIT,
SIGILL,
SIGTRAP,
SIGABRT,
SIGIOT,
SIGBUS,
SIGFPE,
SIGKILL,
SIGUSR1,
SIGSEGV,
SIGUSR2,
SIGPIPE,
SIGALRM,
SIGTERM,
SIGCHLD,
SIGCONT,
SIGSTOP,
SIGTSTP,
SIGTTIN,
SIGTTOU,
SIGURG,
SIGXCPU,
SIGXFSZ,
SIGVTALRM,
SIGPROF,
SIGWINCH,
SIGIO,
SIGSYS,
SSL_OP_ALL,
SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION,
SSL_OP_CIPHER_SERVER_PREFERENCE,
SSL_OP_CISCO_ANYCONNECT,
SSL_OP_COOKIE_EXCHANGE,
SSL_OP_CRYPTOPRO_TLSEXT_BUG,
SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS,
SSL_OP_EPHEMERAL_RSA,
SSL_OP_LEGACY_SERVER_CONNECT,
SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER,
SSL_OP_MICROSOFT_SESS_ID_BUG,
SSL_OP_MSIE_SSLV2_RSA_PADDING,
SSL_OP_NETSCAPE_CA_DN_BUG,
SSL_OP_NETSCAPE_CHALLENGE_BUG,
SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG,
SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG,
SSL_OP_NO_COMPRESSION,
SSL_OP_NO_QUERY_MTU,
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION,
SSL_OP_NO_SSLv2,
SSL_OP_NO_SSLv3,
SSL_OP_NO_TICKET,
SSL_OP_NO_TLSv1,
SSL_OP_NO_TLSv1_1,
SSL_OP_NO_TLSv1_2,
SSL_OP_PKCS1_CHECK_1,
SSL_OP_PKCS1_CHECK_2,
SSL_OP_SINGLE_DH_USE,
SSL_OP_SINGLE_ECDH_USE,
SSL_OP_SSLEAY_080_CLIENT_DH_BUG,
SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG,
SSL_OP_TLS_BLOCK_PADDING_BUG,
SSL_OP_TLS_D5_BUG,
SSL_OP_TLS_ROLLBACK_BUG,
ENGINE_METHOD_DSA,
ENGINE_METHOD_DH,
ENGINE_METHOD_RAND,
ENGINE_METHOD_ECDH,
ENGINE_METHOD_ECDSA,
ENGINE_METHOD_CIPHERS,
ENGINE_METHOD_DIGESTS,
ENGINE_METHOD_STORE,
ENGINE_METHOD_PKEY_METHS,
ENGINE_METHOD_PKEY_ASN1_METHS,
ENGINE_METHOD_ALL,
ENGINE_METHOD_NONE,
DH_CHECK_P_NOT_SAFE_PRIME,
DH_CHECK_P_NOT_PRIME,
DH_UNABLE_TO_CHECK_GENERATOR,
DH_NOT_SUITABLE_GENERATOR,
NPN_ENABLED,
RSA_PKCS1_PADDING,
RSA_SSLV23_PADDING,
RSA_NO_PADDING,
RSA_PKCS1_OAEP_PADDING,
RSA_X931_PADDING,
RSA_PKCS1_PSS_PADDING,
POINT_CONVERSION_COMPRESSED,
POINT_CONVERSION_UNCOMPRESSED,
POINT_CONVERSION_HYBRID,
F_OK,
R_OK,
W_OK,
X_OK,
UV_UDP_REUSEADDR,
};
1 change: 1 addition & 0 deletions src/node-fallbacks/domain.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import domain from "domain-browser";
export var { create, createDomain } = domain;
export default domain;
14 changes: 2 additions & 12 deletions src/node-fallbacks/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -530,15 +530,5 @@ Object.assign(EventEmitter, {
listenerCount,
});

export default EventEmitter;
export {
EventEmitter,
addAbortListener,
captureRejectionSymbol,
getEventListeners,
getMaxListeners,
EventEmitter as init,
listenerCount,
once,
setMaxListeners,
};
// CommonJS so that `require("events")` returns the constructor.
module.exports = EventEmitter;
Comment thread
robobun marked this conversation as resolved.
Resolved
Loading
Loading