Skip to content

deepagents fails to build with Vite/Rollup in browser targets due to named Node.js builtin imports #292

@BjoernSchotte

Description

Description

Importing anything from deepagents in a Vite browser build fails with hard errors. The built dist/index.js is a single flat bundle that contains named imports from Node.js builtins. Vite externalizes node:* modules for browser compatibility, but named imports (as opposed to default imports) cause Rollup to fail because it verifies that the binding exists in the externalized module.

The library's architecture already supports browser use — BackendProtocol, StateBackend, StoreBackend, all middleware, and createDeepAgent have zero Node.js dependencies. The issue is purely packaging: the single barrel export bundles Node-only modules alongside browser-safe ones into a flat dist/index.js.

Package version: 1.8.1 (latest on npm)
Vite version: 7.3.1 (latest)

Reproduction

Minimal repro: https://github.com/BjoernSchotte/deepagents-vite-repro

git clone https://github.com/BjoernSchotte/deepagents-vite-repro.git
cd deepagents-vite-repro
bun install   # or npm install
bun run build # or npx vite build

Actual Behavior

The build fails with 3 sequential hard errors (each one hidden behind the previous):

Error 1: node:async_hooks (from @langchain/langgraph, not deepagents)

"AsyncLocalStorage" is not exported by "__vite-browser-external"
  import { AsyncLocalStorage } from "node:async_hooks";

Error 2: "path" (from deepagents backends/utils.ts)

After stubbing node:async_hooks via resolve.alias:

"basename" is not exported by "__vite-browser-external"
  import { basename } from "path";

Error 3: node:child_process (from deepagents backends/filesystem.ts / local-shell.ts)

After also stubbing path:

"spawn" is not exported by "__vite-browser-external"
  import cp, { spawn } from "node:child_process";

After stubbing all 6 node:* modules + path via Vite resolve.alias, the build succeeds (840 modules, zero errors). The Node-only code paths are dead code when using createDeepAgent + StateBackend.

Why Named Imports Fail But Default Imports Don't

Vite externalizes unresolvable modules as __vite-browser-external (an empty object). Default imports (import fs from "node:fs") succeed because fs just becomes {} — Rollup doesn't verify property access at build time. Named imports (import { spawn } from "node:child_process") fail because Rollup verifies the binding exists as an export.

The built dist/index.js lines 5–18:

import micromatch from "micromatch";             // ✅ default import, warning only
import { basename } from "path";                 // ❌ named import, HARD FAIL
import fs from "node:fs/promises";               // ✅ default import, warning only
import fs$1 from "node:fs";                      // ✅ default import, warning only
import path from "node:path";                    // ✅ default import, warning only
import cp, { spawn } from "node:child_process"; // ❌ named import `spawn`, HARD FAIL
import fg from "fast-glob";                      // ✅ default import, warning only
import os from "node:os";                        // ✅ default import, warning only

Only 2 of the 14 Node.js-related imports in dist/index.js cause hard build failures.

Suggested Fix

Minimal fix (one-line change)

Replace import { basename } from "path" in backends/utils.ts (line 10) with an inline function:

- import { basename } from "path";
+ function basename(p: string): string {
+   const i = p.lastIndexOf("/");
+   return i === -1 ? p : p.slice(i + 1);
+ }

This eliminates Error 2. Error 3 (spawn) would still require consumer-side aliasing, but consumers would only need to stub node:child_process (and node:async_hooks which is a @langchain/langgraph issue, not deepagents).

Proper fix: deepagents/browser subpath export

Add a src/browser.ts barrel that re-exports only browser-safe modules (everything except config.ts, FilesystemBackend, LocalShellBackend, skills/loader.ts, middleware/agent-memory.ts). Add a second tsdown entry point and update the exports map:

"./browser": {
  "import": { "types": "./dist/browser.d.ts", "default": "./dist/browser.js" }
}

This would let browser consumers write:

import { createDeepAgent, StateBackend } from "deepagents/browser";

with zero Vite configuration needed.

Workaround

Add resolve.alias entries in vite.config.ts to stub the Node builtins with modules that provide the named exports Rollup needs:

resolve: {
  alias: {
    'node:async_hooks': './src/stubs/async-hooks.ts',   // for @langchain/langgraph
    'node:fs/promises': './src/stubs/fs-promises.ts',
    'node:fs': './src/stubs/fs.ts',
    'node:path': './src/stubs/path.ts',
    'node:child_process': './src/stubs/child-process.ts',
    'node:os': './src/stubs/os.ts',
    'path': './src/stubs/path.ts',
  }
}

The stubs provide the named exports so the build succeeds; the Node-only code is dead code when using createDeepAgent + StateBackend.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions