Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/event-client-production-stripping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@tanstack/devtools-event-client': minor
---

The root export of `@tanstack/devtools-event-client` now resolves to a no-op
outside development (`process.env.NODE_ENV !== 'development'`), so the real
`EventClient` is tree-shaken out of production bundles by default.

If you want devtools events to keep working in production, import the real
client from the new `@tanstack/devtools-event-client/production` subpath, which
always ships the real implementation. The public API is identical between the
two imports.
24 changes: 24 additions & 0 deletions docs/building-custom-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,27 @@ For usage details, see the [Using devtools-utils](./devtools-utils) guide.
Once your plugin is working, you can share it with the community by publishing it to npm and submitting it to the TanStack Devtools Marketplace. The marketplace is a registry of third-party plugins that users can discover and install directly from the devtools UI.

For submission instructions and the registry format, see [Third-party Plugins](./third-party-plugins).

## Production builds

By default the **root import** of `@tanstack/devtools-event-client` no-ops
outside development. When your bundler sets `process.env.NODE_ENV` to anything
other than `'development'`, the real client is replaced by a no-op and
tree-shaken out of your production bundle:

```ts
// dev: real client — production: no-op (tree-shaken away)
import { EventClient } from '@tanstack/devtools-event-client'
```

If you are an open-source author who deliberately wants devtools events in
production, import the real client from the `/production` subpath instead. It is
never stripped:

```ts
// always the real client, in every environment
import { EventClient } from '@tanstack/devtools-event-client/production'
```

The public API is identical between the two imports — only the production
runtime behavior differs.
24 changes: 24 additions & 0 deletions docs/framework/angular/guides/custom-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,27 @@ Heres an example of both:

[tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state
```

## Production builds

By default the **root import** of `@tanstack/devtools-event-client` no-ops
outside development. When your bundler sets `process.env.NODE_ENV` to anything
other than `'development'`, the real client is replaced by a no-op and
tree-shaken out of your production bundle:

```ts
// dev: real client — production: no-op (tree-shaken away)
import { EventClient } from '@tanstack/devtools-event-client'
```

If you are an open-source author who deliberately wants devtools events in
production, import the real client from the `/production` subpath instead. It is
never stripped:

```ts
// always the real client, in every environment
import { EventClient } from '@tanstack/devtools-event-client/production'
```

The public API is identical between the two imports — only the production
runtime behavior differs.
24 changes: 24 additions & 0 deletions docs/framework/preact/guides/custom-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,27 @@ Heres an example of both:

🌴 [tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state
```

## Production builds

By default the **root import** of `@tanstack/devtools-event-client` no-ops
outside development. When your bundler sets `process.env.NODE_ENV` to anything
other than `'development'`, the real client is replaced by a no-op and
tree-shaken out of your production bundle:

```ts
// dev: real client — production: no-op (tree-shaken away)
import { EventClient } from '@tanstack/devtools-event-client'
```

If you are an open-source author who deliberately wants devtools events in
production, import the real client from the `/production` subpath instead. It is
never stripped:

```ts
// always the real client, in every environment
import { EventClient } from '@tanstack/devtools-event-client/production'
```

The public API is identical between the two imports — only the production
runtime behavior differs.
24 changes: 24 additions & 0 deletions docs/framework/react/guides/custom-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,27 @@ Heres an example of both:
🌴 [tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state
```

## Production builds

By default the **root import** of `@tanstack/devtools-event-client` no-ops
outside development. When your bundler sets `process.env.NODE_ENV` to anything
other than `'development'`, the real client is replaced by a no-op and
tree-shaken out of your production bundle:

```ts
// dev: real client — production: no-op (tree-shaken away)
import { EventClient } from '@tanstack/devtools-event-client'
```

If you are an open-source author who deliberately wants devtools events in
production, import the real client from the `/production` subpath instead. It is
never stripped:

```ts
// always the real client, in every environment
import { EventClient } from '@tanstack/devtools-event-client/production'
```

The public API is identical between the two imports — only the production
runtime behavior differs.
24 changes: 24 additions & 0 deletions docs/framework/solid/guides/custom-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,27 @@ Heres an example of both:

[tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state
```

## Production builds

By default the **root import** of `@tanstack/devtools-event-client` no-ops
outside development. When your bundler sets `process.env.NODE_ENV` to anything
other than `'development'`, the real client is replaced by a no-op and
tree-shaken out of your production bundle:

```ts
// dev: real client — production: no-op (tree-shaken away)
import { EventClient } from '@tanstack/devtools-event-client'
```

If you are an open-source author who deliberately wants devtools events in
production, import the real client from the `/production` subpath instead. It is
never stripped:

```ts
// always the real client, in every environment
import { EventClient } from '@tanstack/devtools-event-client/production'
```

The public API is identical between the two imports — only the production
runtime behavior differs.
24 changes: 24 additions & 0 deletions docs/framework/vue/guides/custom-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,27 @@ Heres an example of both:

[tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state
```

## Production builds

By default the **root import** of `@tanstack/devtools-event-client` no-ops
outside development. When your bundler sets `process.env.NODE_ENV` to anything
other than `'development'`, the real client is replaced by a no-op and
tree-shaken out of your production bundle:

```ts
// dev: real client — production: no-op (tree-shaken away)
import { EventClient } from '@tanstack/devtools-event-client'
```

If you are an open-source author who deliberately wants devtools events in
production, import the real client from the `/production` subpath instead. It is
never stripped:

```ts
// always the real client, in every environment
import { EventClient } from '@tanstack/devtools-event-client/production'
```

The public API is identical between the two imports — only the production
runtime behavior differs.
26 changes: 26 additions & 0 deletions docs/production.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,32 @@ function App() {
}
```

## Event client in production

By default the **root import** of `@tanstack/devtools-event-client` no-ops
outside development. When your bundler sets `process.env.NODE_ENV` to anything
other than `'development'`, the real client is replaced by a no-op and
tree-shaken out of your production bundle:

```ts
// dev: real client — production: no-op (tree-shaken away)
import { EventClient } from '@tanstack/devtools-event-client'
```

If you are an open-source author who deliberately wants devtools events in
production, import the real client from the `/production` subpath instead. It is
never stripped:

```ts
// always the real client, in every environment
import { EventClient } from '@tanstack/devtools-event-client/production'
```

The public API is identical between the two imports — only the production
runtime behavior differs.

This is independent of the Vite plugin's `removeDevtoolsOnBuild` option — the event client strips itself based on `NODE_ENV`, whether or not you use the Vite plugin.

## Where to install the Devtools

If you are using the devtools in development only, you can install them as a development dependency and only import them in development builds. This is the default recommended way to use the devtools.
Expand Down
26 changes: 26 additions & 0 deletions packages/event-bus-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,29 @@ plugin.on('b', (e) => {
The `EventClient` class is a base class for creating plugins that can subscribe to events in the TanStack Devtools event bus. It allows you to define a set of events and their corresponding data schemas using a standard-schema based schemas.

It will work on both the client/server side and all you have to worry about is emitting/listening to events.

## Production builds

By default the **root import** of `@tanstack/devtools-event-client` no-ops
outside development. When your bundler sets `process.env.NODE_ENV` to anything
other than `'development'`, the real client is replaced by a no-op and
tree-shaken out of your production bundle:

```ts
// dev: real client — production: no-op (tree-shaken away)
import { EventClient } from '@tanstack/devtools-event-client'
```

If you are an open-source author who deliberately wants devtools events in
production, import the real client from the `/production` subpath instead. It is
never stripped:

```ts
// always the real client, in every environment
import { EventClient } from '@tanstack/devtools-event-client/production'
```

The public API is identical between the two imports — only the production
runtime behavior differs.

"Outside development" includes when `NODE_ENV` is unset — common in plain Node scripts, some SSR dev servers, and test runners — so the root import resolves to the no-op there too. Set `NODE_ENV=development`, or use the `/production` subpath, to get the real client in those contexts.
10 changes: 10 additions & 0 deletions packages/event-bus-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@
"default": "./dist/cjs/index.cjs"
}
},
"./production": {
"import": {
"types": "./dist/esm/production.d.ts",
"default": "./dist/esm/production.js"
},
"require": {
"types": "./dist/cjs/production.d.cts",
"default": "./dist/cjs/production.cjs"
}
},
"./package.json": "./package.json"
},
"bin": {
Expand Down
31 changes: 16 additions & 15 deletions packages/event-bus-client/skills/devtools-event-client/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Install the package:
npm i @tanstack/devtools-event-client
```

The package exports a single class:
The package has two entry points — the root export (the real client in development, a no-op tree-shaken out of production), and the `/production` subpath (always the real client, for libraries that want devtools events in production):

```ts
import { EventClient } from '@tanstack/devtools-event-client'
Expand Down Expand Up @@ -257,30 +257,31 @@ storeInspector.emit('state-changed', {
})
```

### 7. Not stripping EventClient emit calls for production (HIGH)
### 7. Forgetting the root export no-ops in production (HIGH)

The Vite plugin strips adapter imports (e.g., `@tanstack/react-devtools`) from production builds, but it does NOT strip `@tanstack/devtools-event-client` imports or `emit()` calls. Library authors must guard emit calls themselves.
The **root import** of `@tanstack/devtools-event-client` resolves to a no-op
when `process.env.NODE_ENV !== 'development'`, and the real client is
tree-shaken out of production bundles. This is the default and what you want for
most libraries — your `emit()` calls cost nothing in production.

Options:

**Option A:** Use the `enabled` constructor option:
"Outside development" includes when `NODE_ENV` is unset — common in plain Node scripts, some SSR dev servers, and test runners — so the root import resolves to the no-op there too. Set `NODE_ENV=development`, or use the `/production` subpath, to get the real client in those contexts.

```ts
super({
pluginId: 'store-inspector',
enabled: process.env.NODE_ENV !== 'production',
})
// dev: real client — production: no-op, removed from the bundle
import { EventClient } from '@tanstack/devtools-event-client'
```

**Option B:** Conditional guard at the call site:
If you are publishing an open-source library and deliberately want devtools
events to keep working in production, import from the `/production` subpath,
which always ships the real client:

```ts
if (process.env.NODE_ENV !== 'production') {
storeInspector.emit('state-changed', data)
}
import { EventClient } from '@tanstack/devtools-event-client/production'
```

When `enabled` is `false`, `emit()` returns immediately (no event creation, no queuing, no connection attempt). This is the preferred approach.
The `enabled` constructor option still works for fine-grained runtime control,
but you no longer need to guard `emit()` calls manually for bundle size — the
root export handles that for you.

## See Also

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ if (process.env.NODE_ENV !== 'production') {
}
```

**Important:** The Vite plugin strips `@tanstack/react-devtools` from production but does NOT strip `@tanstack/devtools-event-client`. You must guard yourself.
**Important:** The Vite plugin strips `@tanstack/react-devtools` from production. The root import of `@tanstack/devtools-event-client` also no-ops and is tree-shaken out when `process.env.NODE_ENV !== 'development'`, so `emit()` calls cost nothing in production by default. Import from `@tanstack/devtools-event-client/production` if you deliberately want events in production. The `enabled` option remains available for runtime control.

### 6. Server/Client Transparent Bridging

Expand Down
20 changes: 19 additions & 1 deletion packages/event-bus-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
export { EventClient } from './plugin'
import { EventClient as EventClientImpl } from './plugin'
import { EventClientNoOp } from './noop'

/**
* The real `EventClient` in development; a no-op everywhere else.
*
* Production bundlers replace `process.env.NODE_ENV` with a literal, fold this
* ternary to `EventClientNoOp`, and tree-shake `./plugin` out of the bundle.
* To keep the real client in production, import it from
* `@tanstack/devtools-event-client/production` instead.
*/
const EventClient = (process.env.NODE_ENV !== 'development'
? EventClientNoOp
: EventClientImpl) as unknown as typeof EventClientImpl

type EventClient<TEventMap extends Record<string, any>> =
EventClientImpl<TEventMap>

export { EventClient }
Loading
Loading