[GH-ISSUE #8428] Better Auth breaks with bun build --compile due to dynamic imports in core and telemetry #28413

Closed
opened 2026-04-17 19:52:13 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @kulterryan on GitHub (Mar 5, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/8428

Originally assigned to: @himself65 on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Create an Elysia backend with Better Auth mounted via .mount(auth.handler) using Prisma adapter
  2. Compile with bun build --compile --target bun --outfile ./dist/server ./src/index.ts
  3. Run the compiled binary: ./dist/server
  4. Better Auth routes fail at runtime

Works fine when running with bun run src/index.ts directly.

Current vs. Expected behavior

Expected: Better Auth routes (/api/v1/sign-in/email, /api/v1/session, etc.) should work when running the compiled binary.

Actual: Better Auth fails at runtime because two internal packages use dynamic import patterns that cannot be bundled by bun build --compile:

1. @better-auth/core — Variable-based dynamic import

// node_modules/@better-auth/core/dist/async_hooks/index.mjs
let moduleName = "node:async_hooks";
const AsyncLocalStoragePromise = import(
  /* @vite-ignore */
  /* webpackIgnore: true */
  moduleName  // ← stored in variable, bundler can't statically resolve
).then((mod) => mod.AsyncLocalStorage)

2. @better-auth/telemetryFunction() constructor bypasses all bundlers

// node_modules/@better-auth/telemetry/dist/index.mjs
const importRuntime = (m) => Function("mm", "return import(mm)")(m);
const [{ default: fs }, { default: path }] = await Promise.all([
  importRuntime("fs/promises"),
  importRuntime("path")
]);
const raw = await fs.readFile(path.join(cwd, "package.json"), "utf-8");

This also reads package.json from disk, which doesn't exist in a compiled binary context.

Workaround: Mark all Better Auth packages as --external in the build command, but this requires node_modules/ at runtime which defeats the purpose of --compile.

What version of Better Auth are you using?

1.3.28

System info

Runtime: Bun 1.3.3
Framework: Elysia 1.4.16
Database: Prisma 7 (PostgreSQL) with @prisma/adapter-pg
OS: Linux (Docker, oven/bun:1.3.3-slim)
Build: bun build --compile --target bun

Which area(s) are affected? (Select all that apply)

Backend, Package

Auth config (if applicable)

import { betterAuth } from 'better-auth';
import { prismaAdapter } from 'better-auth/adapters/prisma';
import { admin, emailOTP } from 'better-auth/plugins';

export const auth = betterAuth({
  database: prismaAdapter(prisma, { provider: 'postgresql' }),
  plugins: [admin(), emailOTP({ otpLength: 6, expiresIn: 300 })],
  emailAndPassword: { enabled: true },
  basePath: '/api/v1',
});

// Mounted in Elysia:
// app.mount(auth.handler)

Additional context

Suggested fix for the Better Auth team:

  1. @better-auth/core: Use a static import string instead of a variable:

    // Before (broken)
    let moduleName = "node:async_hooks";
    import(moduleName)
    
    // After (bundler-friendly)
    import("node:async_hooks")
    
  2. @better-auth/telemetry: Use static imports with graceful fallback:

    // Before (broken)
    const importRuntime = (m) => Function("mm", "return import(mm)")(m);
    
    // After (bundler-friendly)
    try {
      const fs = await import("fs/promises");
      const path = await import("path");
      // ...
    } catch {
      // Telemetry unavailable in compiled binary — skip silently
    }
    

Current workaround (requires node_modules/ at runtime):

bun build --compile --target bun \
  --external better-auth \
  --external @better-auth/core \
  --external @better-auth/telemetry \
  --external @better-auth/utils \
  --external better-call \
  --external kysely \
  --outfile ./dist/server ./src/index.ts
Originally created by @kulterryan on GitHub (Mar 5, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/8428 Originally assigned to: @himself65 on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Create an Elysia backend with Better Auth mounted via `.mount(auth.handler)` using Prisma adapter 2. Compile with `bun build --compile --target bun --outfile ./dist/server ./src/index.ts` 3. Run the compiled binary: `./dist/server` 4. Better Auth routes fail at runtime Works fine when running with `bun run src/index.ts` directly. ### Current vs. Expected behavior **Expected:** Better Auth routes (`/api/v1/sign-in/email`, `/api/v1/session`, etc.) should work when running the compiled binary. **Actual:** Better Auth fails at runtime because two internal packages use dynamic import patterns that cannot be bundled by `bun build --compile`: **1. `@better-auth/core` — Variable-based dynamic import** ```javascript // node_modules/@better-auth/core/dist/async_hooks/index.mjs let moduleName = "node:async_hooks"; const AsyncLocalStoragePromise = import( /* @vite-ignore */ /* webpackIgnore: true */ moduleName // ← stored in variable, bundler can't statically resolve ).then((mod) => mod.AsyncLocalStorage) ``` **2. `@better-auth/telemetry` — `Function()` constructor bypasses all bundlers** ```javascript // node_modules/@better-auth/telemetry/dist/index.mjs const importRuntime = (m) => Function("mm", "return import(mm)")(m); const [{ default: fs }, { default: path }] = await Promise.all([ importRuntime("fs/promises"), importRuntime("path") ]); const raw = await fs.readFile(path.join(cwd, "package.json"), "utf-8"); ``` This also reads `package.json` from disk, which doesn't exist in a compiled binary context. **Workaround:** Mark all Better Auth packages as `--external` in the build command, but this requires `node_modules/` at runtime which defeats the purpose of `--compile`. ### What version of Better Auth are you using? 1.3.28 ### System info ``` Runtime: Bun 1.3.3 Framework: Elysia 1.4.16 Database: Prisma 7 (PostgreSQL) with @prisma/adapter-pg OS: Linux (Docker, oven/bun:1.3.3-slim) Build: bun build --compile --target bun ``` ### Which area(s) are affected? (Select all that apply) Backend, Package ### Auth config (if applicable) ```typescript import { betterAuth } from 'better-auth'; import { prismaAdapter } from 'better-auth/adapters/prisma'; import { admin, emailOTP } from 'better-auth/plugins'; export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: 'postgresql' }), plugins: [admin(), emailOTP({ otpLength: 6, expiresIn: 300 })], emailAndPassword: { enabled: true }, basePath: '/api/v1', }); // Mounted in Elysia: // app.mount(auth.handler) ``` ### Additional context **Suggested fix for the Better Auth team:** 1. **`@better-auth/core`**: Use a static import string instead of a variable: ```javascript // Before (broken) let moduleName = "node:async_hooks"; import(moduleName) // After (bundler-friendly) import("node:async_hooks") ``` 2. **`@better-auth/telemetry`**: Use static imports with graceful fallback: ```javascript // Before (broken) const importRuntime = (m) => Function("mm", "return import(mm)")(m); // After (bundler-friendly) try { const fs = await import("fs/promises"); const path = await import("path"); // ... } catch { // Telemetry unavailable in compiled binary — skip silently } ``` **Current workaround** (requires `node_modules/` at runtime): ```bash bun build --compile --target bun \ --external better-auth \ --external @better-auth/core \ --external @better-auth/telemetry \ --external @better-auth/utils \ --external better-call \ --external kysely \ --outfile ./dist/server ./src/index.ts ```
GiteaMirror added the lockedbug labels 2026-04-17 19:52:13 -05:00
Author
Owner

@kulterryan commented on GitHub (Mar 5, 2026):

Closing to re-create with the proper issue template format.

<!-- gh-comment-id:4007685758 --> @kulterryan commented on GitHub (Mar 5, 2026): Closing to re-create with the proper issue template format.
Author
Owner

@himself65 commented on GitHub (Mar 6, 2026):

Is it possible to make node:async_hooks an external?

<!-- gh-comment-id:4013673081 --> @himself65 commented on GitHub (Mar 6, 2026): Is it possible to make `node:async_hooks` an external?
Author
Owner

@himself65 commented on GitHub (Mar 6, 2026):

// Before (broken)
const importRuntime = (m) => Function("mm", "return import(mm)")(m);

// After (bundler-friendly)
try {
  const fs = await import("fs/promises");
  const path = await import("path");
  // ...
} catch {
  // Telemetry unavailable in compiled binary — skip silently
}

Just comment, this is not bundler-friendly. It will crash some bundler that doesn't support TLA

<!-- gh-comment-id:4013809580 --> @himself65 commented on GitHub (Mar 6, 2026): ```ts // Before (broken) const importRuntime = (m) => Function("mm", "return import(mm)")(m); // After (bundler-friendly) try { const fs = await import("fs/promises"); const path = await import("path"); // ... } catch { // Telemetry unavailable in compiled binary — skip silently } ``` Just comment, this is not bundler-friendly. It will crash some bundler that doesn't support TLA
Author
Owner

@himself65 commented on GitHub (Mar 6, 2026):

  1. @better-auth/core: Use a static import string instead of a variable:
    // Before (broken)
    let moduleName = "node:async_hooks";
    import(moduleName)

    // After (bundler-friendly)
    import("node:async_hooks")

For this, we already fixed this in the latest version

<!-- gh-comment-id:4013847060 --> @himself65 commented on GitHub (Mar 6, 2026): > 1. **`@better-auth/core`**: Use a static import string instead of a variable: > // Before (broken) > let moduleName = "node:async_hooks"; > import(moduleName) > > // After (bundler-friendly) > import("node:async_hooks") For this, we already fixed this in the latest version
Author
Owner

@kulterryan commented on GitHub (Mar 7, 2026):

upgrading to latest version resolved the issue.

<!-- gh-comment-id:4017302695 --> @kulterryan commented on GitHub (Mar 7, 2026): upgrading to latest version resolved the issue.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28413