Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 8 additions & 1 deletion src/node-fallbacks/build-fallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ for (let fileIndex = 0; fileIndex < allFiles.length; fileIndex++) {

// 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 --outdir=${outdir} ${name} --minify-syntax --minify-whitespace --keep-names --format=${name.includes("stream") ? "cjs" : "esm"} --target=node ${{ raw: externalModules }}`.text();
Comment thread
robobun marked this conversation as resolved.
Resolved

commands.push(
buildCommand.then(async text => {
Expand Down Expand Up @@ -76,6 +76,13 @@ for (let fileIndex = 0; fileIndex < allFiles.length; fileIndex++) {
throw new Error("Unsupported function in " + name);
}

// https://github.com/oven-sh/bun/pull/40503 makes --keep-names add __name() calls, which run in user bundles.
if (outfile.includes("__name(")) {
throw new Error(
`Unexpected __name() call in ${name}. If --keep-names added it, drop --minify-syntax and --keep-names.`,
);
}

fs.writeFileSync(`${outdir}/${name}`, outfile);
// Release builds embed the zstd-compressed copy (see
// src/resolver/node_fallbacks.rs) so the ~1 MB of polyfill text doesn't
Expand Down
18 changes: 12 additions & 6 deletions src/node-fallbacks/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const ArrayPrototypeSlice = Array.prototype.slice;
var defaultMaxListeners = 10;

// EventEmitter must be a standard function because some old code will do weird tricks like `EventEmitter.apply(this)`.
const EventEmitter = function EventEmitter(opts) {
function EventEmitter(opts) {
Comment thread
robobun marked this conversation as resolved.
Resolved
if (this._events === undefined || this._events === this.__proto__._events) {
this._events = { __proto__: null };
this._eventsCount = 0;
Expand All @@ -29,7 +29,7 @@ const EventEmitter = function EventEmitter(opts) {
if ((this[kCapture] = opts?.captureRejections ? Boolean(opts?.captureRejections) : EventEmitterPrototype[kCapture])) {
this.emit = emitWithRejectionCapture;
}
};
}
const EventEmitterPrototype = (EventEmitter.prototype = {});

EventEmitterPrototype._events = undefined;
Expand Down Expand Up @@ -530,15 +530,21 @@ Object.assign(EventEmitter, {
listenerCount,
});

// The bundler numbers a function expression named like an export (`once2`). These exports have other binding names.
const eventsGetMaxListeners = getMaxListeners,
eventsListenerCount = listenerCount,
eventsOnce = once,
eventsSetMaxListeners = setMaxListeners;

export default EventEmitter;
export {
EventEmitter,
addAbortListener,
captureRejectionSymbol,
getEventListeners,
getMaxListeners,
eventsGetMaxListeners as getMaxListeners,
EventEmitter as init,
listenerCount,
once,
setMaxListeners,
eventsListenerCount as listenerCount,
eventsOnce as once,
eventsSetMaxListeners as setMaxListeners,
};
5 changes: 4 additions & 1 deletion src/node-fallbacks/http.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import http from "stream-http";
export var { request, get, ClientRequest, IncomingMessage, Agent, globalAgent, STATUS_CODES, METHODS } = http;
export var { request, ClientRequest, IncomingMessage, Agent, globalAgent, STATUS_CODES, METHODS } = http;
// The bundler numbers a function expression named like an export (`get2`). This export has another binding name.
var httpGet = http.get;
export { httpGet as get };
export default http;
9 changes: 7 additions & 2 deletions src/node-fallbacks/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export const debuglog = /* @__PURE__ */ ((debugs = {}, debugEnvRegex = {}, debug
* @param {Object} opts Optional options object that alters the output.
*/
/* legacy: obj, showHidden, depth, colors*/
export const inspect = /* @__PURE__ */ (i =>
const inspect = /* @__PURE__ */ (i =>
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
(
(i.colors = {
Expand Down Expand Up @@ -568,7 +568,7 @@ function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}

export const promisify = /* @__PURE__ */ (x => ((x.custom = Symbol.for("nodejs.util.promisify.custom")), x))(
const promisify = /* @__PURE__ */ (x => ((x.custom = Symbol.for("nodejs.util.promisify.custom")), x))(
function promisify(original) {
if (typeof original !== "function") throw new TypeError('The "original" argument must be of type Function');

Expand Down Expand Up @@ -673,6 +673,11 @@ export function callbackify(original) {
return callbackified;
}

// The bundler numbers a function expression named like an export (`inspect2`). These exports have other binding names.
const utilInspect = inspect,
utilPromisify = promisify;
export { utilInspect as inspect, utilPromisify as promisify };

export const TextEncoder = /* @__PURE__ */ globalThis.TextEncoder;
export const TextDecoder = /* @__PURE__ */ globalThis.TextDecoder;
export default {
Expand Down
100 changes: 100 additions & 0 deletions test/bundler/bundler_browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,106 @@ describe("bundler", () => {
assert(!out.includes("$newPromiseCapability"), "events polyfill must not reference a JSC builtin intrinsic");
},
});
// Node's test/parallel/test-event-emitter-method-names.js reads these names. build-fallbacks.ts must keep them.
const NodeEventsMethodNames = itBundled("browser/NodeEventsMethodNames", {
files: {
"/entry.js": /* js */ `
import { EventEmitter, getMaxListeners, listenerCount, once } from "node:events";
const E = EventEmitter.prototype;
for (const name of Object.getOwnPropertyNames(E).sort()) {
if (typeof E[name] === "function") console.log(name + ": " + E[name].name);
}
console.log("exports: " + [getMaxListeners.name, listenerCount.name, once.name]);
`,
},
target: "browser",
run: {
stdout: `
addListener: addListener
constructor: EventEmitter
emit: emit
eventNames: eventNames
getMaxListeners: getMaxListeners
listenerCount: listenerCount
listeners: listeners
off: removeListener
on: addListener
once: once
prependListener: prependListener
prependOnceListener: prependOnceListener
rawListeners: rawListeners
removeAllListeners: removeAllListeners
removeListener: removeListener
setMaxListeners: setMaxListeners
exports: getMaxListeners,listenerCount,once
`,
},
});
// The user's --minify-syntax drops the names again, unless --keep-names ("cli") or minify.keepNames ("api") is set.
for (const backend of ["cli", "api"] as const) {
itBundled(`browser/NodeEventsMethodNamesKeepNames-${backend}`, {
...NodeEventsMethodNames.options,
backend,
minifySyntax: true,
keepNames: true,
});
}
// buffer is hand-written, stream ships as CommonJS, and stream's require("events") gives events an exports object.
itBundled("browser/NodePolyfillFunctionNames", {
files: {
"/entry.js": /* js */ `
import { Buffer } from "node:buffer";
import { EventEmitter } from "node:events";
import { addAbortSignal, Writable } from "node:stream";
console.log(JSON.stringify([
EventEmitter.name,
new EventEmitter().constructor.name,
Buffer.concat.name,
Buffer.prototype.toString.name,
addAbortSignal.name,
Writable.prototype.setDefaultEncoding.name,
]));
`,
},
target: "browser",
run: {
stdout: '["EventEmitter","EventEmitter","concat","toString","addAbortSignal","setDefaultEncoding"]',
},
});
// With an exports object, the bundler numbers a function expression named like an export (EventEmitter2, once2).
itBundled("browser/NodePolyfillFunctionNamesRequire", {
files: {
"/entry.js": /* js */ `
const [events, http, util] = [require("node:events"), require("node:http"), require("node:util")];
const E = events.EventEmitter.prototype;
console.log(JSON.stringify([
events.EventEmitter.name,
new events.EventEmitter().constructor.name,
E.once.name,
E.listenerCount.name,
E.getMaxListeners.name,
E.setMaxListeners.name,
http.get.name,
util.inspect.name,
util.promisify.name,
]));
`,
},
target: "browser",
run: {
stdout: JSON.stringify([
"EventEmitter",
"EventEmitter",
"once",
"listenerCount",
"getMaxListeners",
"setMaxListeners",
"get",
"inspect",
"promisify",
]),
},
});
itBundled("browser/NodeUrlProtocolTablesIgnorePrototype", {
files: {
"/entry.js": /* js */ `
Expand Down
Loading