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

@junro/component-tagger

v0.0.1

Published

Vite plugin that tags JSX elements with data-dev-* attributes (source path, line, enclosing component) so Junro's web plugin can point a report at the right file.

Readme

@junro/component-tagger

A Vite plugin that stamps every JSX element with data-dev-* attributes at build time — the source file, line, column and the React component that rendered it — so that a picked DOM node can be traced back to apps/web/src/features/pricing/PricingCard.tsx:42:8.

It is the tagging half of the Junro web plugin: the widget reads these attributes off whatever the reporter clicks and puts the file and component into the report. It works on its own too — as stable selectors for E2E tests, or for "which component rendered this?" in DevTools.

Forked from vite-plugin-component-debugger (MIT). See LICENSE.

Install

pnpm add -D @junro/component-tagger
# npm install --save-dev @junro/component-tagger

Quick start

// vite.config.ts
import { fileURLToPath } from 'node:url'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import componentTagger from '@junro/component-tagger'

export default defineConfig({
  plugins: [
    // FIRST. It must see the untouched source: @vitejs/plugin-react prepends HMR code (line
    // numbers would shift) and TanStack Router's code splitter regenerates route modules.
    componentTagger({
      // undefined → tag the dev server only. Set to true to ship tags in a production build.
      enabled: process.env.ENABLE_COMPONENT_TAGGER === 'true' || undefined,
      // Directory that data-dev-path is relative to (default: Vite's root).
      projectRoot: fileURLToPath(new URL('.', import.meta.url)),
    }),
    react(),
  ],
})

What it does

// src/components/PricingCard.tsx
export function PricingCard({ plan }: Props) {
  return (
    <section className="card">
      <button onClick={upgrade}>Upgrade</button>
    </section>
  )
}

becomes

<section
  data-dev-id="src/components/PricingCard.tsx:4:4"
  data-dev-name="section"
  data-dev-path="src/components/PricingCard.tsx"
  data-dev-line="4"
  data-dev-file="PricingCard.tsx"
  data-dev-component="PricingCard"
  className="card"
>
  <button data-dev-id="src/components/PricingCard.tsx:5:6" data-dev-name="button" … data-dev-component="PricingCard">

| Attribute | Value | | -------------------- | ----------------------------------------------------------------------- | | data-dev-id | path:line:column of the opening tag — unique per source element | | data-dev-name | The JSX tag as written: section, Card, Menu.Item | | data-dev-path | Source path relative to projectRoot | | data-dev-line | Line of the opening tag | | data-dev-file | Basename of the source file | | data-dev-component | The React component whose body renders the element (see below) | | data-dev-metadata | URL-encoded JSON of string props / text content (opt-in, see includeProps) |

data-dev-component — the enclosing component

The component name is resolved from the AST, not from the tag. Every common way of declaring a component is recognised:

function Card() {}                       // Card
export default function () {}            // file basename (index.tsx → parent directory)
const Card = () => …                     // Card
const Card = memo(forwardRef(() => …))   // Card  (any call wrapper: memo, forwardRef, observer, …)
const Card = (() => …) as FC             // Card  (as / satisfies)
Menu.Item = () => …                      // Menu.Item
class Table extends Component {}         // Table, for every method and class-property arrow

Callbacks inherit the component they live in: elements rendered from items.map(item => <li />), render props, useMemo(() => <p />) and lowercase render helpers such as renderRow inside Table all report Table. A lowercase function with no capitalized ancestor reports itself (renderIcon), and JSX outside any function reports ''.

Caveat — components that do not forward props. Tags are written on the JSX call site. When <Card className="x"> is rendered by PricingPage, the <Card> tag carries data-dev-component="PricingPage", and whether it reaches the DOM depends on Card spreading its props onto a host element. A Card that ignores unknown props drops the call-site tag; what survives in the DOM is the tag on Card's own root element, which reads data-dev-component="Card" with Card's source path. Both are true statements about the element — the tag on the DOM always names the component whose file contains that line.

Which modules are tagged

  • Files whose extension is in extensions (default .jsx, .tsx) after stripping any query or hash from the module id. route.tsx?tsr-split=component---x (TanStack Router's code splitter), Comp.tsx?v=abc (Vite's cache buster) and Comp.tsx#hash all resolve to the .tsx file.
  • \0-prefixed virtual modules are never tagged.
  • Anything under a node_modules directory is skipped unless includeNodeModules: true.
  • An element that already carries <prefix>-id is left alone, so a second pass is a no-op.
  • <svg:rect> (namespaced names) and fragments are never tagged; <A.B.C> is tagged as A.B.C.

Monorepos and prebuilt packages

projectRoot is what makes data-dev-path meaningful across packages. Point every Vite config in the workspace at the repository root and the app reports apps/web/src/… while a design-system component reports packages/design-system/src/primitives/dialog.tsx.

