Skip to main content

Bundle size and module formats

The SDK ships a CommonJS and an ESM build, and keeps its two heavy dependencies, Zod and socket.io-client, out of the import graph until they are used. A project that calls a single method, leaves validation off and never opens a socket loads about 49 kB minified (10.9 kB gzip), down from 706 kB (141 kB gzip) in 1.0.158.

What ships in the package​

FieldValueUsed by
maindist/index.jsNode and any CommonJS consumer
moduleesm/index.jsBundlers (webpack, Vite, Rollup, esbuild)
typesdist/index.d.tsTypeScript
sideEffectsfalseBundlers, to drop unused modules
exports., ./types, ./dist/*, ./esm/*, ./package.jsonNode and bundlers that read the exports map

The exports map sends import to the ESM build and require to the CommonJS build, for both the root and oneentry/types. It also exposes ./dist/* and ./esm/*, so existing deep imports (oneentry/dist/<module>/<module>Interfaces and the rest) keep resolving, and your current setup doesn't need to change.

sideEffects: false tells the bundler that importing an SDK module has no side effects. That is what makes tree-shaking work: modules you don't use are dropped from the output.

Zod loads only when validation runs​

Response validation is off by default. The schemas and validation helpers are loaded on demand, the first time a response has to be validated. (Earlier versions imported the schemas statically in every module, which put Zod into every bundle even when nothing was validated.)

  • With validation.enabled: false (the default), Zod and the per-module schemas (about 400 kB) end up in chunks that are never requested.
  • With validation.enabled: true, validation works as before; the schemas are fetched the first time they are needed.
  • In Node, require('oneentry') no longer loads Zod at startup.

These figures assume a bundler that does code splitting, the default in webpack, Vite and Rollup. A bundle forced into a single file still shrinks, but only to ~447 kB, because Zod is inlined even though it never runs.

The public API is the same. The internal _validateResponse helper is now async, which matters only if you extended the SDK's base classes yourself.

socket.io loads only when you open a socket​

WS.connect() is still synchronous and returns a socket.io Socket, but socket.io-client (~43 kB) is imported the first time connect() is called.

Until the chunk loads, the returned object queues every call you make on it (on, emit, disconnect) and replays them onto the real socket in the same tick the socket is created, before the connection can deliver anything. No event is lost:

// Nothing changes in normal use — handlers registered here always fire.
// Requires an access token: call AuthProvider.auth() first. With only auth.refreshToken
// in the config, make an authorized call (for example Users.getUser()) before connect()
// so the SDK refreshes the token.
const socket = WS.connect();

socket.on('connect', () => console.log('WebSocket connected'));
socket.on('my_event', (payload) => console.log(payload));

Reading the connection state early is still accurate, because a freshly created socket isn't connected either: id is undefined and connected is false, as before.

The one difference: a method that has to return something (for example listeners()) can't answer before the chunk arrives, and nested objects such as socket.io are reachable only after it loads. Registering handlers and emitting, the normal use, are unaffected.

Bundle size at a glance​

ScenarioMinifiedGzip
Before (1.0.158, every consumer)706 kB141 kB
Validation off, no socket49 kB10.9 kB
Validation off, socket opened~92 kB~24 kB
Single-file bundle, no code splitting~447 kB~94 kB

Measured on 1.0.173 (1.0.158 for the first row) with esbuild 0.24.2: ESM output, --bundle --splitting --minify, browser platform, an app that calls Pages.getPageByUrl once (plus WS.connect() for the socket row); gzip at level 9. Other bundlers give slightly different numbers.

Getting the smallest build​

  • Leave validation off in production. Turn it on during development to catch data inconsistencies, then off again, so the schemas stay out of the loaded code.
  • Let your bundler split code. Code splitting is on by default in webpack, Vite and Rollup. Disabling it inlines the lazy chunks and gives most of the savings back.
  • Import types with import type. Type imports are erased at compile time; see Importing types.
  • Destructure only the modules you use. With sideEffects: false and the ESM build, unused modules are dropped from the output.