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

@dcoder-x/plugin-shared

v0.1.24

Published

Internal shared library for the Clippy build plugin ecosystem. Contains the AST extractors, selector generators, flow inferrers, and package builders used by `@dcoder-x/next` and `@dcoder-x/vite`.

Readme

@dcoder-x/plugin-shared

Internal shared library for the Clippy build plugin ecosystem. Contains the AST extractors, selector generators, flow inferrers, and package builders used by @dcoder-x/next and @dcoder-x/vite.

You do not need to install this package directly. It is a transitive dependency of the adapter packages and is installed automatically.


If you are building a custom adapter

If you are integrating Clippy with a bundler other than Next.js or Vite, this package provides the full pipeline as composable classes.

npm install @dcoder-x/plugin-shared

Pipeline overview

Source files
    │
    ▼
ClippyIdInjector.injectClippyIds(source, filePath, routePath?)
    │ Injects data-clippy-id and data-clippy-component into compiled HTML elements
    ▼
RouteExtractor.extract()
    │ Discovers all routes from filesystem conventions or React Router AST
    ▼
ComponentExtractor.extract()        ComponentExtractor.extractComponents()
    │ Finds all interactive elements   │ Extracts state, handlers, interaction graphs
    ▼                                  ▼
SelectorGenerator.generate(elements, injectedMap)
    │ Matches injected IDs to elements, ranks selector candidates
    ▼
FlowInferrer.infer()
    │ Builds navigation edge graph, detects flow chains, generates intent patterns
    ▼
PackageBuilder.buildArtifacts()
    │ Assembles clippy-policy.json and clippy-selectors.json
    ▼
PackageWriter.writeArtifacts()   /   Uploader.upload()
    Writes JSON files locally         Uploads to Clippy backend

Minimal custom adapter

import { injectClippyIds, inferRouteFromFilePath } from '@dcoder-x/plugin-shared'
import { RouteExtractor } from '@dcoder-x/plugin-shared/extractors/RouteExtractor'
import { ComponentExtractor } from '@dcoder-x/plugin-shared/extractors/ComponentExtractor'
import { SelectorGenerator } from '@dcoder-x/plugin-shared/extractors/SelectorGenerator'
import { FlowInferrer } from '@dcoder-x/plugin-shared/extractors/FlowInferrer'
import { PackageBuilder } from '@dcoder-x/plugin-shared/upload/PackageBuilder'
import { PackageWriter } from '@dcoder-x/plugin-shared/upload/PackageWriter'
import type { ClippyPluginOptions } from '@dcoder-x/plugin-shared'

async function runClippyPipeline(
  projectRoot: string,
  buildId: string,
  moduleGraph: Map<string, { id: string; importedIds: readonly string[] }>,
  injectedMap: Record<string, Array<{ clippyId: string; component: string; tag: string; line: number; label?: string }>>,
  options: ClippyPluginOptions
) {
  const routes = await new RouteExtractor(projectRoot).extract()

  const extractor = new ComponentExtractor(
    { type: 'rollup', moduleGraph },
    routes
  )

  const elements = await extractor.extract()
  const components = await extractor.extractComponents()
  const selectors = new SelectorGenerator().generate(elements, injectedMap)
  const flows = new FlowInferrer(routes, elements, components).infer()

  const artifacts = new PackageBuilder().buildArtifacts({
    projectRoot,
    buildId,
    bundler: 'vite',   // or 'webpack'
    routes,
    selectors,
    flows,
    components,
  })

  if (options.localOutputDir) {
    new PackageWriter().writeArtifacts(options.localOutputDir, artifacts)
  }
}

Transform hook (injection)

Call injectClippyIds in your bundler's transform hook for each .tsx / .jsx / .ts / .js file:

// In your bundler's transform/loader:
const routePath = inferRouteFromFilePath(filePath) ?? undefined
const result = injectClippyIds(sourceCode, filePath, routePath)

// result.source       — transformed source with data-clippy-id attributes
// result.injected     — metadata array for building the injectedMap
// result.injectedCount — number of attributes injected (0 = file had no HTML elements)

if (result.injectedCount > 0) {
  injectedMap[filePath] = result.injected
}

inferRouteFromFilePath detects App Router (app/**/page.tsx) and Pages Router (pages/**/*.tsx) conventions and returns the route path string (e.g., /dashboard/forms). Returns null for non-route files.


Exported API

Injection

| Export | Description | |---|---| | injectClippyIds(source, filePath, routePath?) | Injects data-clippy-id and data-clippy-component into all native HTML elements in a JSX/TSX source string | | inferRouteFromFilePath(filePath) | Derives route path from an absolute file path for App Router / Pages Router files | | deriveClippyId(component, tag, line, routePath?, label?) | Computes a stable data-clippy-id value | | deriveRouteComponentName(routePath) | Converts /admin/transactionsAdminTransactions | | GENERIC_COMPONENT_NAMES | Set of component names treated as generic (Page, Layout, App, etc.) |