A package consumed as a prebuilt dist/ (a component library built with vite build) is never seen by the consuming app's Vite pipeline, so add the tagger to that package's own Vite config. Because it is always a build, opt in explicitly:

componentTagger({
  enabled: process.env.ENABLE_COMPONENT_TAGGER === 'true',
  projectRoot: fileURLToPath(new URL('../..', import.meta.url)),
  excludePaths: ['**/*.stories.tsx'],
})

and run ENABLE_COMPONENT_TAGGER=true vite build for the development artefact only.

Configuration

Core

| Option | Type | Default | Description | | -------------------- | ---------- | ------------------ | ----------------------------------------------------------------------------------------------------- | | enabled | boolean | undefined | undefined → only when Vite's command is 'serve' (dev server). An explicit boolean always wins. | | projectRoot | string | Vite config.root | Directory data-dev-path / data-dev-id are relative to. process.cwd() when used outside Vite. | | includeNodeModules | boolean | false | Also tag modules under node_modules. | | tagComponents | boolean | true | Tag component call sites (<Card>, <Menu.Item>, <motion.div>) as well as host elements. | | attributePrefix | string | 'data-dev' | Prefix for every attribute. | | extensions | string[] | ['.jsx', '.tsx'] | File extensions to process. | | preset | Preset | undefined | 'minimal' (id only), 'testing' (id, name, component), 'debugging' (everything), 'production' |

Attribute control

| Option | Type | Default | Description | | ------------------- | ----------------- | ----------- | ------------------------------------------------------ | | includeAttributes | AttributeName[] | undefined | Allowlist — only these attributes are written. | | excludeAttributes | AttributeName[] | undefined | Disallowlist. Ignored when includeAttributes is set. | | transformers | object | undefined | Per-attribute value transformers (path, id, name, line, file, component). | | groupAttributes | boolean | false | Emit one <prefix> attribute holding all values as JSON. | | metadataEncoding | 'json' \| 'base64' \| 'none' | 'json' | Encoding of data-dev-metadata (and of the grouped attribute). | | includeProps | boolean | false | Put string / boolean props into data-dev-metadata. | | includeContent | boolean | false | Put the element's text content into data-dev-metadata. |

AttributeName is 'id' | 'name' | 'path' | 'line' | 'file' | 'component' | 'metadata'.

Filtering

| Option | Type | Default | Description | | ----------------- | --------------------------- | -------------------------------- | ------------------------------------------------------------ | | includePaths | string[] | undefined | Globs (matched against the projectRoot-relative path) to include. | | excludePaths | string[] | undefined | Globs to exclude. | | excludeElements | string[] | ['Fragment', 'React.Fragment'] | Tag names (full dotted form) to skip. | | customExcludes | Set<string> | Three.js / react-three-fiber elements | More tag names to skip. | | shouldTag | (info: ComponentInfo) => boolean | undefined | Per-element veto. info has elementName, component, filePath, line, column, props, content. | | customAttributes| (info: ComponentInfo) => Record<string, string> | undefined | Extra <prefix>-* attributes per element. | | maxDepth / minDepth / tagOnlyRoots | number / number / boolean | 0 / 0 / false | Limit tagging by JSX nesting depth. |

Statistics and debugging

| Option | Type | Default | Description | | ----------------------- | ----------------------------- | ----------- | -------------------------------------------------------- | | onTransform | (stats: TransformStats) => void | undefined | Called after each tagged file. | | onComplete | (stats: CompletionStats) => void | undefined | Called from buildEnd. | | exportStats | string | undefined | Write the completion stats to this JSON file (inside projectRoot). | | includeSourceMapHints | boolean | false | Add a <prefix>-sourcemap attribute. | | debug | boolean | false | Log every processed file and tagged element. |

Troubleshooting

Line numbers are off by ~19. The tagger is running after @vitejs/plugin-react. Put it first.

Route components are not tagged. With @tanstack/router-plugin and autoCodeSplitting the tagger must be listed before tanstackRouter(); the split modules are regenerated from the AST and would otherwise lose their source positions.

Nothing is tagged in a build. That is the default: leave enabled undefined for dev-only tags, or pass enabled: true for the build that should carry them.

Design-system elements have no tags. The package is consumed prebuilt — add the tagger to its own Vite config (see Monorepos and prebuilt packages).

Development

pnpm --filter @junro/component-tagger test    # vitest
pnpm --filter @junro/component-tagger build   # tsup → dist (cjs + esm + d.ts)
pnpm --filter @junro/component-tagger check   # test + build

License

MIT. Forked from vite-plugin-component-debugger (Copyright (c) 2024); portions Copyright (c) 2026 Junro.