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

@jdlanglois/oeil

v0.2.0

Published

A tiny frontend framework with interchangeable view syntaxes

Readme

@jdlanglois/oeil

npm install @jdlanglois/oeil

A tiny frontend framework with plain mutable state and interchangeable view syntaxes. Hyperscript, Hiccup, and cached HTML templates all produce the same fixed-shape canonical VNodes and use one renderer.

import { mount, redraw, h } from "@jdlanglois/oeil/h"

const state = { count: 0 }

function Counter() {
  let local = 0
  return () => h("main", null,
    h("p", null, `Global: ${state.count}`),
    h("p", null, `Local: ${local}`),
    h("button", { onclick: () => state.count++ }, "Global +"),
    h("button", { onclick: () => local++ }, "Local +"),
  )
}

mount(document.querySelector("#app")!, Counter)

DOM events schedule a coalesced redraw. Call redraw() after mutations from promises, timers, network callbacks, or other external sources.

View syntaxes

Hyperscript

Hyperscript constructs canonical VNodes directly and is the recommended performance-oriented API.

import { mount, redraw, route, list, h } from "@jdlanglois/oeil/h"

h("div", { class: "greeting" }, h("strong", "Hello"), " world")

Hyperscript and Hiccup both support Emmet-style element selectors. The tag defaults to div; selector classes are prepended to explicit classes, while an explicit id overrides the selector id.

h("h1.red.large#heading", "Hello")
h(".card#primary", { class: "wide" }, "Content")

Hiccup

import { mount, redraw, route, list, hiccup } from "@jdlanglois/oeil/hiccup"

hiccup(["div", { class: "greeting" }, ["strong", "Hello"], " world"])
hiccup(["h1.red#heading", "Hello"])
hiccup([".card#primary", { className: "wide" }, "Content"])

Raw Hiccup returned from a component remains supported for compatibility.

HTML templates

Template strings are parsed once per call site and instantiate canonical VNodes on each redraw.

import { mount, redraw, route, list, html } from "@jdlanglois/oeil/html"

html`<button class="primary" onclick=${increment}>Count: ${count}</button>`

Each syntax-specific entry point reexports mount, redraw, route, and list, so an application can use one import while excluding the other syntax frontends. The root @jdlanglois/oeil entry exports all strategies and relies on standard ESM tree shaking.

The syntaxes can still be mixed because they share the Renderable and canonical VNode representations. Local state uses closure components. Add key to list children when identity must follow data through reorders.

Fragments

Components can return arrays of VNodes; oeil normalizes them as implicit fragments:

const Layout = () => [
  h("aside", "Navigation"),
  h("main", "Content"),
]

An array beginning with a string, component, or Fragment remains Hiccup syntax. Use an explicit fragment when a key or deliberate range identity is needed.

HTML templates support multiple root nodes directly:

const Layout = () => html`
  <aside>Navigation</aside>
  <main>Content</main>
`

Hyperscript and Hiccup can use the shared Fragment token, while fragment() works with every frontend:

import { Fragment, fragment, h } from "@jdlanglois/oeil/h"
import { hiccup } from "@jdlanglois/oeil/hiccup"

h(Fragment, null,
  h("aside", "Navigation"),
  h("main", "Content"),
)

hiccup([Fragment,
  ["aside", "Navigation"],
  ["main", "Content"],
])

fragment(h("aside", "Navigation"), h("main", "Content"))

Fragments retain a DOM range, so keyed fragment components move all of their nodes together and preserve DOM/component identity. Empty fragments and transitions between empty and populated fragments are supported.

Specialized keyed lists

A specialized keyed list can skip item view construction when an explicit revision remains unchanged:

import { h, list } from "@jdlanglois/oeil/h"

h("ul", null, list(rows, {
  key: row => row.id,
  version: row => row.version,
  view: row => h("li", null, row.label),
}))

The revision must include every value used by view, including external props. Omitting version preserves mutable-state correctness by reevaluating every item.