Extractors

| Export | Description | |---|---| | RouteExtractor | Filesystem + AST route discovery | | ComponentExtractor | Module-graph traversal, element extraction, component analysis | | SelectorGenerator | Ranks and matches selector candidates for each element | | FlowInferrer | Navigation edge graph construction and flow chain detection | | InteractionGraphExtractor | AST extraction of state → conditional render chains | | ComponentContextResolver | useState / useReducer / event handler extraction |

Output

| Export | Description | |---|---| | PackageBuilder | Assembles PolicyArtifacts from pipeline output | | PackageWriter | Writes clippy-policy.json and clippy-selectors.json to disk | | Uploader | HTTP upload client for the Clippy backend |

Types

All public types are exported from the package root:

import type {
  ClippyPluginOptions,
  PolicyArtifacts,
  PolicyDocument,
  PolicyComponent,
  PolicyFlow,
  PolicySelectorEntry,
  SelectorManifest,
  SelectorManifestEntry,
  DiscoveredRoute,
  DiscoveredElement,
  ElementWithSelectors,
  SelectorCandidate,
  ComponentInteraction,
  TriggerSpec,
  EffectSpec,
  InferredFlow,
  FlowEdge,
  UploadResult,
} from '@dcoder-x/plugin-shared'

Output artifact formats

clippy-policy.json — full policy document

interface PolicyDocument {
  version: string
  buildId: string
  generatedAt: string
  bundler: 'webpack' | 'vite'
  routes: DiscoveredRoute[]
  selectors: PolicySelectorEntry[]
  components: PolicyComponent[]  // only components with state or interactions
  flows: PolicyFlow[]
}

clippy-selectors.json — deduplicated selector manifest

interface SelectorManifest {
  version: string
  buildId: string
  generatedAt: string
  selectors: SelectorManifestEntry[]
}

interface SelectorManifestEntry {
  id: string           // data-clippy-id value
  selector: string     // CSS selector string
  component: string    // enclosing component name
  tag: string          // lowercase HTML tag
  label?: string       // human-readable label (may be null)
  routes: string[]     // all routes where this element appears
}

DiscoveredRoute

interface DiscoveredRoute {
  path: string          // e.g. "/dashboard/forms/[id]"
  filePath: string      // relative to project root, e.g. "app/dashboard/forms/[id]/page.tsx"
  isDynamic: boolean
  params: string[]      // e.g. ["id"]
  layout: string | null // relative path to nearest layout file
  routerType: 'app' | 'pages' | 'react-router' | 'tanstack'
  semantic: string      // space-separated route segments, reversed
}

PolicyComponent

Only components with at least one stateVariable or interaction are included.

interface PolicyComponent {
  name: string
  filePath: string   // relative to project root
  route: string
  stateVariables: Array<{
    name: string
    setter?: string
    initialValue?: string
  }>
  interactions: ComponentInteraction[]
}

interface ComponentInteraction {
  trigger: {
    event: string      // e.g. "onClick"
    element: string    // JSX tag that carries the handler
    setsState?: string // state variable the handler mutates
  }
  effect: {
    type: 'conditionalRender' | 'asyncEffect' | 'contextDependency'
    rendersWhenTrue?: string   // component name that appears
    rendersWhenFalse?: string  // component name that disappears
    waitStrategy: 'elementAppears' | 'domSettle' | 'none'
    selector?: string          // [data-clippy-component='...'] selector to wait for
    settleMs?: number          // milliseconds to wait for DOM to settle
  }
}

PolicyFlow

interface PolicyFlow {
  flowId: string
  page: string               // starting route
  intentPatterns: string[]   // user phrases that match this flow
  steps: PolicyFlowStep[]
}

interface PolicyFlowStep {
  step: number
  action: 'navigate' | 'transition' | 'interact'
  target: string             // CSS selector for the element to act on
}

Selector ID format

ComponentName[-LabelText]-tag-lineNumber

Examples:
  DashboardForms-CreateForm-button-138
  AdminTransactions-Approve-button-172
  LoginPage-form-115          (no label — form labels use submit button text only)
  Input-input-7               (shared component, no label from its own file)
  • ComponentName — enclosing React component, or route-derived name when generic (e.g., AdminTransactions instead of Page)
  • LabelTextaria-label > visible text (TitleCase, max 2 words) > placeholder. Omitted for forms (submit button text is used instead of the full field list)
  • tag — lowercase HTML tag
  • lineNumber — source file line, stable tiebreaker

Related packages