Skip to content

package.json exports has no browser condition, so bundlers resolve the Node build and the browser field is ignored #610

Description

@AmaadMartin

Part of #607.

Describe the bug

import { LlmAgent } from '@google/adk' in a browser bundle resolves to the Node build (dist/esm), which pulls fs, path, http, zlib and fails.

core/package.json does declare a top-level browser field:

"browser": "./dist/web/index_web.js",

but it is never consulted, because the exports map takes precedence over the legacy browser field in every modern bundler, and exports has no browser condition:

"exports": {
  ".": {
    "types": "./dist/types/index.d.ts",
    "import": "./dist/esm/index.js",
    "require": "./dist/cjs/index.js",
    "default": "./dist/esm/index.js"
  }
}

Second problem: "." is the only declared subpath, so a consumer cannot opt in by hand either — @google/adk/dist/web/index_web.js is rejected by the exports map:

✘ [ERROR] Could not resolve "@google/adk/dist/web/index_web.js"
   The path "./dist/web/index_web.js" is not exported by package "@google/adk"

The web build is therefore unreachable by name. The only way to reach it today is a relative path into node_modules, or a bundler alias.

To Reproduce

echo "import {LlmAgent} from '@google/adk';" > t.ts

npx esbuild t.ts --bundle --platform=browser --target=chrome138
# 625 errors: Could not resolve "fs" / "path" / "http" / "zlib" / ...
# (i.e. it resolved dist/esm, the Node build)

echo "import {LlmAgent} from '@google/adk/dist/web/index_web.js';" > t2.ts
npx esbuild t2.ts --bundle --platform=browser
# ✘ [ERROR] ... is not exported by package "@google/adk"

Expected behavior

import from '@google/adk' with --platform=browser (or Vite/webpack/Rollup browser targets) should resolve dist/web.

Proposed fix

core/package.json — add a browser condition, and expose the web subpath:

   "exports": {
     ".": {
       "types": "./dist/types/index.d.ts",
+      "browser": "./dist/web/index_web.js",
       "import": "./dist/esm/index.js",
       "require": "./dist/cjs/index.js",
       "default": "./dist/esm/index.js"
-    }
+    },
+    "./dist/web/*": "./dist/web/*",
+    "./package.json": "./package.json"
   },

Two ordering notes that matter:

  • "browser" must come before "import"/"require". Conditions are matched in declaration order, and import would otherwise win for ESM browser bundles.
  • "types" stays first so TypeScript keeps resolving declarations.

Consider a "worker" condition too, pointing at the same web entry — relevant for Cloudflare Workers and similar, though note the Chrome Prompt API is unavailable in Web Workers so it is not needed for that use case.

Related: dist/web ships no type declarations

Once the subpath is reachable, deep imports are implicitly any, because declarations live in dist/types with nothing beside dist/web/*.js. In my project I worked around this by pointing tsc at dist/types and esbuild at dist/web for the same specifier, via paths + an alias.

A types condition on the subpath export would remove the need:

"./dist/web/*": { "types": "./dist/types/*", "default": "./dist/web/*" }

Happy to split that into its own issue if preferred.

Verification

# should resolve dist/web, not dist/esm
npx esbuild t.ts --bundle --platform=browser --target=chrome138

Note this fix alone does not produce a clean bundle — it correctly routes to dist/web, which still has the defects in #608 (unparseable apigee_llm.js), #609 (createRequire banner) and the remaining Node leakage. #608 in particular must land or this change simply surfaces a different error.

Environment

  • @google/adk main @ 8944bfb, core v1.5.0 (also reproduced on published 1.4.0)

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions