mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
## Summary
- Refactored `maybeMarkConstructorAsPure` to `minifyGlobalConstructor`
that returns `?Expr`
- Added minification optimizations for global constructors that work
identically with/without `new`
- Converts constructors to more compact forms: `new Object()` → `{}`,
`new Array()` → `[]`, etc.
- Fixed issue where minification was incorrectly applied to runtime
node_modules code
## Details
This PR refactors the existing `maybeMarkConstructorAsPure` function to
`minifyGlobalConstructor` and changes it to return an optional
expression. This enables powerful minification optimizations for global
constructors.
### Optimizations Added:
#### 1. Error Constructors (4 bytes saved each)
- `new Error(...)` → `Error(...)`
- `new TypeError(...)` → `TypeError(...)`
- `new SyntaxError(...)` → `SyntaxError(...)`
- `new RangeError(...)` → `RangeError(...)`
- `new ReferenceError(...)` → `ReferenceError(...)`
- `new EvalError(...)` → `EvalError(...)`
- `new URIError(...)` → `URIError(...)`
- `new AggregateError(...)` → `AggregateError(...)`
#### 2. Object Constructor
- `new Object()` → `{}` (11 bytes saved)
- `new Object({a: 1})` → `{a: 1}` (11 bytes saved)
- `new Object([1, 2])` → `[1, 2]` (11 bytes saved)
- `new Object(null)` → `{}` (15 bytes saved)
- `new Object(undefined)` → `{}` (20 bytes saved)
#### 3. Array Constructor
- `new Array()` → `[]` (10 bytes saved)
- `new Array(1, 2, 3)` → `[1, 2, 3]` (9 bytes saved)
- `new Array(5)` → `Array(5)` (4 bytes saved, preserves sparse array
semantics)
#### 4. Function and RegExp Constructors
- `new Function(...)` → `Function(...)` (4 bytes saved)
- `new RegExp(...)` → `RegExp(...)` (4 bytes saved)
### Important Fixes:
- Added check to prevent minification of node_modules code at runtime
(only applies during bundling)
- Preserved sparse array semantics for `new Array(number)`
- Extracted `callFromNew` helper to reduce code duplication
### Size Impact:
- React SSR bundle: 463 bytes saved
- Each optimization safely preserves JavaScript semantics
## Test plan
✅ All tests pass:
- Added comprehensive tests in `bundler_minify.test.ts`
- Verified Error constructors work identically with/without `new`
- Tested Object/Array literal conversions
- Ensured sparse array semantics are preserved
- Updated source map positions in `bundler_npm.test.ts`
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
182 lines
4.8 KiB
TypeScript
182 lines
4.8 KiB
TypeScript
import { file, spawn } from "bun";
|
|
import { expect, it } from "bun:test";
|
|
import { bunEnv, bunExe } from "harness";
|
|
import { join } from "node:path";
|
|
|
|
it("should log to console correctly", async () => {
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), join(import.meta.dir, "console-log.js")],
|
|
stdin: "inherit",
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: bunEnv,
|
|
});
|
|
const exitCode = await exited;
|
|
const err = (await stderr.text()).replaceAll("\r\n", "\n");
|
|
const out = (await stdout.text()).replaceAll("\r\n", "\n");
|
|
const expected = (await new Response(file(join(import.meta.dir, "console-log.expected.txt"))).text()).replaceAll(
|
|
"\r\n",
|
|
"\n",
|
|
);
|
|
|
|
const errMatch = err === "uh oh\n";
|
|
const outmatch = out === expected;
|
|
|
|
if (errMatch && outmatch && exitCode === 0) {
|
|
expect().pass();
|
|
return;
|
|
}
|
|
|
|
console.error(err);
|
|
console.log("Length of output:", out.length);
|
|
console.log("Length of expected:", expected.length);
|
|
console.log("Exit code:", exitCode);
|
|
|
|
expect(out).toBe(expected);
|
|
expect(err).toBe("uh oh\n");
|
|
expect(exitCode).toBe(0);
|
|
});
|
|
|
|
it("long arrays get cutoff", () => {
|
|
const proc = Bun.spawnSync({
|
|
cmd: [bunExe(), "-e", `console.log(Array(1000).fill(0))`],
|
|
env: bunEnv,
|
|
stdio: ["inherit", "pipe", "pipe"],
|
|
});
|
|
expect(proc.exitCode).toBe(0);
|
|
expect(proc.stderr.toString("utf8")).toBeEmpty();
|
|
expect(proc.stdout.toString("utf8")).toEqual(
|
|
"[\n" +
|
|
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n" +
|
|
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n" +
|
|
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n" +
|
|
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n" +
|
|
" ... 900 more items\n" +
|
|
"]\n" +
|
|
"",
|
|
);
|
|
});
|
|
|
|
it("console.group", async () => {
|
|
const filepath = join(import.meta.dir, "console-group.fixture.js").replaceAll("\\", "/");
|
|
const proc = Bun.spawnSync({
|
|
cmd: [bunExe(), filepath],
|
|
env: { ...bunEnv, "BUN_JSC_showPrivateScriptsInStackTraces": "0" },
|
|
stdio: ["inherit", "pipe", "pipe"],
|
|
});
|
|
expect(proc.exitCode).toBe(0);
|
|
let stdout = proc.stdout
|
|
.toString("utf8")
|
|
.replaceAll("\r\n", "\n")
|
|
.replaceAll("\\", "/")
|
|
.trim()
|
|
.replaceAll(filepath, "<file>");
|
|
let stderr = proc.stderr
|
|
.toString("utf8")
|
|
.replaceAll("\r\n", "\n")
|
|
.replaceAll("\\", "/")
|
|
.trim()
|
|
.replaceAll(filepath, "<file>")
|
|
// Normalize line numbers for consistency between debug and release builds
|
|
.replace(/\(\d+:\d+\)/g, "(N:NN)")
|
|
.replace(/<file>:\d+:\d+/g, "<file>:NN:NN");
|
|
expect(stdout).toMatchInlineSnapshot(`
|
|
"Basic group
|
|
Inside basic group
|
|
Outer group
|
|
Inside outer group
|
|
Inner group
|
|
Inside inner group
|
|
Back to outer group
|
|
Level 1
|
|
Level 2
|
|
Level 3
|
|
Deep inside
|
|
undefined
|
|
Empty nested
|
|
Test extra end
|
|
Inside
|
|
Different logs
|
|
Regular log
|
|
Info log
|
|
Debug log
|
|
Complex types
|
|
{
|
|
a: 1,
|
|
b: 2,
|
|
}
|
|
[ 1, 2, 3 ]
|
|
null
|
|
undefined
|
|
0
|
|
false
|
|
|
|
Inside falsy groups
|
|
🎉 Unicode!
|
|
Inside unicode group
|
|
Tab\tNewline
|
|
Quote"Backslash
|
|
Special chars"
|
|
`);
|
|
expect(stderr).toMatchInlineSnapshot(`
|
|
"Warning log
|
|
warn: console.warn an error
|
|
at <file>:NN:NN
|
|
at loadAndEvaluateModule (N:NN)
|
|
|
|
52 | console.group("Different logs");
|
|
53 | console.log("Regular log");
|
|
54 | console.info("Info log");
|
|
55 | console.warn("Warning log");
|
|
56 | console.warn(new Error("console.warn an error"));
|
|
57 | console.error(new Error("console.error an error"));
|
|
^
|
|
error: console.error an error
|
|
at <file>:NN:NN
|
|
at loadAndEvaluateModule (N:NN)
|
|
|
|
53 | console.log("Regular log");
|
|
54 | console.info("Info log");
|
|
55 | console.warn("Warning log");
|
|
56 | console.warn(new Error("console.warn an error"));
|
|
57 | console.error(new Error("console.error an error"));
|
|
58 | console.error(new NamedError("console.error a named error"));
|
|
^
|
|
NamedError: console.error a named error
|
|
at <file>:NN:NN
|
|
at loadAndEvaluateModule (N:NN)
|
|
|
|
NamedError: console.warn a named error
|
|
at <file>:NN:NN
|
|
at loadAndEvaluateModule (N:NN)
|
|
|
|
Error log"
|
|
`);
|
|
});
|
|
|
|
it("console.log with SharedArrayBuffer", () => {
|
|
const proc = Bun.spawnSync({
|
|
cmd: [
|
|
bunExe(),
|
|
"-e",
|
|
`
|
|
console.log(new ArrayBuffer(0));
|
|
console.log(new SharedArrayBuffer(0));
|
|
console.log(new ArrayBuffer(3));
|
|
console.log(new SharedArrayBuffer(3));
|
|
`,
|
|
],
|
|
env: bunEnv,
|
|
stdio: ["inherit", "pipe", "pipe"],
|
|
});
|
|
expect(proc.stderr.toString("utf8")).toBeEmpty();
|
|
expect(proc.exitCode).toBe(0);
|
|
expect(proc.stdout.toString("utf8")).toMatchInlineSnapshot(`
|
|
"ArrayBuffer(0) []
|
|
SharedArrayBuffer(0) []
|
|
ArrayBuffer(3) [ 0, 0, 0 ]
|
|
SharedArrayBuffer(3) [ 0, 0, 0 ]
|
|
"
|
|
`);
|
|
});
|