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

@herb-tools/analysis

v0.11.0

Published

Project analysis for Herb: template indexes, render call sites and partial resolution

Readme

Herb Analysis

Package: @herb-tools/analysis


Project-wide analysis for HTML+ERB templates. Builds the indexes that answer questions a single template can't answer on its own, such as which partials a page renders, which pages reach a given partial, what locals a partial declares, and which nodes are affected when a piece of state changes.

Installation

:::code-group

npm add @herb-tools/analysis
pnpm add @herb-tools/analysis
yarn add @herb-tools/analysis
bun add @herb-tools/analysis

:::

Entry Points

The package has two entry points.

@herb-tools/analysis holds the environment-agnostic pieces. Types, resolution helpers, and the index classes themselves. It reads nothing from disk and works in the browser.

@herb-tools/analysis/node holds the builders. Indexing a project walks the filesystem, so ProjectIndex, buildPartialIndex, and buildRenderGraph live behind this entry point.

import { resolvePartial, affectedNodes } from "@herb-tools/analysis"
import { ProjectIndex } from "@herb-tools/analysis/node"

Project Index

ProjectIndex is the front door. It owns the partial index and the render graph, builds them in the required order, and keeps them in step as files change.

import { Herb } from "@herb-tools/node-wasm"
import { ProjectIndex } from "@herb-tools/analysis/node"

await Herb.load()

const index = new ProjectIndex({ root: process.cwd(), backend: Herb })

await index.indexAll()

index.partials
index.callers
index.viewRoot

Call sites resolve against the partial declarations, which is why indexAll() indexes partials before callers. Use indexPartials() and indexCallers() directly when you need the stages apart.

Render Graph

RenderGraph maps the render tree in both directions.

const graph = index.callers

graph.callersOf("app/views/users/_card.html.erb")
graph.rootsOf("app/views/users/show.html.erb")
graph.inferSignature("app/views/users/_card.html.erb")
graph.contextOf("app/views/users/_card.html.erb")

inferSignature() derives a partial's locals from every call site that renders it, which is how a partial with no strict locals still gets a signature.

Serialize with toJSON() and restore with renderGraphFrom() to cache the graph across processes.

Partial Index

PartialIndex records what each partial declares, including strict locals.

import { declarationFromSource } from "@herb-tools/analysis/node"

const declaration = declarationFromSource(Herb, file, source)

declaration.hasDeclaration
declaration.locals

Each entry in locals is a StrictLocal with a name, a required flag, and the source of its default when it has one.

Serialize and restore with partialIndexFrom(). After an autofix rewrites a file, refreshPartialAfterFix() updates the entry in place and reports whether the declaration actually changed.

Path Resolution

Helpers for mapping between files and Action View names, with no index required.

import {
  isTemplatePath,
  isPartialPath,
  partialNameForFile,
  templateNameForFile,
  layoutCandidatesFor,
  resolvePartial,
  projectRelativePath
} from "@herb-tools/analysis"

State Dependencies

affectedNodes() finds the nodes in a template that depend on a given piece of state. dependencyIndex() groups them by state name, and affectedTemplates() walks a graph to find every template reachable from an entry point that depends on it.

import { affectedNodes, dependencyIndex } from "@herb-tools/analysis"

affectedNodes(Herb, source, "@post")
dependencyIndex(Herb, file, source)

Each result carries an AffectedNodeKind of text_content, conditional, render, attribute_value, expression, or iteration.

See Also