Morphos & Kosmesis are here.Explore
PraxisJS

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/devtools
pnpm add @praxisjs/devtools
yarn add @praxisjs/devtools
bun add @praxisjs/devtools
import { 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:

OptionTypeDescription
labelstringName shown in the panel. Defaults to the field or method name.
logboolean | "trace"Also logs every change or call to the console. "trace" logs with a stack trace, which shows what caused the change.
breakboolean | (next, prev) => booleanPauses 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.

On this page