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

@relatude/site

v1.0.0-alpha.2

Published

Marks a site's pages for the Relatude CMS interface's preview, so Locate finds every field exactly: any framework, no dependencies.

Readme

@relatude/site

Marks a site's pages for the Relatude CMS interface's preview. Locate in the preview outlines a field where the page shows it, and a click opens that field in the editor. It finds most fields by their values; a page that marks them is found exactly, whatever it shows: a date formatted, a price with its unit, a picture as a background, a heading the same as the menu's link. A page that moves to another without loading, as client-routed apps do, also says which page it shows now.

npm install @relatude/site

It has no dependencies and works with any way of rendering, as the marks are attributes on elements, which the CMS's script in the page reads and reads again as the page changes:

| Attribute | On | Says | |---|---|---| | data-relatude-field="{node}/{Property}" | the element showing a field | a node's property, by the node's Guid and the property's code name | | data-relatude-field="{node}/{Embedded}/{inner}/{Property}" | the same | an inner node's property, by its holder, the embedded property and the inner node's Guid | | data-relatude-page="{node}" | any one element, the app's root say | the node the page shows now; "" for a page of no node | | data-relatude-culture="{code}" | beside data-relatude-page | the culture it is shown in |

The functions write them only on pages the preview shows (the CMS adds its script to those and to no other), so a visitor's page is as it was.

React, Preact, Solid

import { fieldAttributes, pageAttributes } from '@relatude/site';

export function Article({ article }: { article: Article }) {
  return (
    <main {...pageAttributes(article.id, article.culture)}>
      <h1 {...fieldAttributes(article.id, 'Name')}>{article.name}</h1>
      <time {...fieldAttributes(article.id, 'Published')}>{format(article.published)}</time>
      <div {...fieldAttributes(article.id, 'Body')} dangerouslySetInnerHTML={{ __html: article.body }} />
      {article.specifications.map(s => (
        <dd key={s.id} {...fieldAttributes(article.id, 'Value', { embedded: 'Specifications', id: s.id })}>{s.value}</dd>
      ))}
    </main>
  );
}

Vue

<h1 v-bind="fieldAttributes(article.id, 'Name')">{{ article.name }}</h1>

Svelte

<h1 {...fieldAttributes(article.id, 'Name')}>{article.name}</h1>

Angular, Lit and templates that bind one attribute

fieldMark is the attribute's value, or null where fields are not marked, which leaves the attribute out:

<h1 [attr.data-relatude-field]="fieldMark(article.id, 'Name')">{{ article.name }}</h1>

Code that works on elements

import { setFieldMark, setPageMark } from '@relatude/site';

setPageMark(document.querySelector('main'), article.id);
setFieldMark(heading, article.id, 'Name');

What it needs

  • Node ids. A node is its Guid, or an object holding it as id, $id (the CMS API's) or Id. Whatever the site's pages are made from must carry the ids of the nodes it shows.
  • The page's node, when the page changes without loading. The CMS tells the script which node a page is when it sends the page, which holds only for that address. Name the node the app shows now on one element with pageAttributes, or leave it to the preview, which takes the address apart with the site's URL manager.
  • The script tag in the page. inPreview() looks for the CMS's script, script[data-relatude-finder], which the CMS puts at the end of the body: it knows once the document is parsed, as a module script or a bundler's entry runs. The CMS adds it to pages it serves through UseRelatudeCMS(), so an app's index.html must be served after it (app.UseRelatudeCMS() before app.UseStaticFiles(), or the app's own route).
  • Hydration. A page rendered on a server and hydrated in the browser must have the same markup in both: call markAlways() before rendering, on the server and in the browser, and every page carries the marks.