Skip to content

Migrate to esm style - #1396

Merged
Ryang-21 merged 5 commits into
modernizationfrom
migrate-to-esm-style
Apr 24, 2026
Merged

Migrate to esm style#1396
Ryang-21 merged 5 commits into
modernizationfrom
migrate-to-esm-style

Conversation

@Ryang-21

Copy link
Copy Markdown
Contributor

What

Migrates the SDK to ESM-primary with a dual-package layout

  • package.json gains "type": "module". main now points at ./lib/cjs/index.js, module at
    consumers work.
  • build:node runs Babel twice: an ESM pass into lib/ (BABEL_MODULES=false) and a CJS pass into
    lib/cjs/ (BABEL_MODULES=commonjs). A new config/write-module-type.js drops a {"type":"commonjs"}
    sentinel into the CJS subtree so Node interprets those .js files correctly under the root
    "type":"module".
  • babel.config.js, eslint.config.js, config/build.config.js, config/set-output-dir.js,
    config/webpack.config.browser.js converted to ESM (import/export, import.meta.dirname,
    import.meta.resolve). Babel cache keyed on BABEL_MODULES so ESM and CJS passes don't stomp each other.
  • All src/**/*.ts relative imports rewritten with explicit .js extensions (required by module:
    nodenext). Dead code removed: USE_AXIOS global, module.exports re-exports from src/index.ts and
    src/browser.ts.
  • Webpack picks up extensionAlias so .js specifiers resolve to .ts siblings during the browser build.
  • Test harness (test/test-utils/stellar-sdk-import.ts) switches from require() to top-level await
    import(); test/setup-browser.ts picks one bundle based on VITE_TRANSPORT instead of probing variants.
  • ghooks dropped — it was pulling in CJS hook files that broke under "type":"module".

Why

Packages across the JS ecosystem are going ESM-only, and until this PR the SDK couldn't be consumed
cleanly from ESM projects — it shipped CJS-only output with a module.exports default hack and a
runtime require() branch that tripped up bundlers and breaks outright under Node's ESM loader. Going
ESM-primary with a CJS variant under lib/cjs/ means modern consumers get native ESM (tree-shaking,
top-level await, no interop shims) while existing CJS consumers keep working through the require
export condition. Bundling the fetch-default work into the same release avoids shipping two
back-to-back majors that each change public entry points.

@github-actions

Copy link
Copy Markdown

Size Change: +3.66 kB (+0.02%)

Total Size: 19.6 MB

📦 View Changed
Filename Size Change
dist/stellar-sdk-axios.js 5.41 MB +246 B (0%)
dist/stellar-sdk-axios.min.js 4.54 MB -477 B (-0.01%)
dist/stellar-sdk.js 5.29 MB +2.99 kB (+0.06%)
dist/stellar-sdk.min.js 4.39 MB +906 B (+0.02%)

compressed-size-action

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Migrates the SDK to be ESM-primary while maintaining CommonJS compatibility via a dual-output layout, updating build/test/tooling to work under "type": "module" and Node’s ESM resolution rules.

Changes:

  • Switch package publishing to ESM-first with conditional exports and a generated CJS subtree under lib/**/cjs.
  • Convert build/tooling configs and scripts to ESM and update source imports to explicit .js specifiers for moduleResolution: nodenext.
  • Update test utilities and HTTP client wiring to work with ESM imports and the axios/fetch variants.

Reviewed changes

