npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jacare/core

v0.1.17

Published

Jacaré runtime — fine-grained reactivity engine

Readme

@jacare/core

npm downloads license CI node demo

The runtime for Jacaré — a fine-grained reactive UI framework with no Virtual DOM.

Jacaré compiles .jcr modules into direct DOM updates. When a value changes, only the nodes that depend on it update. This package provides the reactivity engine, DOM bindings, navigation, forms, SSR helpers, and lifecycle hooks.


Table of contents


Install

npm install @jacare/core

For a full app with CLI, compiler, and Vite plugin:

npm install -g @jacare/cli
npm install @jacare/core
npm install -D @jacare/vite-plugin

npm: https://www.npmjs.com/package/@jacare/core


Quick start

Create a .jcr file — a plain JavaScript module with a view tagged template:

import { pulse, derive, view } from '@jacare/core'

const count = pulse(0)
const label = derive(() => `Count: ${count()}`)

export default view`
  <section class="counter">
    <p>${label}</p>
    <button on-click=${() => count.update((n) => n + 1)}>
      Increment
    </button>
  </section>
`

Mount it from boot.js:

import App from './app.jcr'

const root = document.getElementById('app')
const dispose = App(root)

// later: dispose() to clean up

Each .jcr module compiles to three functions:

| Function | Purpose | |----------|---------| | mount(target, props?) | Client-side DOM mount | | render(props?) | SSR HTML string | | resume(target, state, props?) | Hydrate SSR output |


Core concepts

Pulses (signals)

State is stored in pulses. Reading a pulse inside a template or effect subscribes to it. Writing triggers only the dependents.

const count = pulse(0)

count()              // read → 0
count.set(5)         // write
count.update(n => n + 1)  // functional update

Derived values

Computed values re-run only when their dependencies change:

const first = pulse(2)
const second = pulse(3)
const total = derive(() => first() + second())

Effects

Run side effects when tracked pulses change:

import { watch } from '@jacare/core'

watch(() => {
  console.log('count is', count())
})

No Virtual DOM

Jacaré does not diff a virtual tree. The compiler emits precise DOM operations — text updates, attribute bindings, keyed list reconciliation — wired directly to the pulse graph.


Reactivity API

Canonical names

| API | Alias | Description | |-----|-------|-------------| | signal(initial) | pulse(initial) | Reactive value | | computed(fn) | derive(fn) | Derived value — dispose() for dynamic lifetimes | | effect(fn) | watch(fn) | Side effect | | untrack(fn) | — | Run without tracking | | batch(fn) | — | Batch multiple writes | | flushSync() | — | Drain pending updates now | | enablePatience() / disablePatience() | — | Opt-in microtask coalesce + internal lanes (default stays sync) | | runAsLane(lane, fn) | — | Runtime/tooling write origin (input / default / idle) | | runUntracked(fn) | — | Untracked execution (used by DOM bindings) |

Untracked reads

| API | Use when | |-----|----------| | signal.peek / computed.peek | Read current value without subscribing | | untrack(fn) | Temporarily disable tracking inside an effect | | runUntracked(fn) | Same as untrack — used internally by bindText, bindModel, etc. |

Pulse graph internals

Each signal and computed owns a DependencyCell:

  • Subscribers stored in an array for fast notify()
  • A Set provides O(1) duplicate checks during dependency tracking
  • Unsubscribe uses swap-remove — O(1) amortized

Effects and computeds use an OwnerNode tree. Disposing an owner clears dependencies and runs cleanups automatically.

import { pulse, derive, view } from '@jacare/core'

const items = pulse([
  { id: '1', label: 'Learn Jacaré', done: false },
])

const filter = pulse('')
const filtered = derive(() => {
  const q = filter().trim().toLowerCase()
  if (!q) return items()
  return items().filter((item) => item.label.toLowerCase().includes(q))
})

function addItem(label) {
  items.update((list) => [
    ...list,
    { id: String(Date.now()), label, done: false },
  ])
}

Template bindings

Use these inside view\...``:

Text

view`<p>${greeting}</p>`

Reactive pulses are read automatically. For mixed text:

view`<p>Total: ${total()}</p>`

Events

view`<button on-click=${save}>Save</button>`
view`<input on-input=${(e) => filter.set(e.target.value)} />`

Alias: @click=${fn}

Attributes

view`<a bind-href=${url}>Link</a>`

Alias: :href=${url}

Two-way form bindings

view`
  <input bind-value=${text} />
  <input type="checkbox" bind-checked=${done} />
`

bind-value and bind-checked compile to bindModel — DOM and signal stay in sync.

Conditional classes

view`<li class-done=${() => item.done}>...</li>`

Alias: class:done=${() => item.done}

Conditionals

view`
  #if show()
    <p>Visible</p>
  #elif loading()
    <p>Loading…</p>
  #else
    <p>Hidden</p>
  #end
`

Alias: @if / @elseif / @else / @end

Multiple siblings inside a branch mount in source order (branch / showIf use an insertion cursor after the comment anchor).

Lists

view`
  #for items() as item (item.id)
    <li>${item.label}</li>
  #end
`
  • item.id is the key for efficient DOM reconciliation
  • Alias: @each items() as item (item.id) / @end

Components

Import another .jcr module and use it as a self-closing tag:

import Field from './Field.jcr'

export default view`
  <Field :label=${'Email'} :field=${form.fields.email} :type=${'email'} />
`

Props are passed with :propName=${value}. Inside the child component, props appear as variables in scope.


