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

@zeix/cem-plugin-le-truc

v0.2.1

Published

Custom Elements Manifest analyzer plugin for @zeix/le-truc factory components

Readme

@zeix/cem-plugin-le-truc

A @custom-elements-manifest/analyzer plugin that generates standards-compliant custom-elements.json manifests for components built with @zeix/le-truc.

Le Truc uses a factory pattern (defineComponent<Props>(tagName, factory)) rather than class declarations. This plugin bridges the gap so the full CEM ecosystem (editor LSP, AI coding agents, design system tooling) works out of the box.

The plugin runs within @custom-elements-manifest/analyzer (cem analyze) and does not require @pwrs/cem. The two packages are complementary, not competing — see Complementary tooling below.

Installation

bun add -D @zeix/cem-plugin-le-truc

Usage

Add to custom-elements-manifest.config.mjs:

import { leTrucPlugin } from '@zeix/cem-plugin-le-truc'
import ts from 'typescript'

let typeChecker

export default {
  globs: ['src/**/*.ts'],
  exclude: ['**/*.test.ts'],
  overrideModuleCreation({ ts, globs }) {
    const program = ts.createProgram(globs, { strict: true })
    typeChecker = program.getTypeChecker()
    return program.getSourceFiles().filter(sf => !sf.isDeclarationFile)
  },
  plugins: [leTrucPlugin(() => typeChecker)],
}

Run the analyzer:

npx cem analyze

This generates custom-elements.json from your Le Truc components.

What gets extracted

| CEM field | Source | |---|---| | tagName | First argument of defineComponent(tagName, …) | | name | PascalCase from tagName (basic-counterBasicCounter) | | description | JSDoc above the export default defineComponent(…) | | members | Properties of the Props type argument via TypeScript type checker | | attributes | Properties in expose({}) whose initializer is an as* call from @zeix/le-truc (imported by name or relative path), plus @attribute/@attr JSDoc tags | | slots, events, cssParts, cssProperties | @slot, @fires, @csspart, @cssprop JSDoc tags | | demos | @demo {url} description JSDoc tags |

The plugin also fixes up three schema-compliance gaps in the default analyzer's output: it links the default export to the synthesised declaration name (required by Reference.name), adds package: "global:" to built-in superclass references (e.g. HTMLElement stubs), and relativizes module paths against the working directory — the overrideModuleCreation boilerplate feeds ts.createProgram source files whose names are absolute, which would otherwise leak local (or CI-runner) paths into the published manifest.

JSDoc contract

/**
 * A counter that increments on click. Use it for demonstrating reactive
 * property updates. The host element should contain a `<button>` and a
 * `<span>`; the value must be a non-negative integer.
 * @slot - Default slot for button label
 * @fires count-changed - Fired when the count changes
 * @csspart counter - The counter container
 * @cssprop --counter-color - Text color
 * @demo {./examples/basic-counter.html} Interactive counter demo
 */
export default defineComponent<CounterProps>('basic-counter', ({ expose }) => {
  expose({ count: asInteger() })
  return []
})

Property descriptions go on the Props type:

export type CounterProps = {
  /** Current count value. Read from the `count` attribute at connect time. */
  count: number
}

Description quality

The description is free-form JSDoc, but a few conventions improve the manifest's usefulness across the CEM ecosystem — and score well in documentation-health tools like cem health:

  • Explain purpose and context. Use words like use, for, when, provides to describe what the component is for, not just what it is.
  • State constraints with RFC 2119 keywords. Words like must, should, avoid clarify requirements (e.g. "the value must be a non-negative integer").
  • Mention accessibility. If the component provides ARIA semantics, keyboard interaction, or screen-reader support, note it.
  • Mention keyboard interaction. If the component handles keyboard events (Enter, Space, Arrow keys, focus management), document it.

These are recommendations, not requirements — a clear one-liner is better than keyword-stuffed prose.

@attribute annotations (connect-time attributes)

Attributes that a component reads once via host.getAttribute() at connect time — server-side configuration of stable client-side behavior — never appear in expose({}), so they cannot be detected statically. Declare them with @attribute (alias @attr), the same tag names the stock analyzer supports for class-based components:

/**
 * A resizable split view.
 * @attribute {'horizontal'|'vertical'} [orientation=horizontal] - Layout direction. Read once at connect time.
 */

Syntax: @attribute {type} name - description, all parts except the name optional; [name=default] declares a default value. The emitted entry has no fieldName — the CEM schema's way of saying the attribute is not backed by a property. If the name collides with an expose()-derived attribute, the entries merge: the Props type remains the source of truth for type and fieldName, while the JSDoc tag contributes description and default.

@demo annotations

/**
 * @demo {./examples/basic-counter.html} Interactive counter demo
 * @demo {https://myapp.com/demos/counter} Hosted production demo
 */

The URL in braces identifies the demo page; the trailing text is a markdown description. A demo with both a URL and a description scores full marks in cem health. The URL may be relative (published with the package) or absolute (hosted).

Complementary tooling (@pwrs/cem)

This plugin generates custom-elements.json via @custom-elements-manifest/analyzer (cem analyze). For additional tooling — consuming the manifest rather than generating it — install @pwrs/cem globally:

bun add -g @pwrs/cem

@pwrs/cem provides tools that read the generated manifest:

| Command | Purpose | |---|---| | cem validate | Validate custom-elements.json against the CEM schema | | cem health | Score documentation quality (descriptions, attributes, demos, etc.) | | cem lsp | Language Server for editor autocomplete, hover docs, and diagnostics | | cem mcp | MCP server for AI coding agents (Claude Code, etc.) | | cem list | Query the manifest (tags, attributes, slots, events, etc.) | | cem serve | Development server with live reload for component demos |

Note: @pwrs/cem and @custom-elements-manifest/analyzer both provide a cem CLI but share no overlapping commands. The analyzer provides cem analyze (generation); @pwrs/cem provides validate, lsp, mcp, serve, etc. (consumption). They are complementary — bun run resolves the local analyzer for cem analyze, while bare cem <command> resolves the global @pwrs/cem.