Copilot reviewed 79 out of 81 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
yarn.lock Removes dependencies tied to dropped ghooks and related transitive packages.
test/test-utils/stellar-sdk-import.ts Switches test harness from require() to dynamic import() and uses top-level await for exports.
src/webauth/utils.ts Updates relative import specifiers to .js.
src/webauth/index.ts Updates exports to .js specifiers.
src/webauth/challenge_transaction.ts Updates relative imports to .js specifiers.
src/stellartoml/index.ts Updates imports for http-client/config to ESM-friendly .js paths.
src/rpc/transaction.ts Updates internal imports to .js specifiers.
src/rpc/server.ts Updates internal imports and adjusts type-only imports for ESM.
src/rpc/parsers.ts Updates internal import specifier to .js.
src/rpc/jsonrpc.ts Converts HttpClient import to type-only and ESM .js specifier.
src/rpc/index.ts Updates exports to .js specifiers and removes module.exports default hack.
src/rpc/browser.ts Updates exports to ESM .js specifiers and removes CJS default export hack.
src/rpc/axios.ts Updates imports to ESM .js specifiers and type-only usage.
src/index.ts Updates all top-level exports to explicit .js paths and removes module.exports default hack.
src/http-client/index.ts Updates exports to .js specifiers.
src/http-client/fetch-client.ts Adds interop normalization for feaxios default export shape across ESM/CJS.
src/http-client/axios.ts Updates exports to .js specifiers.
src/horizon/types/trade.ts Updates type import to .js specifier.
src/horizon/types/offer.ts Converts AssetType to type-only import and updates .js specifier.
src/horizon/types/effects.ts Updates imports to .js specifiers and uses type-only where appropriate.
src/horizon/types/assets.ts Converts AssetType to type-only import and updates .js specifier.
src/horizon/transaction_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/trades_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/trade_aggregation_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/strict_send_path_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/strict_receive_path_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/server_api.ts Updates imports to .js specifiers and uses type-only for record types.
src/horizon/server.ts Updates all internal imports to .js specifiers and type-only HttpClient.
src/horizon/payment_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/path_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/orderbook_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/operation_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/offer_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/liquidity_pool_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/ledger_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/index.ts Updates exports to .js specifiers and removes module.exports default hack.
src/horizon/horizon_axios_client.ts Updates http-client import to ESM .js specifier and type-only imports.
src/horizon/horizon_api.ts Converts Horizon types to type-only imports.
src/horizon/friendbot_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/effect_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/claimable_balances_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/call_builder.ts Updates imports to .js specifiers (including URITemplate) and type-only imports.
src/horizon/assets_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/horizon/account_response.ts Updates imports to .js specifiers.
src/horizon/account_call_builder.ts Updates imports to .js specifiers and type-only HttpClient.
src/federation/server.ts Updates imports to .js specifiers and type-only Api.
src/federation/index.ts Updates exports to .js specifiers.
src/federation/api.ts Updates type-only import to .js specifier.
src/errors/not_found.ts Updates internal import to .js specifier.
src/errors/network.ts Updates internal import to .js specifier.
src/errors/index.ts Updates exports to .js specifiers.
src/errors/bad_response.ts Updates internal import to .js specifier.
src/errors/bad_request.ts Updates internal import to .js specifier.
src/contract/wasm_spec_parser.ts Updates internal import to .js specifier.
src/contract/utils.ts Updates internal imports to .js specifiers and type-only usage.
src/contract/types.ts Converts MemoType to type-only and updates internal imports to .js.
src/contract/spec.ts Converts ScIntType to type-only and updates internal imports to .js.
src/contract/sent_transaction.ts Updates internal imports to .js specifiers and type-only usage.
src/contract/index.ts Updates exports to .js specifiers.
src/contract/client.ts Updates internal imports to .js specifiers.
src/contract/basic_node_signer.ts Updates internal imports to .js specifiers and type-only usage.
src/contract/assembled_transaction.ts Updates internal imports to .js specifiers.
src/cli/util.ts Updates imports to .js specifiers and type-only usage.
src/cli/index.ts Updates imports to .js specifiers.
src/browser.ts Updates imports/exports to ESM .js specifiers and removes CJS default hack.
src/browser-axios.ts Updates imports/exports to ESM .js specifiers and removes CJS default hack.
src/bindings/wasm_fetcher.ts Updates imports to .js specifiers.
src/bindings/types.ts Updates imports to .js specifiers.
src/bindings/index.ts Updates exports to .js specifiers.
src/bindings/generator.ts Updates imports to .js specifiers.
src/bindings/client.ts Updates imports to .js specifiers.
prettier.config.js Converts Prettier config to ESM default export.
package.json Sets "type":"module", updates entrypoints/exports for dual ESM+CJS layout, updates build scripts, drops ghooks.
eslint.config.js Converts ESLint flat config to ESM default export and ESM imports.
config/write-module-type.js Adds helper script to write package.json { type } sentinel into emitted CJS subtrees.
config/webpack.config.browser.js Converts webpack browser config to ESM and adds extensionAlias for .js.ts source specifiers.
config/set-output-dir.js Converts TS outDir setter script to ESM.
config/build.config.js Converts build config to ESM default export.
config/babel-plugin-alias-http-client.js Converts custom Babel plugin to ESM and updates rewrite patterns for .js specifiers.
bin/stellar-js Converts CLI entry from require() to ESM import.
babel.config.js Converts Babel config to ESM and updates caching to vary by BABEL_MODULES.

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

