Commit Graph

54 Commits

Author SHA1 Message Date
Jarred Sumner
5a0705348b feat(transpiler): add replMode option for REPL transforms
Add a new `replMode` option to Bun.Transpiler that transforms code for
interactive REPL evaluation:

- Wraps expressions in `{ value: expr }` for result capture
- Uses sync/async IIFE wrappers to avoid parentheses around objects
- Hoists var/let/const declarations for persistence across REPL lines
- Converts const to let for REPL mutability
- Hoists function declarations with this.funcName assignment
- Hoists class declarations with var for vm context persistence
- Auto-detects object literals (starting with { without trailing ;)

This enables building a Node.js-compatible REPL using Bun.Transpiler
with vm.runInContext for persistent variable scope.

Usage:
```typescript
const transpiler = new Bun.Transpiler({
  loader: "tsx",
  replMode: true,
});
const transformed = transpiler.transformSync(userInput);
const result = await vm.runInContext(transformed, context);
console.log(result.value);
```

REPL transforms are extracted into separate repl_transforms.zig module.

Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 8
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# REPL Transform Fixes and Node.js Parity

## Current Status

The basic `replMode` option is implemented. This plan covers fixes and parity with Node.js REPL.

## Issues to Fix

### 1. Value Wrapper Has Extra Parentheses (CRITICAL)
**Current output:**
```js
({
  __proto__: null,
  value: 42
});
```

**Expected behavior (per Node.js):**
- For **non-async expressions**: Node.js returns `null` (no transform) - the REPL evaluates the expression directly
- For **async expressions**: `(async () => { return { __proto__: null, value: (expr) } })()`

**Solution:**
1. For non-async expressions: Don't wrap in `{ value: expr }` - just return the expression as-is
2. For async expressions: The `{ __proto__: null, value: expr }` is already inside the function after `return`, so no outer parens needed
3. Add inner parens around the expression value for clarity: `{ __proto__: null, value: (expr) }`

### 2. Object Literal Disambiguation (CRITICAL)
**Input:** `{a: 1}` or `{foo: await fetch()}`

**Current:** Parsed as block with labeled statement, NOT object literal

**Solution:** Pre-check input at transpiler layer:
- If code starts with `{` and doesn't end with `;`, try parsing as `(_=CODE)`
- If valid, wrap input as `(CODE)` before processing
- This matches Node.js approach in `repl.js` line 411-414

### 3. Class Declarations Don't Persist to VM Context
**Current:** Uses `let ClassName;` hoisting - doesn't become vm context property

**Node.js behavior:** Also uses `let` - this is a known limitation in Node.js too!

Looking at Node.js `await.js` line 31-37:
```js
ClassDeclaration(node, state, c) {
  state.prepend(node, `${node.id.name}=`);
  ArrayPrototypePush(state.hoistedDeclarationStatements, `let ${node.id.name}; `);
}
```

**Decision:** Use `var` instead of `let` for class hoisting. This makes classes persist to vm context, matching user expectations for REPL behavior. (Different from Node.js which uses `let`)

---

## Implementation Plan

## Usage Example

```typescript
// REPL tool implementation
const transpiler = new Bun.Transpiler({
  loader: "tsx",
  replMode: true,  // NEW OPTION
});

// For each REPL input line:
const transformed = transpiler.transformSync(userInput);

// Execute in persistent VM context
const result = vm.runInContext(transformed, replContext);

// result.value contains the expression result (wrapped to prevent auto-await)
console.log(result.value);
```

## Design Decisions

- **Value wrapper**: Use `{ value: expr }` wrapper like Node.js to prevent auto-awaiting Promise results
- **Static imports**: Keep static imports as-is (Bun handles them natively)
- **Scope**: Full Node.js REPL transform parity

---

## Node.js REPL Transform Behavior (Reference)

From `vendor/node/lib/internal/repl/await.js`:

### 1. Object Literal Detection
```javascript
// {a:1} → ({a:1}) when starts with { and no trailing ;
if (/^\s*{/.test(code) && !/;\s*$/.test(code)) {
  code = `(${code})`;
}
```

### 2. Top-Level Await Transform
```javascript
// Input:  await x
// Output: (async () => { return { value: (await x) } })()

// Input:  var x = await 1
// Output: var x; (async () => { void (x = await 1) })()

// Input:  const x = await 1
// Output: let x; (async () => { void (x = await 1) })()  // const→let

// Input:  function foo() {}  (with await somewhere)
// Output: var foo; (async () => { this.foo = foo; function foo() {} })()

// Input:  class Foo {}  (with await somewhere)
// Output: let Foo; (async () => { Foo=class Foo {} })()
```

### 3. Transform Skipping
Returns `null` (no transform) when:
- No `await` expression present at top level
- Top-level `return` statement exists
- Code is inside async functions/arrow functions/class methods

---

## Implementation Plan

### Fix 1: Remove Extra Parentheses from Value Wrapper

**Problem:** The printer adds `()` around objects at statement start to disambiguate from blocks.

**Solution:** Always use an IIFE wrapper (sync or async) so the object is after `return`:

```js
// Non-async expression (current - BAD)
({ __proto__: null, value: 42 });

// Non-async expression (fixed - GOOD)
(() => { return { __proto__: null, value: 42 } })()

// Non-async with hoisting (fixed - GOOD)
var x;
(() => { void (x = 1); return { __proto__: null, value: x } })()

// Async (already correct)
var x;
(async () => { void (x = await 1); return { __proto__: null, value: x } })()
```

**Files to modify:**
1. `src/ast/P.zig` - `applyReplValueWrapper()` function

**Changes:**
- Remove the simple `{ value: expr }` wrapper approach
- Always use `applyReplAsyncTransform()` style IIFE wrapping, but with `is_async = false` for non-async code
- This ensures the object is always after `return`, avoiding the parentheses issue
- Hoisting still works for both cases

### Fix 2: Object Literal Disambiguation

**File:** `src/bun.js/api/JSTranspiler.zig` - Before parsing

Add pre-processing check:
```zig
// In transformSync, before parsing:
if (config.repl_mode) {
    // Check if input looks like object literal: starts with { and doesn't end with ;
    if (startsWithBrace(source) and !endsWithSemicolon(source)) {
        // Try parsing as expression by wrapping: _=(CODE)
        // If valid, wrap input as (CODE)
        source = wrapAsExpression(source);
    }
}
```

This matches Node.js `isObjectLiteral()` check in `repl/utils.js:786-789`:
```js
function isObjectLiteral(code) {
  return /^\s*{/.test(code) && !/;\s*$/.test(code);
}
```

### Fix 3: Class Declaration Persistence

**File:** `src/ast/P.zig` - `applyReplAsyncTransform()` in the class handling section

Change from:
```zig
// let Foo; (hoisted)
try hoisted_stmts.append(p.s(S.Local{ .kind = .k_let, ... }));
```

To:
```zig
// var Foo; (hoisted) - use var so it becomes context property
try hoisted_stmts.append(p.s(S.Local{ .kind = .k_var, ... }));
// Also add: this.Foo = Foo; assignment after class declaration
```

---

## Node.js REPL Test Cases to Match

From `vendor/node/test/parallel/test-repl-preprocess-top-level-await.js`:

| Input | Expected Output |
|-------|-----------------|
| `await 0` | `(async () => { return { value: (await 0) } })()` |
| `var a = await 1` | `var a; (async () => { void (a = await 1) })()` |
| `let a = await 1` | `let a; (async () => { void (a = await 1) })()` |
| `const a = await 1` | `let a; (async () => { void (a = await 1) })()` |
| `await 0; function foo() {}` | `var foo; (async () => { await 0; this.foo = foo; function foo() {} })()` |
| `await 0; class Foo {}` | `let Foo; (async () => { await 0; Foo=class Foo {} })()` |
| `var {a} = {a:1}, [b] = [1]` | `var a, b; (async () => { void ( ({a} = {a:1}), ([b] = [1])) })()` |

---

## Files to Modify

| File | Changes |
|------|---------|
| `src/ast/P.zig` | Fix value wrapper format, fix class hoisting to use var |
| `src/bun.js/api/JSTranspiler.zig` | Add object literal pre-check |
| `src/ast/js_printer.zig` | May need to check object literal printing |
| `test/js/bun/transpiler/repl-transform.test.ts` | Update tests for exact Node.js parity |

---

## Verification

1. Run Node.js preprocess test cases through Bun's transpiler
2. Verify output matches Node.js exactly (or functionally equivalent)
3. Test with vm.runInContext for variable persistence
4. Test object literal inputs: `{a: 1}`, `{foo: await bar()}`

---

## DEPRECATED - Previous Implementation (Already Done)

### 1. Add `replMode` to Bun.Transpiler API

**File**: `src/bun.js/api/JSTranspiler.zig`

Add to `Config` struct (around line 27-44):
```zig
pub const Config = struct {
    // ... existing fields ...
    repl_mode: bool = false,
```

Parse the option in `Config.fromJS()` (around line 420-430):
```zig
if (try object.getBooleanLoose(globalThis, "replMode")) |flag| {
    this.repl_mode = flag;
}
```

Apply the option in `constructor()` (around line 714-721):
```zig
transpiler.options.repl_mode = config.repl_mode;
```

### 2. Add Feature Flag to Runtime

**File**: `src/runtime.zig` (in `Runtime.Features`)

```zig
/// REPL mode: transforms code for interactive evaluation
/// - Wraps lone object literals `{...}` in parentheses
/// - Hoists variable declarations for REPL persistence
/// - Wraps last expression in { value: expr } for result capture
/// - Assigns functions to context for persistence
repl_mode: bool = false,
```

### 3. Add to BundleOptions

**File**: `src/options.zig`

Add to `BundleOptions` struct:
```zig
repl_mode: bool = false,
```

### 4. Implement REPL Transforms in Parser

**File**: `src/ast/P.zig`

#### 4a. Object Literal Detection (Parser-Level)

In REPL mode, the parser should prefer interpreting ambiguous `{...}` as object literals instead of blocks.

**Location**: `src/ast/parseStmt.zig` in statement parsing

When `repl_mode` is true and the parser sees `{` at the start of a statement:
1. Try parsing as expression statement (object literal) first
2. If that fails, fall back to block statement

This is similar to how JavaScript engines handle REPL input. The parser already has the infrastructure to do this - we just need to change the precedence in REPL mode.

```zig
// In parseStmt when repl_mode is true and we see '{'
if (p.options.features.repl_mode and p.token.tag == .t_open_brace) {
    // Try parsing as expression first
    const saved_state = p.saveState();
    if (p.tryParseExpressionStatement()) |expr_stmt| {
        return expr_stmt;
    }
    p.restoreState(saved_state);
    // Fall back to block statement
    return p.parseBlockStatement();
}
```

This handles:
- `{a: 1}` → parsed as object literal expression
- `{a: 1, b: 2}` → parsed as object literal expression
- `{ let x = 1; }` → fails as expression, parsed as block
- `{ label: break label; }` → fails as expression (break not valid in object), parsed as block

#### 4b. REPL Transform Pass (in toAST after visiting)

Add a new function `applyReplTransforms()` that:

1. **Detect if transform is needed**: Walk AST to check for top-level `await`
2. **Skip transform when**:
   - No `await` at top level
   - Top-level `return` statement exists
3. **When transform IS needed**:
   - Wrap entire code in `(async () => { ... })()`
   - Hoist variable declarations outside the async wrapper
   - Convert `const` to `let` for persistence
   - Wrap last expression in `return { value: (expr) }`
   - Handle function declarations (assign to `this`)
   - Handle class declarations (hoist as `let`)

**Key Logic:**
```zig
fn applyReplTransforms(p: *Parser, stmts: []Stmt) ![]Stmt {
    // 1. Check for top-level await
    const has_await = p.hasTopLevelAwait(stmts);
    const has_return = p.hasTopLevelReturn(stmts);

    if (!has_await or has_return) {
        // Just wrap last expression, no async wrapper needed
        return p.wrapLastExpression(stmts);
    }

    // 2. Collect declarations to hoist
    var hoisted = std.ArrayList(Stmt).init(p.allocator);
    var inner_stmts = std.ArrayList(Stmt).init(p.allocator);

    for (stmts) |stmt| {
        switch (stmt.data) {
            .s_local => |local| {
                // Hoist declaration, convert const→let
                try hoisted.append(p.createHoistedDecl(local));
                // Add assignment expression to inner
                try inner_stmts.append(p.createAssignmentExpr(local));
            },
            .s_function => |func| {
                // var foo; (hoisted)
                try hoisted.append(p.createVarDecl(func.name));
                // this.foo = foo; function foo() {} (inner)
                try inner_stmts.append(p.createThisAssignment(func.name));
                try inner_stmts.append(stmt);
            },
            .s_class => |class| {
                // let Foo; (hoisted)
                try hoisted.append(p.createLetDecl(class.name));
                // Foo = class Foo {} (inner)
                try inner_stmts.append(p.createClassAssignment(class));
            },
            else => try inner_stmts.append(stmt),
        }
    }

    // 3. Wrap last expression in return { value: expr }
    p.wrapLastExpressionWithReturn(&inner_stmts);

    // 4. Create async IIFE: (async () => { ...inner... })()
    const async_iife = p.createAsyncIIFE(inner_stmts.items);

    // 5. Combine: hoisted declarations + async IIFE
    try hoisted.append(async_iife);
    return hoisted.toOwnedSlice();
}
```

### 5. TypeScript Type Definitions

**File**: `packages/bun-types/bun.d.ts`

Add to `TranspilerOptions` interface (around line 1748):
```typescript
interface TranspilerOptions {
  // ... existing options ...

  /**
   * Enable REPL mode transforms:
   * - Wraps object literals in parentheses
   * - Hoists declarations for REPL persistence
   * - Wraps last expression in { value: expr } for result capture
   * - Wraps code with await in async IIFE
   */
  replMode?: boolean;
}
```

---

## Files to Modify

| File | Changes |
|------|---------|
| `src/bun.js/api/JSTranspiler.zig` | Add `repl_mode` to Config, parse from JS, apply to transpiler |
| `src/runtime.zig` | Add `repl_mode: bool` to `Runtime.Features` |
| `src/options.zig` | Add `repl_mode: bool` to `BundleOptions` |
| `src/ast/P.zig` | REPL transform pass in `toAST()` |
| `src/ast/parseStmt.zig` | Object literal vs block disambiguation in REPL mode |
| `packages/bun-types/bun.d.ts` | Add `replMode?: boolean` to `TranspilerOptions` |

---

## Test Cases

Create test file: `test/js/bun/transpiler/repl-transform.test.ts`

### Part 1: Transform Output Tests (Unit Tests)

Test exact transformation output matches expected patterns:

```typescript
import { expect, test, describe } from "bun:test";
import vm from "node:vm";

describe("Bun.Transpiler replMode - Transform Output", () => {
  const transpiler = new Bun.Transpiler({ loader: "tsx", replMode: true });

  // Based on Node.js test-repl-preprocess-top-level-await.js
  const testCases: [string, string | null][] = [
    // No await = null (no async transform, but still expression capture)
    ['0', null],

    // Basic await
    ['await 0', '(async () => { return { value: (await 0) } })()'],
    ['await 0;', '(async () => { return { value: (await 0) }; })()'],
    ['(await 0)', '(async () => { return ({ value: (await 0) }) })()'],

    // No transform for await inside async functions
    ['async function foo() { await 0; }', null],
    ['async () => await 0', null],
    ['class A { async method() { await 0 } }', null],

    // Top-level return = no transform
    ['await 0; return 0;', null],

    // Multiple await - last one gets return wrapper
    ['await 1; await 2;', '(async () => { await 1; return { value: (await 2) }; })()'],

    // Variable hoisting - var
    ['var a = await 1', 'var a; (async () => { void (a = await 1) })()'],

    // Variable hoisting - let
    ['let a = await 1', 'let a; (async () => { void (a = await 1) })()'],

    // Variable hoisting - const becomes let
    ['const a = await 1', 'let a; (async () => { void (a = await 1) })()'],

    // For loop with var - hoist var
    ['for (var i = 0; i < 1; ++i) { await i }',
     'var i; (async () => { for (void (i = 0); i < 1; ++i) { await i } })()'],

    // For loop with let - no hoist
    ['for (let i = 0; i < 1; ++i) { await i }',
     '(async () => { for (let i = 0; i < 1; ++i) { await i } })()'],

    // Destructuring with var
    ['var {a} = {a:1}, [b] = [1], {c:{d}} = {c:{d: await 1}}',
     'var a, b, d; (async () => { void ( ({a} = {a:1}), ([b] = [1]), ({c:{d}} = {c:{d: await 1}})) })()'],

    // Destructuring with let
    ['let [a, b, c] = await ([1, 2, 3])',
     'let a, b, c; (async () => { void ([a, b, c] = await ([1, 2, 3])) })()'],

    // Function declarations - assign to this
    ['await 0; function foo() {}',
     'var foo; (async () => { await 0; this.foo = foo; function foo() {} })()'],

    // Class declarations - hoist as let
    ['await 0; class Foo {}',
     'let Foo; (async () => { await 0; Foo=class Foo {} })()'],

    // Nested scopes
    ['if (await true) { var a = 1; }',
     'var a; (async () => { if (await true) { void (a = 1); } })()'],
    ['if (await true) { let a = 1; }',
     '(async () => { if (await true) { let a = 1; } })()'],

    // Mixed declarations
    ['var a = await 1; let b = 2; const c = 3;',
     'var a; let b; let c; (async () => { void (a = await 1); void (b = 2); void (c = 3); })()'],

    // for await
    ['for await (var i of asyncIterable) { i; }',
     'var i; (async () => { for await (i of asyncIterable) { i; } })()'],

    // for-of with var
    ['for (var i of [1,2,3]) { await 1; }',
     'var i; (async () => { for (i of [1,2,3]) { await 1; } })()'],

    // for-in with var
    ['for (var i in {x:1}) { await 1 }',
     'var i; (async () => { for (i in {x:1}) { await 1 } })()'],

    // Spread in destructuring
    ['var { ...rest } = await {}',
     'var rest; (async () => { void ({ ...rest } = await {}) })()'],
  ];

  for (const [input, expected] of testCases) {
    test(`transform: ${input.slice(0, 40)}...`, () => {
      const result = transpiler.transformSync(input);
      if (expected === null) {
        // No async transform expected, but expression capture may still happen
        expect(result).not.toMatch(/^\(async/);
      } else {
        expect(result.trim()).toBe(expected);
      }
    });
  }

  // Object literal detection - parser handles this automatically in REPL mode
  describe("object literal vs block disambiguation", () => {
    test("{a: 1} parsed as object literal", async () => {
      const ctx = vm.createContext({});
      const code = transpiler.transformSync("{a: 1}");
      const result = await vm.runInContext(code, ctx);
      // Should evaluate to object, not undefined (block with label)
      expect(result.value).toEqual({ a: 1 });
    });

    test("{a: 1, b: 2} parsed as object literal", async () => {
      const ctx = vm.createContext({});
      const code = transpiler.transformSync("{a: 1, b: 2}");
      const result = await vm.runInContext(code, ctx);
      expect(result.value).toEqual({ a: 1, b: 2 });
    });

    test("{ let x = 1; x } parsed as block", async () => {
      const ctx = vm.createContext({});
      const code = transpiler.transformSync("{ let x = 1; x }");
      const result = await vm.runInContext(code, ctx);
      // Block returns last expression value
      expect(result.value).toBe(1);
    });

    test("{ x: 1; y: 2 } parsed as block with labels", async () => {
      // Semicolons make this a block with labeled statements, not object
      const ctx = vm.createContext({});
      const code = transpiler.transformSync("{ x: 1; y: 2 }");
      const result = await vm.runInContext(code, ctx);
      // Block with labels returns last value
      expect(result.value).toBe(2);
    });
  });
});
```

### Part 2: Variable Persistence Tests (Integration with node:vm)

Test that variables persist across multiple REPL evaluations:

```typescript
import { expect, test, describe } from "bun:test";
import vm from "node:vm";

describe("REPL Variable Persistence", () => {
  const transpiler = new Bun.Transpiler({ loader: "tsx", replMode: true });

  // Helper to run multiple REPL lines in sequence
  async function runReplSession(lines: string[], context?: object) {
    const ctx = vm.createContext(context ?? { console });
    const results: any[] = [];

    for (const line of lines) {
      const transformed = transpiler.transformSync(line);
      const result = await vm.runInContext(transformed, ctx);
      results.push(result?.value ?? result);
    }

    return { results, context: ctx };
  }

  test("var persists across lines", async () => {
    const { results, context } = await runReplSession([
      "var x = 10",
      "x + 5",
      "x = 20",
      "x",
    ]);

    expect(results[1]).toBe(15);  // x + 5
    expect(results[3]).toBe(20);  // x after reassignment
    expect(context.x).toBe(20);   // x visible in context
  });

  test("let persists across lines (hoisted)", async () => {
    const { results } = await runReplSession([
      "let y = await Promise.resolve(100)",
      "y * 2",
    ]);

    expect(results[1]).toBe(200);
  });

  test("const becomes let, can be reassigned in later lines", async () => {
    const { results } = await runReplSession([
      "const z = await Promise.resolve(5)",
      "z",
      // In REPL, const becomes let, so next line can redeclare
      "z = 10",  // This works because const→let
      "z",
    ]);

    expect(results[1]).toBe(5);
    expect(results[3]).toBe(10);
  });

  test("function declarations persist", async () => {
    const { results, context } = await runReplSession([
      "await 1; function add(a, b) { return a + b; }",
      "add(2, 3)",
      "function multiply(a, b) { return a * b; }",  // no await
      "multiply(4, 5)",
    ]);

    expect(results[1]).toBe(5);
    expect(results[3]).toBe(20);
    expect(typeof context.add).toBe("function");
    expect(typeof context.multiply).toBe("function");
  });

  test("class declarations persist", async () => {
    const { results, context } = await runReplSession([
      "await 1; class Counter { constructor() { this.count = 0; } inc() { this.count++; } }",
      "const c = new Counter()",
      "c.inc(); c.inc(); c.count",
    ]);

    expect(results[2]).toBe(2);
    expect(typeof context.Counter).toBe("function");
  });

  test("complex session with mixed declarations", async () => {
    const { results } = await runReplSession([
      "var total = 0",
      "async function addAsync(n) { return total += await Promise.resolve(n); }",
      "await addAsync(10)",
      "await addAsync(20)",
      "total",
    ]);

    expect(results[2]).toBe(10);
    expect(results[3]).toBe(30);
    expect(results[4]).toBe(30);
  });

  test("destructuring assignment persists", async () => {
    const { results, context } = await runReplSession([
      "var { a, b } = await Promise.resolve({ a: 1, b: 2 })",
      "a + b",
      "var [x, y, z] = [10, 20, 30]",
      "x + y + z",
    ]);

    expect(results[1]).toBe(3);
    expect(results[3]).toBe(60);
    expect(context.a).toBe(1);
    expect(context.x).toBe(10);
  });
});
```

### Part 3: eval() Scoping Semantics Tests

Test that REPL behaves like eval() with proper scoping:

```typescript
import { expect, test, describe } from "bun:test";
import vm from "node:vm";

describe("REPL eval() Scoping Semantics", () => {
  const transpiler = new Bun.Transpiler({ loader: "tsx", replMode: true });

  test("var hoists to global context", async () => {
    const ctx = vm.createContext({});

    const code = transpiler.transformSync("var globalVar = 42");
    await vm.runInContext(code, ctx);

    expect(ctx.globalVar).toBe(42);
  });

  test("let/const hoisted for REPL but scoped correctly", async () => {
    const ctx = vm.createContext({});

    // With await, let is hoisted outside async wrapper
    const code1 = transpiler.transformSync("let x = await 1");
    await vm.runInContext(code1, ctx);
    expect(ctx.x).toBe(1);

    // Without await, let behavior depends on implementation
    const code2 = transpiler.transformSync("let y = 2");
    await vm.runInContext(code2, ctx);
    // y should still be accessible in REPL context
  });

  test("block-scoped let does NOT leak", async () => {
    const ctx = vm.createContext({});

    const code = transpiler.transformSync("if (await true) { let blockScoped = 1; }");
    await vm.runInContext(code, ctx);

    // blockScoped should NOT be visible in context
    expect(ctx.blockScoped).toBeUndefined();
  });

  test("function in block hoists with var (sloppy mode)", async () => {
    const ctx = vm.createContext({});

    const code = transpiler.transformSync("if (await true) { function blockFn() { return 42; } }");
    await vm.runInContext(code, ctx);

    // In sloppy mode, function in block hoists to function scope
    expect(typeof ctx.blockFn).toBe("function");
    expect(ctx.blockFn()).toBe(42);
  });

  test("this binding in function declarations", async () => {
    const ctx = vm.createContext({});

    const code = transpiler.transformSync("await 1; function greet() { return 'hello'; }");
    await vm.runInContext(code, ctx);

    // Function should be assigned to this (context) for REPL persistence
    expect(ctx.greet()).toBe("hello");
  });

  test("async function expression captures result", async () => {
    const ctx = vm.createContext({});

    const code = transpiler.transformSync("await (async () => { return 42; })()");
    const result = await vm.runInContext(code, ctx);

    expect(result.value).toBe(42);
  });

  test("Promise result NOT auto-awaited due to { value: } wrapper", async () => {
    const ctx = vm.createContext({});

    // Without wrapper, result would be auto-awaited
    // With { value: } wrapper, we get the Promise object
    const code = transpiler.transformSync("await Promise.resolve(Promise.resolve(42))");
    const result = await vm.runInContext(code, ctx);

    // The inner Promise should be in value, not auto-resolved
    expect(result.value).toBeInstanceOf(Promise);
    expect(await result.value).toBe(42);
  });
});
```

### Part 4: Edge Cases and Error Handling

```typescript
import { expect, test, describe } from "bun:test";
import vm from "node:vm";

describe("REPL Edge Cases", () => {
  const transpiler = new Bun.Transpiler({ loader: "tsx", replMode: true });

  test("empty input", () => {
    const result = transpiler.transformSync("");
    expect(result).toBe("");
  });

  test("whitespace only", () => {
    const result = transpiler.transformSync("   \n\t  ");
    expect(result.trim()).toBe("");
  });

  test("comment only", () => {
    const result = transpiler.transformSync("// just a comment");
    expect(result).toContain("// just a comment");
  });

  test("multiline input", () => {
    const input = `
      var x = await 1;
      var y = await 2;
      x + y
    `;
    const result = transpiler.transformSync(input);
    expect(result).toContain("var x");
    expect(result).toContain("var y");
    expect(result).toContain("async");
  });

  test("TypeScript syntax", () => {
    const input = "const x: number = await Promise.resolve(42)";
    const result = transpiler.transformSync(input);
    expect(result).not.toContain(": number"); // Types stripped
    expect(result).toContain("let x");
  });

  test("JSX in REPL", () => {
    const input = "await Promise.resolve(<div>Hello</div>)";
    const result = transpiler.transformSync(input);
    expect(result).toContain("async");
  });

  test("import expression (dynamic)", () => {
    // Dynamic imports should work fine
    const input = "await import('fs')";
    const result = transpiler.transformSync(input);
    expect(result).toContain("import");
  });

  test("nested await expressions", () => {
    const input = "await (await Promise.resolve(Promise.resolve(1)))";
    const result = transpiler.transformSync(input);
    expect(result).toContain("{ value:");
  });

  test("for-await-of", () => {
    const input = `
      async function* gen() { yield 1; yield 2; }
      for await (const x of gen()) { console.log(x); }
    `;
    const result = transpiler.transformSync(input);
    expect(result).toContain("var gen");
    expect(result).toContain("for await");
  });
});
```

---

## Verification Plan

### 1. Build and Basic Tests
```bash
# Build Bun with changes
bun bd

# Run the REPL transform tests
bun bd test test/js/bun/transpiler/repl-transform.test.ts
```

### 2. Manual Transform Output Verification
```typescript
// test-repl-manual.ts
const t = new Bun.Transpiler({ loader: "tsx", replMode: true });

// Object literal
console.log("Object literal:");
console.log(t.transformSync("{a: 1}"));
// Expected: contains "({a: 1})"

// Basic await
console.log("\nBasic await:");
console.log(t.transformSync("await 0"));
// Expected: (async () => { return { value: (await 0) } })()

// Variable hoisting
console.log("\nVar hoisting:");
console.log(t.transformSync("var x = await 1"));
// Expected: var x; (async () => { void (x = await 1) })()

// const → let
console.log("\nConst to let:");
console.log(t.transformSync("const x = await 1"));
// Expected: let x; (async () => { void (x = await 1) })()

// Function hoisting
console.log("\nFunction:");
console.log(t.transformSync("await 0; function foo() {}"));
// Expected: var foo; (async () => { await 0; this.foo = foo; function foo() {} })()
```

### 3. Full REPL Session Simulation
```typescript
// test-repl-session.ts
import vm from "node:vm";

const t = new Bun.Transpiler({ loader: "tsx", replMode: true });
const ctx = vm.createContext({ console, Promise });

async function repl(code: string) {
  const transformed = t.transformSync(code);
  console.log(`> ${code}`);
  console.log(`[transformed]: ${transformed}`);
  const result = await vm.runInContext(transformed, ctx);
  console.log(`= ${JSON.stringify(result?.value ?? result)}\n`);
  return result?.value ?? result;
}

// Test session
await repl("var counter = 0");
await repl("function increment() { return ++counter; }");
await repl("increment()");  // Should be 1
await repl("increment()");  // Should be 2
await repl("counter");      // Should be 2

await repl("const data = await Promise.resolve({ x: 10, y: 20 })");
await repl("data.x + data.y");  // Should be 30

await repl("class Point { constructor(x, y) { this.x = x; this.y = y; } }");
await repl("const p = new Point(3, 4)");
await repl("Math.sqrt(p.x**2 + p.y**2)");  // Should be 5
```

### 4. Verify No Regressions
```bash
# Run existing transpiler tests
bun bd test test/js/bun/transpiler/

# Run existing vm tests
bun bd test test/js/node/vm/
```

### 5. Cross-check with Node.js (Optional)
Compare transform outputs with Node.js's `processTopLevelAwait`:
```typescript
// Compare a few key transforms with Node.js output
const cases = [
  "await 0",
  "var x = await 1",
  "await 0; function foo() {}",
];
// Verify Bun output matches Node.js patterns
```
</claude-plan>
2026-01-19 00:22:36 -08:00
robobun
3898ed5e3f perf: pack boolean flags and reorder fields to reduce struct padding (#25627) 2025-12-21 17:12:42 -08:00
robobun
c59a6997cd feat(bundler): add statically-analyzable dead-code elimination via feature flags (#25462)
## Summary
- Adds `import { feature } from "bun:bundle"` for compile-time feature
flag checking
- `feature("FLAG_NAME")` calls are replaced with `true`/`false` at
bundle time
- Enables dead-code elimination through `--feature=FLAG_NAME` CLI
argument
- Works in `bun build`, `bun run`, and `bun test`
- Available in both CLI and `Bun.build()` JavaScript API

## Usage

```ts
import { feature } from "bun:bundle";

if (feature("SUPER_SECRET")) {
  console.log("Secret feature enabled!");
} else {
  console.log("Normal mode");
}
```

### CLI
```bash
# Enable feature during build
bun build --feature=SUPER_SECRET index.ts

# Enable at runtime
bun run --feature=SUPER_SECRET index.ts

# Enable in tests
bun test --feature=SUPER_SECRET
```

### JavaScript API
```ts
await Bun.build({
  entrypoints: ['./index.ts'],
  outdir: './out',
  features: ['SUPER_SECRET', 'ANOTHER_FLAG'],
});
```

## Implementation
- Added `bundler_feature_flags` (as `*const bun.StringSet`) to
`RuntimeFeatures` and `BundleOptions`
- Added `bundler_feature_flag_ref` to Parser struct to track the
`feature` import
- Handle `bun:bundle` import at parse time (similar to macros) - capture
ref, return empty statement
- Handle `feature()` calls in `e_call` visitor - replace with boolean
based on flags
- Wire feature flags through CLI arguments and `Bun.build()` API to
bundler options
- Added `features` option to `JSBundler.zig` for JavaScript API support
- Added TypeScript types in `bun.d.ts`
- Added documentation to `docs/bundler/index.mdx`

## Test plan
- [x] Basic feature flag enabled/disabled tests (both CLI and API
backends)
- [x] Multiple feature flags test
- [x] Dead code elimination verification tests
- [x] Error handling for invalid arguments
- [x] Runtime tests with `bun run --feature=FLAG`
- [x] Test runner tests with `bun test --feature=FLAG`
- [x] Aliased import tests (`import { feature as checkFeature }`)
- [x] Ternary operator DCE tests
- [x] Tests use `itBundled` with both `backend: "cli"` and `backend:
"api"`

🤖 Generated with [Claude Code](https://claude.com/claude-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: Alistair Smith <hi@alistair.sh>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-12-11 17:44:14 -08:00
robobun
509a97a435 Add --no-env-file flag to disable automatic .env loading (#24767)
## Summary

Implements `--no-env-file` CLI flag and bunfig configuration options to
disable automatic `.env` file loading at runtime and in the bundler.

## Motivation

Users may want to disable automatic `.env` file loading for:
- Production environments where env vars are managed externally
- CI/CD pipelines where .env files should be ignored
- Testing scenarios where explicit env control is needed
- Security contexts where .env files should not be trusted

## Changes

### CLI Flag
- Added `--no-env-file` flag that disables loading of default .env files
- Still respects explicit `--env-file` arguments for intentional env
loading

### Bunfig Configuration
Added support for disabling .env loading via `bunfig.toml`:
- `env = false` - disables default .env file loading
- `env = null` - disables default .env file loading  
- `env.file = false` - disables default .env file loading
- `env.file = null` - disables default .env file loading

### Implementation
- Added `disable_default_env_files` field to `api.TransformOptions` with
serialization support
- Added `disable_default_env_files` field to `options.Env` struct
- Implemented `loadEnvConfig` in bunfig parser to handle env
configuration
- Wired up flag throughout runtime and bundler code paths
- Preserved package.json script runner behavior (always skips default
.env files)

## Tests

Added comprehensive test suite (`test/cli/run/no-envfile.test.ts`) with
9 tests covering:
- `--no-env-file` flag with `.env`, `.env.local`,
`.env.development.local`
- Bunfig configurations: `env = false`, `env.file = false`, `env = true`
- `--no-env-file` with `-e` eval flag
- `--no-env-file` combined with `--env-file` (explicit files still load)
- Production mode behavior

All tests pass with debug bun and fail with system bun (as expected).

## Example Usage

```bash
# Disable all default .env files
bun --no-env-file index.js

# Disable defaults but load explicit file
bun --no-env-file --env-file .env.production index.js

# Disable via bunfig.toml
cat > bunfig.toml << 'CONFIG'
env = false
CONFIG
bun index.js
```

## Files Changed
- `src/cli/Arguments.zig` - CLI flag parsing
- `src/api/schema.zig` - API schema field with encode/decode
- `src/options.zig` - Env struct field and wiring
- `src/bunfig.zig` - Config parsing with loadEnvConfig
- `src/transpiler.zig` - Runtime wiring
- `src/bun.js.zig` - Runtime wiring
- `src/cli/exec_command.zig` - Runtime wiring
- `src/cli/run_command.zig` - Preserved package.json script runner
behavior
- `test/cli/run/no-envfile.test.ts` - Comprehensive test suite

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-17 15:04:42 -05:00
pfg
05d0475c6c Update to zig 0.15.2 (#24204)
Fixes ENG-21287

Build times, from `bun run build && echo '//' >> src/main.zig && time
bun run build`

|Platform|0.14.1|0.15.2|Speedup|
|-|-|-|-|
|macos debug asan|126.90s|106.27s|1.19x|
|macos debug noasan|60.62s|50.85s|1.19x|
|linux debug asan|292.77s|241.45s|1.21x|
|linux debug noasan|146.58s|130.94s|1.12x|
|linux debug use_llvm=false|n/a|78.27s|1.87x|
|windows debug asan|177.13s|142.55s|1.24x|

Runtime performance:

- next build memory usage may have gone up by 5%. Otherwise seems the
same. Some code with writers may have gotten slower, especially one
instance of a counting writer and a few instances of unbuffered writers
that now have vtable overhead.
- File size reduced by 800kb (from 100.2mb to 99.4mb)

Improvements:

- `@export` hack is no longer needed for watch
- native x86_64 backend for linux builds faster. to use it, set use_llvm
false and no_link_obj false. also set `ASAN_OPTIONS=detect_leaks=0`
otherwise it will spam the output with tens of thousands of lines of
debug info errors. may need to use the zig lldb fork for debugging.
- zig test-obj, which we will be able to use for zig unit tests

Still an issue:

- false 'dependency loop' errors remain in watch mode
- watch mode crashes observed

Follow-up:

- [ ] search `comptime Writer: type` and `comptime W: type` and remove
- [ ] remove format_mode in our zig fork
- [ ] remove deprecated.zig autoFormatLabelFallback
- [ ] remove deprecated.zig autoFormatLabel
- [ ] remove deprecated.BufferedWriter and BufferedReader
- [ ] remove override_no_export_cpp_apis as it is no longer needed
- [ ] css Parser(W) -> Parser, and remove all the comptime writer: type
params
- [ ] remove deprecated writer fully

Files that add lines:

```
649     src/deprecated.zig
167     scripts/pack-codegen-for-zig-team.ts
54      scripts/cleartrace-impl.js
46      scripts/cleartrace.ts
43      src/windows.zig
18      src/fs.zig
17      src/bun.js/ConsoleObject.zig
16      src/output.zig
12      src/bun.js/test/debug.zig
12      src/bun.js/node/node_fs.zig
8       src/env_loader.zig
7       src/css/printer.zig
7       src/cli/init_command.zig
7       src/bun.js/node.zig
6       src/string/escapeRegExp.zig
6       src/install/PnpmMatcher.zig
5       src/bun.js/webcore/Blob.zig
4       src/crash_handler.zig
4       src/bun.zig
3       src/install/lockfile/bun.lock.zig
3       src/cli/update_interactive_command.zig
3       src/cli/pack_command.zig
3       build.zig
2       src/Progress.zig
2       src/install/lockfile/lockfile_json_stringify_for_debugging.zig
2       src/css/small_list.zig
2       src/bun.js/webcore/prompt.zig
1       test/internal/ban-words.test.ts
1       test/internal/ban-limits.json
1       src/watcher/WatcherTrace.zig
1       src/transpiler.zig
1       src/shell/builtin/cp.zig
1       src/js_printer.zig
1       src/io/PipeReader.zig
1       src/install/bin.zig
1       src/css/selectors/selector.zig
1       src/cli/run_command.zig
1       src/bun.js/RuntimeTranspilerStore.zig
1       src/bun.js/bindings/JSRef.zig
1       src/bake/DevServer.zig
```

Files that remove lines:

```
-1      src/test/recover.zig
-1      src/sql/postgres/SocketMonitor.zig
-1      src/sql/mysql/MySQLRequestQueue.zig
-1      src/sourcemap/CodeCoverage.zig
-1      src/css/values/color_js.zig
-1      src/compile_target.zig
-1      src/bundler/linker_context/convertStmtsForChunk.zig
-1      src/bundler/bundle_v2.zig
-1      src/bun.js/webcore/blob/read_file.zig
-1      src/ast/base.zig
-2      src/sql/postgres/protocol/ArrayList.zig
-2      src/shell/builtin/mkdir.zig
-2      src/install/PackageManager/patchPackage.zig
-2      src/install/PackageManager/PackageManagerDirectories.zig
-2      src/fmt.zig
-2      src/css/declaration.zig
-2      src/css/css_parser.zig
-2      src/collections/baby_list.zig
-2      src/bun.js/bindings/ZigStackFrame.zig
-2      src/ast/E.zig
-3      src/StandaloneModuleGraph.zig
-3      src/deps/picohttp.zig
-3      src/deps/libuv.zig
-3      src/btjs.zig
-4      src/threading/Futex.zig
-4      src/shell/builtin/touch.zig
-4      src/meta.zig
-4      src/install/lockfile.zig
-4      src/css/selectors/parser.zig
-5      src/shell/interpreter.zig
-5      src/css/error.zig
-5      src/bun.js/web_worker.zig
-5      src/bun.js.zig
-6      src/cli/test_command.zig
-6      src/bun.js/VirtualMachine.zig
-6      src/bun.js/uuid.zig
-6      src/bun.js/bindings/JSValue.zig
-9      src/bun.js/test/pretty_format.zig
-9      src/bun.js/api/BunObject.zig
-14     src/install/install_binding.zig
-14     src/fd.zig
-14     src/bun.js/node/path.zig
-14     scripts/pack-codegen-for-zig-team.sh
-17     src/bun.js/test/diff_format.zig
```

`git diff --numstat origin/main...HEAD | awk '{ print ($1-$2)"\t"$3 }' |
sort -rn`

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Meghan Denny <meghan@bun.com>
Co-authored-by: tayor.fish <contact@taylor.fish>
2025-11-10 14:38:26 -08:00
Marko Vejnovic
e76570f452 feat(ENG-21362): Environment Variables Store (#23930) 2025-10-23 23:08:08 -07:00
Meghan Denny
45760cd53c ci: instrument being able to run leaksanitizer (#21142)
tests not in `test/no-validate-leaksan.txt` will run with leaksanitizer
in CI
leaks documented in `test/leaksan.supp` will not cause a test failure

-- notes about leaksanitizer

- will not catch garbage collected objects accumulated during
long-running processes
- will not catch js objects (eg a strong held to a promise)
- will catch native calls to `malloc` not `free`d
- will catch allocations made in C, Zig, C++, libc, dependencies,
dlopen'd

---------

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-09-19 02:06:02 -07:00
Meghan Denny
ef8408c797 zig: remove bun.fs_allocator alias (#22782) 2025-09-18 19:37:24 -07:00
taylor.fish
edf13bd91d Refactor BabyList (#22502)
(For internal tracking: fixes STAB-1129, STAB-1145, STAB-1146,
STAB-1150, STAB-1126, STAB-1147, STAB-1148, STAB-1149, STAB-1158)

---------

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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-09 20:41:10 -07:00
taylor.fish
437e15bae5 Replace catch bun.outOfMemory() with safer alternatives (#22141)
Replace `catch bun.outOfMemory()`, which can accidentally catch
non-OOM-related errors, with either `bun.handleOom` or a manual `catch
|err| switch (err)`.

(For internal tracking: fixes STAB-1070)

---------

Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
2025-08-26 12:50:25 -07:00
Dylan Conway
8fad98ffdb Add Bun.YAML.parse and YAML imports (#22073)
### What does this PR do?
This PR adds builtin YAML parsing with `Bun.YAML.parse`
```js
import { YAML } from "bun";
const items = YAML.parse("- item1");
console.log(items); // [ "item1" ]
```

Also YAML imports work just like JSON and TOML imports
```js
import pkg from "./package.yaml"
console.log({ pkg }); // { pkg: { name: "pkg", version: "1.1.1" } }
```
### How did you verify your code works?
Added some tests for YAML imports and parsed values.

---------

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>
2025-08-23 06:55:30 -07:00
Dylan Conway
9a2dfee3ca Fix env loader buffer overflow by using stack fallback allocator (#21416)
## Summary
- Fixed buffer overflow in env_loader when parsing large environment
variables with escape sequences
- Replaced fixed 4096-byte buffer with a stack fallback allocator that
automatically switches to heap allocation for larger values
- Added comprehensive tests to prevent regression

## Background
The env_loader previously used a fixed threadlocal buffer that could
overflow when parsing environment variables containing escape sequences.
This caused crashes when the parsed value exceeded 4KB.

## Changes
- Replaced fixed buffer with `StackFallbackAllocator` that uses 4KB
stack buffer for common cases and falls back to heap for larger values
- Updated all env parsing functions to accept a reusable buffer
parameter
- Added proper memory cleanup with defer statements

## Test plan
- [x] Added test cases for large environment variables with escape
sequences
- [x] Added test for values larger than 4KB  
- [x] Added edge case tests (empty quotes, escape at EOF)
- [x] All existing env tests continue to pass

fixes #11627
fixes BAPI-1274

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-07-28 00:13:17 -07:00
taylor.fish
07cd45deae Refactor Zig imports and file structure (part 1) (#21270)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-07-22 17:51:38 -07:00
pfg
83760fc446 Sort imports in all files (#21119)
Co-authored-by: taylor.fish <contact@taylor.fish>
2025-07-21 13:26:47 -07:00
Ciro Spaciari
964f2a8941 fix(fetch/s3) Handle backpressure when upload large bodies (#20481)
Co-authored-by: cirospaciari <6379399+cirospaciari@users.noreply.github.com>
2025-06-27 20:52:46 -07:00
190n
346e97dde2 fix bugs found by exception scope verification (#20285)
Co-authored-by: 190n <7763597+190n@users.noreply.github.com>
2025-06-18 23:08:19 -07:00
Jarred Sumner
498186764a Remove a memcpy (#20261)
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2025-06-08 05:07:09 -07:00
Jarred Sumner
3ea6133c46 CI: Remove unused top-level decls in formatter in zig (#19879)
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-23 22:49:48 -07:00
Dylan Conway
8e80afbce1 Replace string runtime flags with enum (#19827) 2025-05-22 22:36:46 -07:00
Dylan Conway
acf36d958a fix(npmrc): handle BOM conversion (#18878) 2025-05-06 22:16:56 -07:00
Christoph
b7c5bd0922 Fix crash on empty text file import (#19165)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-04-25 23:42:41 -07:00
Jarred Sumner
9646bf1a38 Fixes #16671 (#19227)
Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
2025-04-23 16:42:33 -07:00
chloe caruso
3349c995b5 no usingnamespace, organize jsc namespace, enable -fincremental (#19122)
Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
2025-04-22 16:34:15 -07:00
chloe caruso
4ec410e0d7 internal: make @import("bun") work in zig (#19096) 2025-04-17 12:32:47 -07:00
chloe caruso
903706dccf file descriptor rewrite (#18790) 2025-04-15 09:37:11 -07:00
Don Isaac
f730a355bf fix: BufferWriter never returns an error (#18981)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-04-13 08:57:41 -07:00
chloe caruso
c29933f823 implement require.extensions attempt 2 (#18686) 2025-04-01 14:31:16 -07:00
Jarred Sumner
f3da1b80bc Use macOS signpost api for tracing (#14871)
Co-authored-by: Jarred-Sumner <Jarred-Sumner@users.noreply.github.com>
Co-authored-by: Don Isaac <donald.isaac@gmail.com>
Co-authored-by: DonIsaac <DonIsaac@users.noreply.github.com>
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2025-03-31 04:13:11 -07:00
Jarred Sumner
f38d35f7c9 Revert #18562 #18478 (#18610) 2025-03-28 20:23:49 -07:00
chloe caruso
8f4575c0e4 fix: detection module type from extension (#18562) 2025-03-27 20:47:31 -07:00
Don Isaac
75144ab881 fix: reflect-metadata import order (#18086)
Co-authored-by: DonIsaac <22823424+DonIsaac@users.noreply.github.com>
2025-03-26 16:19:45 -07:00
chloe caruso
589fa6274d dev server: forgotten changes (#17985)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-03-07 17:53:07 -08:00
chloe caruso
66cf62c3c4 dev server: rewrite HMRModule, support sync esm + hot.accept (#17954)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-03-07 15:12:16 -08:00
Zack Radisic
e6cb0de539 CSS modules (#17958) 2025-03-06 23:35:06 -08:00
pfg
d502df353c Support import with { type: "json" } and others (#16624) 2025-03-06 15:04:29 -08:00
Meghan Denny
1574df835e zig: make JSValue.toBunString use JSError (#17648) 2025-02-25 13:04:44 -08:00
chloe caruso
cbeffe1b48 hmr7 (#17641) 2025-02-24 20:02:38 -08:00
chloe caruso
3b956757d9 disable async in script tags in dev server (#17517)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-02-21 11:28:27 -08:00
chloe caruso
dc5fae461d Implement simple barrel file optimization (#17514)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-02-20 22:50:51 -08:00
Don Isaac
f0e7251b61 chore: remove dead in transpiler (#17379) 2025-02-16 02:20:39 -08:00
chloe caruso
2b97d61deb chore: remove some trivial usage of usingnamespace (#17268) 2025-02-11 19:38:52 -08:00
Jarred Sumner
7a918d24a7 Fix loading react-jsxdev instead of react-jsx (#17013)
Co-authored-by: chloe caruso <git@paperclover.net>
2025-02-03 14:10:09 -08:00
Meghan Denny
26d3688e53 zig: update to 0.14.0-dev (#16862)
Co-authored-by: nektro <5464072+nektro@users.noreply.github.com>
2025-02-01 01:11:02 -08:00
Meghan Denny
676e8d1632 zig: delete is_bindgen (#16858) 2025-01-28 23:51:24 -08:00
Meghan Denny
af79cebf9e unflag experimental css and html (#16561)
Co-authored-by: nektro <5464072+nektro@users.noreply.github.com>
Co-authored-by: Zack Radisic <56137411+zackradisic@users.noreply.github.com>
2025-01-21 06:44:54 -08:00
Zack Radisic
d40f971a53 CSS fixes (#16514) 2025-01-18 23:55:54 -08:00
pfg
0d17843251 Fix bun run folder (#15117)
Co-authored-by: pfgithub <pfgithub@users.noreply.github.com>
2025-01-17 22:08:07 -08:00
chloe caruso
834ad11d48 get node:fs tests passing part 1 (#16270) 2025-01-14 20:53:02 -08:00
Jarred Sumner
c2e150f916 Revert "Skip javascriptcore's first parse step for ES Modules" (#16370) 2025-01-13 01:24:48 -08:00
pfg
ccc7bde7c6 Skip javascriptcore's first parse step for ES Modules (#15758) 2025-01-09 19:31:44 -08:00