TanStack / TanStack/form

@tanstack/devtools-event-client ships unconditionally in production bundles via form-core

Open
#2,132 4 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

v1 v2: needs investigation
Dominant language
TypeScript
Stars
6.7k
Forks
682
Avg merge
5d 18h
Merged PRs (30d)
7

Description

Describe the bug

@tanstack/form-core unconditionally imports and uses @tanstack/devtools-event-client in production builds. The devtools event client (7.5 KB unminified) is a hard dependency in form-core's package.json and is imported at the top level of FormApi.ts and utils.ts with no process.env.NODE_ENV guard, no import.meta.env.DEV check, and no dead-code-elimination hint.

This means every app using @tanstack/react-form ships the full devtools plugin system to users, regardless of whether TanStack Devtools is installed or used.

What ships in production

Analyzing a Vite production build, the useForm chunk contains:

  • The full EventClient class from @tanstack/devtools-event-client (~7.5 KB source, ~4 KB minified)
  • tanstack-devtools-global event listeners
  • tanstack-connect / tanstack-connect-success connection handshake logic
  • Retry loops with setInterval for reconnection attempts
  • CustomEvent dispatching infrastructure
  • 🌴 [tanstack-devtools:form-devtools-plugin] debug log strings (minified but present)
  • @tanstack/pacer-lite's liteThrottle (~0.5 KB) used solely to throttle devtools state broadcasting

In our production bundle, this accounts for ~11.6 KB of dev-only code in the useForm chunk (39 KB total).

Root cause

In packages/form-core/src/EventClient.ts:

import { EventClient } from '@tanstack/devtools-event-client'

class FormEventClient extends EventClient {
  constructor() {
    super({ pluginId: 'form-devtools', reconnectEveryMs: 1000 })
  }
}

export const formEventClient = new FormEventClient()

This singleton is instantiated unconditionally at module load time. It's then imported and used throughout FormApi.ts (mount, update, reset, submit) and utils.ts (throttleFormState).

Because the formEventClient singleton has side effects at instantiation (it creates internal state, sets up connection properties), bundlers cannot tree-shake it even though form-core declares "sideEffects": false.

Related issues
  • TanStack/devtools#112 addressed production overhead for @tanstack/react-devtools components via @tanstack/devtools-vite, but that only strips the devtools UI — it does not address the event client baked into form-core itself.
  • #1759 addressed an Angular SSR crash from CustomEvent usage in the event client, but the fix (bumping the event client version) did not address the production bundling concern.
Expected behavior

The devtools event client infrastructure should not be included in production builds. Possible approaches:

  1. Guard with process.env.NODE_ENV !== 'production' — wrap devtools imports and usage so bundlers can dead-code-eliminate the entire devtools path in production builds.
  2. Lazy/conditional initialization — only instantiate the FormEventClient when devtools are actually connected (e.g., when globalThis.__TANSTACK_EVENT_TARGET__ exists).
  3. Move devtools integration to the devtools package — instead of form-core importing the event client, have @tanstack/react-devtools hook into form instances externally (similar to how React DevTools hooks into React).
Your minimal, reproducible example
  1. npm create vite@latest my-app -- --template react-ts
  2. npm install @tanstack/react-form
  3. Use useForm in any component
  4. npx vite build && npx vite-bundle-visualizer
  5. Observe @tanstack/devtools-event-client and @tanstack/pacer-lite in the production bundle
Steps to reproduce
  1. Install @tanstack/react-form@1.28.6 (or latest 1.29.0)
  2. Build for production with Vite (or any bundler)
  3. Inspect the output chunks — the devtools event client is present
Platform
  • macOS
  • Node 24.x
  • Vite 7.3.1
  • @tanstack/react-form 1.28.6
  • @tanstack/form-core 1.28.6
  • @tanstack/devtools-event-client 0.4.3
TanStack Form adapter

react-form

TanStack Form version

1.28.6

TypeScript version

5.6.3

Additional context

The @tanstack/pacer-lite dependency (hard dep of form-core) is also only used for throttleFormState which exclusively serves the devtools state broadcasting. If the devtools code were properly gated, pacer-lite could also be eliminated from production bundles.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with packages/form-core/src/EventClient.ts, then trace its imports and uses in FormApi.ts and utils.ts. Reproduce the issue with the listed Vite production build steps and inspect the output bundle. Done means the devtools event client and its devtools-only dependencies no longer ship in production bundles while the integration remains available when intended.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript, vite
Domain
build-system, devtools, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.