Comment thread babel.config.js
Comment on lines 39 to +43
plugins.push(
path.resolve(__dirname, "config/babel-plugin-alias-http-client.js"),
path.resolve(
import.meta.dirname,
"config/babel-plugin-alias-http-client.js",
),

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

import.meta.dirname is not a standard Node ESM feature (Node exposes import.meta.url), so this will be undefined at runtime and break Babel config resolution. Compute a dirname equivalent via fileURLToPath(import.meta.url) (as done in scripts/download-sac-spec.js) and use that for path.resolve here.

Copilot uses AI. Check for mistakes.
Comment thread config/set-output-dir.js
Comment on lines +4 to +6
const OUTPUT_DIR = process.env.OUTPUT_DIR || "lib";
const configDir = import.meta.dirname;
const tempTsconfigPath = path.resolve(configDir, "tsconfig.tmp.json");

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

import.meta.dirname is not defined in Node ESM; this script will fail when invoked by build:ts. Replace it with an ESM-safe dirname implementation derived from import.meta.url (e.g., fileURLToPath + path.dirname).

Copilot uses AI. Check for mistakes.
Comment on lines 10 to +23
@@ -17,8 +19,8 @@ const version = packageJson.version;
// import of `./http-client` (or `../http-client`) to `./http-client/axios`,
// so Horizon/rpc/stellartoml/federation all pick up axios in the same bundle.
const entryPath = buildConfig.useAxios
? path.resolve(__dirname, '../src/browser-axios.ts')
: path.resolve(__dirname, '../src/browser.ts');
? path.resolve(import.meta.dirname, "../src/browser-axios.ts")
: path.resolve(import.meta.dirname, "../src/browser.ts");

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

This config relies on import.meta.dirname, which is not provided by Node’s ESM implementation and will cause the webpack build to crash. Use an ESM-safe dirname computed from import.meta.url (e.g., fileURLToPath + path.dirname) and replace all path.resolve(import.meta.dirname, ...) occurrences.

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +36
crypto: import.meta.resolve("crypto-browserify"),
stream: import.meta.resolve("stream-browserify"),
buffer: import.meta.resolve("buffer"),

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

import.meta.resolve() returns a URL string (e.g. file:///...), but webpack’s resolve.fallback expects filesystem paths like require.resolve() returns. Convert the resolved URL to a path (e.g., via fileURLToPath(new URL(import.meta.resolve(...)))) or use createRequire(import.meta.url).resolve(...).

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +48
// Node.js environment - dynamically import the lib module. Top-level await
// in this file's module body makes this usable as a synchronous const below,
// and vitest handles the propagated async boundary transparently.
return (await import(getNodeLibPath())) as unknown as StellarSdkModule;

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

In Node ESM, import("../../lib") (a directory import) and extensionless relative imports don’t resolve the way require() does, so this dynamic import is likely to throw at runtime. Import an explicit entry file (e.g. ../../lib/index.js / ../../lib/axios/index.js) or build the specifier with a .js filename.

Copilot uses AI. Check for mistakes.
Comment on lines 103 to 107
typeof process !== "undefined" && process.env?.TRANSPORT === "axios"
? "/http-client/axios"
: "/http-client";
// eslint-disable-next-line import/no-dynamic-require, global-require
const httpClientModule = require(`${getNodeLibPath()}${subpath}`);
const httpClientModule = await import(`${getNodeLibPath()}${subpath}`);
return httpClientModule.httpClient;

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

This dynamic import uses an extensionless specifier like .../http-client / .../http-client/axios, which is not resolvable under Node’s ESM rules. Use explicit .js file specifiers (e.g. /http-client/index.js and /http-client/axios.js) when constructing the import path.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Modernization 2/4: Remove CJS constructs and make the library ESM compliant

3 participants