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

babel-plugin-analytics-hash

v0.1.0

Published

Auto-generate stable, structural analytics IDs for JSX elements at build time

Readme

babel-plugin-analytics-hash

Auto-generate stable, unique data-analytics-id attributes on every JSX element at build time. Configure additional tracking attributes via a JSON file — no developer involvement needed.

The problem

Every time your analytics team needs a data-track attribute on a UI element, it becomes a developer ticket → PR → review → deploy cycle. That takes days to weeks for what's essentially a config change.

Existing solutions either require manual developer discipline (data-testid conventions), rely on fragile CSS selectors (GTM), or are expensive auto-capture platforms (Heap, FullStory).

How this works

  1. Babel plugin walks the JSX AST at build time and injects a data-analytics-id on every element
  2. Each ID is a deterministic SHA-256 hash of the element's structural identity — file path, component ancestry, element type, stable props, sibling index
  3. A manifest file maps hashes to human-readable paths (e.g. e7ddfb4e → src/pages/Login.jsx > LoginPage > button[type=submit])
  4. An analytics config file maps hashes to additional attributes — the plugin injects these at build time

The analytics team inspects an element on staging, grabs the hash, adds their tracking config to a JSON file, and the next build picks it up. No code changes, no PRs.

What makes the hash stable

Hashes are derived from AST structure, not source positions. They're insensitive to:

  • Whitespace and formatting changes
  • Comment additions/removals
  • Import reordering
  • Variable renames
  • Unrelated code changes in the same file

They change when the element's actual identity changes: different component, different type, different file, changed stable props.

How this differs from data-testid plugins

Existing plugins like babel-plugin-react-data-testid generate IDs from component names (LoginForm_button). These aren't unique across siblings, break on component renames, and don't include any workflow for non-developers to configure tracking. This plugin produces unique structural hashes with a config-driven attribute injection layer.

Install

npm install -D babel-plugin-analytics-hash

Peer dependency: @babel/core ^7.0.0

Setup

Vite

// vite.config.js
import react from '@vitejs/plugin-react'

export default {
  plugins: [
    react({
      babel: {
        plugins: [
          ['babel-plugin-analytics-hash', {
            configPath: './analytics-tags.json',
            manifestPath: './analytics-manifest.json',
            hashLength: 8,
          }]
        ]
      }
    })
  ]
}

Webpack / babel-loader

// webpack.config.js or babel.config.json
{
  "plugins": [
    ["babel-plugin-analytics-hash", {
      "configPath": "./analytics-tags.json",
      "manifestPath": "./analytics-manifest.json"
    }]
  ]
}

Next.js

// babel.config.json
{
  "presets": ["next/babel"],
  "plugins": [
    ["babel-plugin-analytics-hash", {
      "configPath": "./analytics-tags.json",
      "manifestPath": "./analytics-manifest.json"
    }]
  ]
}

Create React App (via CRACO)

// craco.config.js
module.exports = {
  babel: {
    plugins: [
      ['babel-plugin-analytics-hash', {
        configPath: './analytics-tags.json',
        manifestPath: './analytics-manifest.json',
      }]
    ]
  }
}

Rollup

import babel from '@rollup/plugin-babel'

export default {
  plugins: [
    babel({
      babelHelpers: 'bundled',
      plugins: [['babel-plugin-analytics-hash']]
    })
  ]
}

It's a standard Babel plugin — if your toolchain runs Babel on JSX, it'll work.

Usage

Build output

Every element gets a hash attribute:

<!-- input -->
<button type="submit" className="btn-primary">Sign In</button>

<!-- output -->
<button data-analytics-id="e7ddfb4e" type="submit" className="btn-primary">Sign In</button>

Manifest

Generated on each build at manifestPath:

{
  "_totalElements": 247,
  "elements": {
    "e7ddfb4e": "src/pages/Login.jsx > LoginPage > form > button[type=submit].btn-primary",
    "776e96df": "src/pages/Login.jsx > LoginPage > form > input#email[name=email][type=email]",
    "821817cf": "src/pages/Dashboard.jsx > Dashboard > div#promo-slot.personalization-slot"
  }
}

Use this to look up what a hash corresponds to, or search by keyword to find elements.

Analytics config

Create analytics-tags.json in your project root:

{
  "e7ddfb4e": {
    "data-track-click": "login_submit",
    "data-track-category": "authentication"
  },
  "821817cf": {
    "data-personalization": "adobe-target-promo-banner"
  }
}

On the next build, the plugin reads this and injects those attributes alongside the hash:

<button data-analytics-id="e7ddfb4e" data-track-click="login_submit" data-track-category="authentication" ...>

Works with any tracking tool — Adobe Analytics, GTM event listeners, Segment, Mixpanel, Adobe Target personalization containers, or custom scripts that query data-* attributes.

Stale config warnings

If the config references a hash that no longer matches any element (deleted or restructured), the build logs a warning:

[analytics-hash] Warning: hash "e7ddfb4e" configured but no matching element found

Workflow summary

  1. Install plugin, add to Babel config
  2. Build → every element gets a data-analytics-id
  3. Analytics person inspects element on staging → copies hash
  4. Adds entry to analytics-tags.json → commits
  5. Next build auto-injects configured attributes
  6. No developer ticket needed

Options

All optional. Works with zero config.

{
  attributeName: "data-analytics-id",  // injected attribute name
  hashLength: 10,                       // hex chars (8 ≈ 65K safe, 10 ≈ 1M safe)
  configPath: "./analytics-tags.json",
  manifestPath: "./analytics-manifest.json",

  // Element filtering
  ignoreElements: ["html", "head", "meta", "script", "style", "br", "hr", "Fragment", ...],
  onlyElements: [],                    // e.g. ["button", "input", "a"] to limit scope
  excludeFiles: ["node_modules", "\\.test\\.", "\\.spec\\."],
  includeFiles: [],                    // e.g. ["src/pages/", "src/features/"]
  excludeComponents: [],               // e.g. ["Icon", "Spinner"]
  onlyInsideComponents: [],            // e.g. ["App"]
  skipIfHasAttribute: "",              // e.g. "data-testid" to skip pre-tagged elements

  enabled: true,                       // set false to disable (e.g. in CI)
}

Performance

Build time: One AST pass + one SHA-256 per element. ~1–3s for a 10K-element app. Per-file during HMR, so effectively invisible in dev.

DOM overhead: ~28 bytes per element for the attribute. After gzip, ~22 KB total for a 10K-element app. Smaller than a favicon. Facebook ships data-testid to billions of pageviews — this is a non-concern.

Manifest: ~100 bytes/element, never shipped to the browser. 10K elements ≈ 1 MB file in your project dir.

Production safety

data-* attributes are part of the HTML spec. They don't execute code, don't expose internals (hashes are opaque), and don't create security vulnerabilities. Inspect Amazon, Netflix, or Airbnb — custom data attributes are everywhere in production.

If you'd rather strip them in prod, set enabled: false in your production Babel config.

Collision handling

With 8 hex chars (32 bits), collision probability stays under 1% up to ~65K elements. With 10 chars (40 bits), you're safe past 1M. The plugin also runs collision detection at build time and logs a warning if one occurs. Bump hashLength if you ever see one.

Roadmap

  • [ ] Chrome extension for visual hash discovery
  • [ ] SWC plugin (for non-Babel setups)
  • [ ] CLI for manifest diffs between releases
  • [ ] Dashboard UI for manifest browsing + config editing

Contributing

See CONTRIBUTING.md.

License

MIT