Skip to content

fix(ts): anchor.BN undefined in ESM — guard CJS globals in index.ts#4525

Open
eteen12 wants to merge 3 commits into
otter-sec:masterfrom
eteen12:fix/bn-esm-export-3711
Open

fix(ts): anchor.BN undefined in ESM — guard CJS globals in index.ts#4525
eteen12 wants to merge 3 commits into
otter-sec:masterfrom
eteen12:fix/bn-esm-export-3711

Conversation

@eteen12
Copy link
Copy Markdown
Contributor

@eteen12 eteen12 commented May 12, 2026

fixes #3711

what's happening

dist/esm/index.js is what bundlers (Vite, Webpack 5, esbuild) load when they follow the "module" field. that file gets these lines verbatim from TypeScript:

if (!isBrowser) {
  exports.workspace = require("./workspace.js").default;
  exports.Wallet = require("./nodewallet.js").default;
}

exports and require are CJS globals — they don't exist in ESM. so the whole module throws a ReferenceError on init and anchor.BN (along with everything else) comes back undefined. users see it as anchor.BN is not a constructor with no obvious reason why.

changes

  • src/index.ts — wrap the CJS block in typeof exports !== "undefined" so the ESM build skips it instead of crashing

why no exports field

an earlier revision of this PR also added an "exports" field to package.json to make the import/require/browser routing explicit. dropped it: once exports is defined, Node disables the legacy .js and /index.js auto-resolution for subpaths, which broke a handful of in-repo tests (and would silently break any downstream user) doing deep imports like @anchor-lang/core/dist/cjs/.... the source guard alone fixes the original BN crash; bundlers still pick up "module", Node still uses "main", nothing else needs to change.

Copilot AI review requested due to automatic review settings May 12, 2026 15:34
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

@eteen12 is attempting to deploy a commit to the Solana Foundation Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR fixes a ReferenceError crash in the ESM build of @anchor-lang/core caused by bare exports and require CJS globals being referenced at module init time, and adds a proper "exports" field to package.json to give bundlers and Node.js a clear ESM/CJS/browser routing contract.

  • src/index.ts: Wraps the workspace/Wallet assignment block with typeof exports !== "undefined" so that the ESM build silently skips it instead of crashing — meaning anchor.BN and all other re-exports are no longer collateral damage.
  • package.json: Introduces an "exports" map routing "browser" → browser bundle, "import" → ESM, "require" → CJS. The condition order is correct and TypeScript type resolution is unaffected since both dist/esm/ and dist/cjs/ emit .d.ts files via "declaration": true in tsconfig.base.json. A "default" fallback is absent, which could affect non-standard runtimes or tooling that matches none of the three conditions.

Confidence Score: 4/5

Safe to merge for the targeted fix; the two minor package.json concerns (no "default" fallback, no subpath wildcards) are low risk in practice but worth addressing before a major release.

The index.ts guard is minimal, correct, and directly addresses the crash. The exports map routing is accurate for the three main consumer environments. The only open questions are the missing "default" fallback (which would affect exotic runtimes) and the potential for breaking any consumer who imports internal dist/ subpaths directly — neither is likely to affect the majority of users but neither has been validated.

ts/packages/anchor/package.json — the new exports field warrants a second look for the missing "default" fallback and the absence of subpath wildcard exports.

Important Files Changed

Filename Overview
ts/packages/anchor/src/index.ts Adds typeof exports !== "undefined" guard around the CJS-only block so the ESM build no longer throws a ReferenceError on init; the fix is minimal and correct — exports and require always coexist in CJS, so checking one is sufficient.
ts/packages/anchor/package.json Adds an exports field routing browser→browser bundle, import→ESM, require→CJS. Condition order and paths are correct; however the map has no "default" fallback and no explicit subpath wildcards, which is a mild breaking change for any consumer importing deep dist/ paths.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Consumer["Consumer imports\n@anchor-lang/core"]
    Consumer -->|"exports field\npresent (new)"| ExportsMap["exports map resolution"]
    ExportsMap -->|"browser condition\n(bundler, browser target)"| BrowserBundle["dist/browser/index.js\n(browser bundle)"]
    ExportsMap -->|"import condition\n(Node.js ESM / SSR bundler)"| ESMBundle["dist/esm/index.js\n(ESM build)"]
    ExportsMap -->|"require condition\n(Node.js CJS)"| CJSBundle["dist/cjs/index.js\n(CJS build)"]
    ESMBundle --> Guard{"typeof exports\n!== 'undefined'?\n(new guard)"}
    Guard -->|"false — ESM\nno ReferenceError"| ExportsOK["Module loads cleanly\nBN, web3, etc. available"]
    Guard -->|"true — CJS"| CJSBlock["exports.workspace = ...\nexports.Wallet = ..."]
    CJSBundle --> CJSBlock2["exports.workspace = ...\nexports.Wallet = ..."]
    CJSBlock2 --> ExportsOK2["Module loads cleanly\nworkspace & Wallet available"]
Loading

Reviews (1): Last reviewed commit: "fix(ts): guard CJS globals in index.ts s..." | Re-trigger Greptile

Comment thread ts/packages/anchor/package.json Outdated
Comment thread ts/packages/anchor/package.json Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes initialization failure of the ESM entrypoint (used by modern bundlers) and formalizes package entrypoint routing via package.json#exports so import/require/browser consumers resolve to the intended builds.

Changes:

  • Guard the CJS-only exports/require block in src/index.ts so ESM consumers don’t crash on module init.
  • Add an "exports" map in package.json to route browser/import/require to the correct dist outputs.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
ts/packages/anchor/src/index.ts Adds a runtime guard to prevent CJS globals usage from crashing the ESM build at import time.
ts/packages/anchor/package.json Introduces an "exports" map to define explicit entrypoints for browser, ESM importers, and CJS require.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ts/packages/anchor/src/index.ts Outdated
Comment thread ts/packages/anchor/package.json Outdated
Comment thread ts/packages/anchor/package.json Outdated
@eteen12
Copy link
Copy Markdown
Contributor Author

eteen12 commented May 12, 2026

fixed bot feedback

@eteen12 eteen12 force-pushed the fix/bn-esm-export-3711 branch from 1d39c15 to 6fb73ae Compare May 12, 2026 18:13
fixes otter-sec#3711

dist/esm/index.js is what bundlers (Vite, Webpack 5, esbuild) load via
the "module" field. that file gets these lines verbatim from TypeScript:

    if (!isBrowser) {
      exports.workspace = require("./workspace.js").default;
      exports.Wallet = require("./nodewallet.js").default;
    }

exports and require are CJS globals — they don't exist in ESM, so the
whole module throws a ReferenceError on init and anchor.BN (along with
everything else) comes back undefined. users see it as
"anchor.BN is not a constructor" with no obvious reason why.

wrap the CJS block in typeof exports !== "undefined" so the ESM build
skips it instead of crashing.
@eteen12 eteen12 force-pushed the fix/bn-esm-export-3711 branch from 6fb73ae to 541b551 Compare May 12, 2026 21:49
Comment thread ts/packages/anchor/src/index.ts Outdated
Co-authored-by: Jamie Hill-Daniel <134328753+jamie-osec@users.noreply.github.com>
@eteen12 eteen12 requested a review from jamie-osec May 13, 2026 21:40
Comment thread ts/packages/anchor/src/index.ts Outdated
Co-authored-by: Jamie Hill-Daniel <134328753+jamie-osec@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

anchor.BN is not a constructor ... due to tsconfig.json

3 participants