Navigation

Client-side routing with lazy-loaded screens:

import { createNav, lazy, screen } from '@jacare/core'
import Shell from './shell.jcr'
import Home from './pages/home.jcr'
import NotFound from './pages/not-found.jcr'

export const nav = createNav({
  base: '/',
  layout: Shell,
  screens: {
    '/': screen(Home),
    '/about': lazy(() => import('./pages/about.jcr')),
    '/users/:id': lazy(() => import('./pages/user.jcr')),
  },
  missing: NotFound,
})

// in boot.js
nav.attach(document.getElementById('app'))

Layout shell

export default view`
  <header>...</header>
  <main jacare-frame></main>
  <footer>...</footer>
`

Active screen content mounts inside jacare-frame.

Declarative links

<a jacare-go="/about" href="/about">About</a>

Jacaré intercepts clicks and syncs the jacare-here class on the active link.

Programmatic navigation

await nav.go('/about')
await nav.go('/settings', { focus: true }) // focus [data-jacare-focus] after mount
await nav.go('/settings', { focus: '#main-title' })
await nav.swap('/settings')  // replace history entry
nav.undo()                   // history.back()
await nav.warm('/about')     // prefetch lazy screen

nav.where is a Signal<NavPlace>:

nav.where()       // reactive — re-runs dependents on navigation
nav.where.peek    // untracked — current place without subscribing

Route helpers

import { createRoute, getRouteParam, getRouteSearch, routeParam, routeSearch, routeHref } from '@jacare/core'

// Preferred in screens — reactive helpers from nav.where
export const route = createRoute(nav.where)
route.param('id')()
route.search('q')()

// Template sugar ${@route/id} / ${@search/q} uses the same active nav:
const id = getRouteParam('id')
const q = getRouteSearch('q')
id()
q()

// One-shot from a NavContext (title / mount), not a subscription:
title: (ctx) => `Item · ${routeParam(ctx, 'id') ?? '…'}`

const link = routeHref('/item/:id', { id: '7' }, { tab: 'specs' })
// '/item/7?tab=specs'
routeHref('/a/:idea/:id', { idea: 'x', id: '1' }) // '/a/x/1' — token replace, no prefix collision

In templates:

<p>${@route/id}</p>
<p>${@search/q}</p>
<h1 data-jacare-focus tabindex="-1">Settings</h1>

Forms

import { createForm } from '@jacare/core'

const form = createForm({
  email: {
    value: '',
    validate: (value) => (value.includes('@') ? 'Invalid email' : null),
  },
  message: {
    value: '',
    validate: (value) => (!value.trim() ? 'Required' : null),
  },
})

export <view>
  <input bind-value=${form.fields.email} on-blur=${() => form.fields.email.blur()} />
  #if form.fields.email.error()
    <span>${form.fields.email.error()}</span>
  #end
</view>

Each form.fields.* is a field signal with error(), touched(), dirty(), and blur().


Lifecycle hooks

Per-screen lifecycle for mount, activate, deactivate, and unmount:

import { createLifecycle } from '@jacare/core'

export const lifecycle = createLifecycle({
  onMount() {
    console.log('screen mounted')
  },
  onActivate(ctx) {
    console.log('screen visible', ctx.path)
    return () => {}
  },
  onDeactivate() {
    console.log('screen hidden')
  },
  onUnmount() {
    console.log('screen destroyed')
  },
})

Export lifecycle from lazy-loaded .jcr screens. Nav wraps it automatically via screen().


Scope debugging

Register values for live inspection (used by @jacare/devtools):

import { registerScope, pulse } from '@jacare/core'

const draft = pulse('')

registerScope('draft', () => draft())

Server-side rendering

import { renderToString, renderToStream } from '@jacare/core'
import { render, resume } from './app.jcr'

// server
const html = renderToString(() => render())

// client hydration
const dispose = resume(root, state)

SSR output includes data-jacare-bind markers for precise hydration targets.


Island mount kit

Embed a .jcr widget into a host page without a Jacaré SPA shell. Use the thin subpath:

import { mountIsland } from '@jacare/core/island'
import Widget from './Widget.jcr'

const dispose = mountIsland('#slot', Widget, {
  props: { start: 2 },
  shadow: true,
})

| Option | Role | |--------|------| | props | Passed to compiled mount | | shadow | Mount inside a shadow root (CSS isolation) | | clear | Clear host children before mount (default true) | | mark | Host attribute after mount (default data-jacare-island) |

Docs: island.md · demo: examples/jacare-island


API reference

Reactivity

signal, pulse, computed, derive, effect, watch, untrack, batch, flushSync, enablePatience, disablePatience, isPatienceEnabled, runAsLane, runUntracked, isTracking

DOM

view, bindText, bindPropText, bindAttribute, bindProperty, bindClass, bindModel, safeUrl, branch, reconcileKeyedList, showIf, ensureScopedStyle, mountSlot

Nav

createNav, lazy, screen, adaptScreen, createRoute, getRouteParam, getRouteSearch, routeHref, routeParam, routeSearch, screenProps · type NavGoOptions

Forms

createForm

Lifecycle

createLifecycle

Scope

registerScope, clearScope, getScopeSnapshot, subscribeScope, startScopePulse

SSR

renderToString, renderToStream, resumeBindings, escapeHtml, safeUrl

Island

mountIsland — import from @jacare/core/island

DevTools hooks

enableDevtools, getPulseGraph, subscribePulseGraph


Links

License

MIT