Router

import { route } from "@jdlanglois/oeil"

route(root, "/", {
  "/": Home,
  "/companies/:id": Company,
})
route.set("/companies/42")
route.params()
route.query()

Browser benchmark

Chromium 151 on Linux ARM64, 1,000 keyed rows. Values are median milliseconds; the benchmark also prints p95 latency and validates initial rendering and keyed reorder correctness.

| Framework/frontend | Initial | Unchanged | Update one | Append/remove | Reorder | Event update | |---|---:|---:|---:|---:|---:|---:| | oeil specialized list | 8.40 | 0.13 | 0.98 | 1.21 | 9.80 | 0.55 | | oeil hyperscript baseline | 9.60 | 1.24 | 1.51 | 1.69 | 9.97 | 1.76 | | oeil Hiccup | 9.40 | 0.93 | 0.89 | 1.56 | 11.03 | 1.38 | | oeil HTML | 9.60 | 1.71 | 1.08 | 1.73 | 10.27 | 2.04 | | Mithril | 9.40 | 0.62 | 0.78 | 1.72 | 13.37 | 1.62 | | Preact | 9.10 | 1.49 | 1.16 | 2.01 | 12.03 | 1.21 | | React | 10.40 | 1.07 | 1.74 | 1.75 | 12.27 | 2.09 |

Findings

  • The specialized list is a clear win for unchanged data: 0.13 ms versus 1.24 ms for ordinary hyperscript, because item views and generic keyed reconciliation are skipped.
  • It produced the fastest initial, append/remove, reorder, and event-update medians in this run.
  • Its one-row update remained slower than Mithril and Hiccup, so an explicit revision check is not a universal win for every mutation pattern.
  • Oeil's keyed reconciliation remains strong, but sub-millisecond differences vary substantially with JIT, GC, and browser scheduling.
  • Keeping only specialized lists increased core size from 2,676 to 3,087 bytes gzip and the full framework from 3,678 to 4,115 bytes gzip. The discarded local-invalidation and text-specialization experiments did not justify their additional complexity and size.

These are microbenchmarks, not universal application rankings. Run them on the target hardware before making framework decisions.

npm run benchmark:install # once
npm run benchmark

Trying oeil in Flems

Run npm run build, then copy the complete contents of exactly one browser bundle into the top of the Flems JavaScript panel:

| View syntax | Copy-paste bundle | Global | |---|---|---| | Hyperscript | dist/oeil-hyperscript.min.js | oeil | | Hiccup | dist/oeil-hiccup.min.js | oeil | | HTML template | dist/oeil-html.min.js | oeil |

Use this HTML panel:

<div id="app"></div>

After the pasted bundle, add one of these examples.

Hyperscript bundle

const { mount, h } = oeil
let count = 0

function App() {
  return h("main.card", 
    h("h1#title", `Count: ${count}`),
    h("button.primary", { onclick: () => count++ }, "Increment"),
  )
}

mount(document.querySelector("#app"), App)

Hiccup bundle

const { mount } = oeil
let count = 0

function App() {
  return ["main.card",
    ["h1#title", `Count: ${count}`],
    ["button.primary", { onclick: () => count++ }, "Increment"],
  ]
}

mount(document.querySelector("#app"), App)

HTML-template bundle

const { mount, html } = oeil
let count = 0

function App() {
  return html`<main class="card">
    <h1 id="title">Count: ${count}</h1>
    <button class="primary" onclick=${() => count++}>Increment</button>
  </main>`
}

mount(document.querySelector("#app"), App)

All three standalone bundles include mount, redraw, route, and list. Only the selected view-construction frontend is added.

Development

npm test
npm run build
npm run benchmark:jsdom

The build reports raw, gzip, and Brotli sizes and enforces 6 KB gzip for core and 8 KB gzip with the router. The renderer is informed by techniques from Mithril; see THIRD_PARTY_NOTICES.md.