DevTools Decorators
Developer experience decorators — @Debug to expose signals, computed values, and methods in DevTools, and @Trace to instrument component performance.
DevTools Decorators
Decorators that feed data into the DevTools panel. Both are no-ops unless DevTools.init() has been called — so they're safe to leave in production code with zero runtime cost.
Install:
npm install @praxisjs/devtoolspnpm add @praxisjs/devtoolsyarn add @praxisjs/devtoolsbun add @praxisjs/devtoolsimport { Debug, Trace } from '@praxisjs/devtools'@Debug(options?)
Exposes a field, getter, or method in the DevTools panel. What it tracks depends on what it decorates:
On @State(), @Prop() and other reactive fields
Records every value change in the Signals panel with a 20-entry history. The field is observed reactively, so a change is reported whatever caused it: an assignment, a parent passing a new @Prop value, a @Persisted value arriving from another tab.
import { Component, State } from '@praxisjs/decorators'
import { Debug } from '@praxisjs/devtools'
import { StatefulComponent } from '@praxisjs/core'
@Component()
class CartStore extends StatefulComponent {
@Debug({ label: 'Cart Items' })
@State()
items: Product[] = []
@Debug()
@State()
total = 0
}Decorator order
@Debug must be written above @State() (or @Prop()). Decorators apply bottom-up, so @State runs first to set up the reactive accessor, then @Debug observes it.
On getters
Tracks a computed value by creating an internal computed() that observes the getter. Changes appear in the Signals panel.
@Debug({ label: 'Has Discount?' })
get hasDiscount() {
return this.total > 100
}On methods
Records every method call in the Timeline with arguments, return value, and execution duration in milliseconds. For a method that returns a promise, the entry is recorded when the promise settles, with the resolved value (or reject …) and the full duration.
@Debug()
async fetchProducts() {
this.items = await api.getProducts()
}Options:
| Option | Type | Description |
|---|---|---|
label | string | Name shown in the panel. Defaults to the field or method name. |
log | boolean | "trace" | Also logs every change or call to the console. "trace" logs with a stack trace, which shows what caused the change. |
break | boolean | (next, prev) => boolean | Pauses in the debugger on every change or call, or only when the function returns true (for methods it receives the arguments). |
// Find out who keeps resetting the cart: log with a stack trace, and stop when it empties.
@Debug({ log: 'trace', break: (next) => (next as Product[]).length === 0 })
@State()
items: Product[] = []@Trace(options?)
Instruments a component class to report lifecycle events and render metrics to the Components panel. @Trace({ slow: 4 }) also warns in the console whenever a render of that component takes more than 4 ms.
@Trace()
@Component()
class DataGrid extends StatefulComponent {
@State() rows: Row[] = []
render() {
return <table>{/* ... */}</table>
}
}Automatically tracks: onBeforeMount, onMount, onBeforeUpdate, onUpdate, onAfterUpdate, onUnmount.
The Components panel shows:
- Render count and last render duration in ms
- Mount and unmount timestamps
- Full lifecycle hook history
Decorator order
@Trace() must be written above @Component() since decorators apply bottom-up.
Production safety
In a production build (process.env.NODE_ENV === "production", which Vite and other bundlers set) @Debug and @Trace return the member or class exactly as declared: no wrapper, no observation, no cost. Load the panel itself with a dynamic import so it is never bundled into production:
// src/main.ts
import { render } from '@praxisjs/runtime'
import { App } from './app'
render(() => <App />, document.getElementById('app')!)
if (import.meta.env.DEV) {
const { DevTools } = await import('@praxisjs/devtools')
DevTools.init()
}→ See DevTools for the full setup guide and panel reference.