DEV Community

Yuuichi Eguchi
Yuuichi Eguchi

Posted on

Edit JSON, See Changes Instantly: HMR in Constela

What is Constela?

Constela is a compiler-first UI framework where you describe UI behavior as JSON instead of JavaScript. No JSX, no hooks, no JavaScript in your components.

{
  "version": "1.0",
  "state": {
    "count": { "type": "number", "initial": 0 }
  },
  "actions": [
    {
      "name": "increment",
      "steps": [{ "do": "update", "target": "count", "operation": "increment" }]
    }
  ],
  "view": {
    "kind": "element",
    "tag": "button",
    "props": { "onClick": { "event": "click", "action": "increment" } },
    "children": [{ "kind": "text", "value": { "expr": "state", "name": "count" } }]
  }
}
Enter fullscreen mode Exit fullscreen mode

The JSON is validated, analyzed, and compiled into minimal runtime code. We added Hot Module Replacement (HMR) to make the development experience even better.

The Problem: Full Reloads Are Painful

Before HMR, every time you saved a JSON file:

  • The entire page reloaded
  • Form inputs were cleared
  • Counter values reset to initial
  • Scroll position jumped to top

When you're tweaking styles or adjusting text, re-entering test data over and over is frustrating.

The Solution: Edit JSON, Keep Your State

With HMR, you edit JSON and save. That's it. The browser updates instantly while preserving your application state.

Before HMR After HMR
~500ms (full reload) ~50ms (hot update)
State lost State preserved
Scroll position lost Scroll preserved
Form inputs cleared Form inputs kept

How to Use It

No configuration required. Just start the dev server:

npx constela dev
Enter fullscreen mode Exit fullscreen mode

You'll see:

[Constela] Dev server started at http://localhost:3000
[Constela] HMR server started at ws://localhost:24678
Enter fullscreen mode Exit fullscreen mode

Now edit any JSON file and save. Changes appear instantly.

Real-World Examples

Example 1: Updating Button Text

You have a counter at 10. You want to change the button label.

Before:

{
  "children": [
    { "kind": "text", "value": { "expr": "lit", "value": "Count: " } },
    { "kind": "text", "value": { "expr": "state", "name": "count" } }
  ]
}
Enter fullscreen mode Exit fullscreen mode

After:

{
  "children": [
    { "kind": "text", "value": { "expr": "lit", "value": "Clicks: " } },
    { "kind": "text", "value": { "expr": "state", "name": "count" } }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Save the file. The button now shows "Clicks: 10". The count wasn't reset.

Example 2: Adding New State

You can add new state fields without losing existing values.

Before:

{
  "state": {
    "count": { "type": "number", "initial": 0 }
  }
}
Enter fullscreen mode Exit fullscreen mode

After:

{
  "state": {
    "count": { "type": "number", "initial": 0 },
    "message": { "type": "string", "initial": "Hello!" }
  }
}
Enter fullscreen mode Exit fullscreen mode

The count value is preserved. The new message field gets its initial value.

Example 3: Changing State Types

What if you change a field's type?

{
  "state": {
    "count": { "type": "string", "initial": "" }
  }
}
Enter fullscreen mode Exit fullscreen mode

Constela detects the type mismatch and shows a warning:

[HMR] Type mismatch for state 'count': number -> string. Using new initial value.
Enter fullscreen mode Exit fullscreen mode

The old number value can't be used as a string, so the new initial value is used instead. This prevents bugs from incompatible types.

Error Handling

If you make a mistake in your JSON, Constela shows a clear error overlay:

┌────────────────────────────────────────────┐
│            Compilation Error               │
│                                            │
│  /src/routes/index.json                    │
│                                            │
│  ┌────────────────────────────────────┐   │
│  │ UNDEFINED_STATE                     │   │
│  │ Undefined state reference: 'cont'   │   │
│  │ at view.children[0].value.name      │   │
│  │                                     │   │
│  │ Did you mean 'count'?               │   │
│  └────────────────────────────────────┘   │
│                                            │
└────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The overlay shows:

  • File path: Which file has the error
  • Error code: UNDEFINED_STATE, SCHEMA_INVALID, etc.
  • Error message: What went wrong
  • JSON path: Where in the JSON structure
  • Suggestion: "Did you mean 'count'?" when applicable

Fix the error and save. The overlay disappears and your app is back.

How It Works (Brief Overview)

Under the hood, HMR uses a WebSocket connection between the dev server and browser:

  1. File watcher detects changes to JSON files
  2. Server recompiles the JSON to a program
  3. WebSocket sends the new program to the browser
  4. Browser saves current state, swaps in new program, restores state

The key insight: since Constela programs are pure JSON with explicit state declarations, we can safely serialize and restore state across updates.

What Triggers HMR

HMR works for:

  • Route JSON files (src/routes/**/*.json)
  • Component JSON files
  • Layout JSON files (src/layouts/**/*.json)

These require a full reload:

  • TypeScript files (API routes, middleware)
  • Configuration files
  • Adding new route files

Summary

Constela brings HMR that just works:

  • Zero config: npx constela dev and you're done
  • State preserved: Form inputs, counters, scroll positions survive updates
  • Instant feedback: Changes appear in ~50ms
  • Clear errors: Overlay shows exactly what's wrong and how to fix it
  • Auto-reconnect: Connection loss is handled gracefully

The philosophy of Constela is "build websites with JSON, not JavaScript". HMR makes that workflow even smoother.

Link

Top comments (0)