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

@meridial/swc-plugin

v0.1.3

Published

SWC plugin that stamps React JSX elements with stable deterministic data-meridial-id attributes

Downloads

396

Readme

meridial-swc-plugin

Next js SWC plugin that stamps React JSX elements with stable, deterministic data-meridial-id attributes at build time for the Meridial in-app guide system.

How it works

The plugin traverses every JSX element in every source file and injects a data-meridial-id attribute whose value is the first 8 hex characters of a SHA-256 hash derived from:

sha256("{relativePath}:{componentName}:{astPath}:{elementType}")[0..8]

The same source code always produces the same IDs, so guide recordings survive rebuilds.

What gets stamped ?

Only React component calls are stamped by default — JSX elements whose names start with an uppercase letter (e.g. <Button>, <Card>, <Dialog>). Native HTML elements (<div>, <span>, <button>, etc.) are skipped unless explicitly opted-in via allowedElements.

This keeps the DOM lean. IDs still reach native elements through React's prop-spread mechanism:

// button.tsx — plugin stamps the <Button> call site in page.tsx
function Button({ className, ...props }) {
  return (
    // Native <button> is skipped by default.
    // data-meridial-id="b1" arrives via {...props} from the parent.
    <button className={className} {...props} />
  )
}

// page.tsx
export default function Page() {
  return (
    <div>
      <Button data-meridial-id="e138a7ec" className="primary">Save</Button>
      <Button data-meridial-id="765146cc" className="secondary">Cancel</Button>
    </div>
  )
}

DOM output:

<button data-meridial-id="e138a7ec" class="primary">Save</button>
<button data-meridial-id="765146cc" class="secondary">Cancel</button>

e138a7ec from page.tsx overrides whatever the Button definition would have stamped, because {...props} is spread after any static attributes on the root element. Both instances are uniquely identifiable.

Installation

npm install --save-dev @meridial/swc-plugin

Usage

Next.js

// next.config.js
module.exports = {
  experimental: {
    swcPlugins: [
      ["@meridial/swc-plugin", {
        rootPath: process.cwd()
      }]
    ]
  }
}

Configuration

All options are optional.

{
  "attribute": "data-meridial-id",
  "hashLength": 8,
  "rootPath": "/absolute/path/to/project",
  "ignoredComponents": ["HeadlessComponent", "AnalyticsWrapper"],
  "ignoredFiles": ["**/test/**", "**/*.test.tsx", "**/*.spec.tsx"],
  "allowedElements": ["button", "input", "a"]
}

| Option | Type | Default | Description | | ------------------- | ---------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | attribute | string | "data-meridial-id" | Data attribute name written to the DOM | | hashLength | number | 8 | Number of hex characters from the SHA-256 hash (max 64) | | rootPath | string | undefined | Project root used to compute relative file paths for hashing. Omitting it uses the absolute path, which breaks reproducibility across machines. | | ignoredComponents | string[] | [] | React component names whose return JSX is not stamped | | ignoredFiles | string[] | [] | Glob patterns for files to skip entirely (tests, Storybook, etc.) | | allowedElements | string[] | [] | Native HTML element names to stamp despite being in the default ignore list |

allowedElements — opting HTML elements back in

By default every standard HTML element is skipped to avoid bloating the DOM with attributes on every <div> and <span>. If you use native elements directly as guide targets (no component wrapper), add them here:

{
  "allowedElements": ["button", "input", "a", "select", "textarea"]
}

With this config, a bare <button> in a component return will receive a data-meridial-id just like a React component would.

The complete list of default-ignored HTML elements (those excluded from stamping by default) can be found in src/element_filter.rs. This list includes all standard HTML tags.

ID stability

Two things invalidate an existing ID:

  1. The file is moved or renamed (changes relativePath)
  2. The JSX tree structure changes so the element's astPath shifts

IDs are not invalidated by:

  • Renaming props or variables
  • Adding/removing sibling non-JSX content (comments, expressions)
  • Changing the element's children

Dynamic lists (.map)

JSX inside .map(), .flatMap(), .forEach(), or .reduce() callbacks is not stamped because a single source location would produce multiple DOM nodes with the same ID. The Meridial recorder handles this by walking up to the nearest stamped ancestor.

Component spread requirement

For the parent-overrides-child mechanism to work, components must spread rest props onto their root element — a standard React best practice already required for ref forwarding and aria/data attribute pass-through:

// Required pattern
function Card({ title, ...props }) {
  return <div {...props}>{title}</div>   // ✅ spread on root
}

// Does NOT participate in override chain
function Card({ title, onClick }) {
  return <div onClick={onClick}>{title}</div>  // ❌ no spread
}

Components that don't spread props keep their definition-site ID. The recorder falls back to the nearest stamped ancestor.