Le Truc Blog 2.3.0

๐Ÿ“ฆ A Custom Elements Manifest for Le Truc

Avatar of Esther Brunner Esther Brunner ยท updated on 4 min read

Le Truc 2.2 ships custom-elements.json, generated by the new @zeix/cem-plugin-le-truc analyzer plugin โ€“ what the manifest unlocks in editors, AI agents, and design system tooling, and how to generate one for your own components.

Le Truc 2.2 ships a Custom Elements Manifest. The package now includes custom-elements.json, and the plugin that generates it โ€“ @zeix/cem-plugin-le-truc โ€“ is published separately, so you can generate a manifest for your own Le Truc components too.

Why a manifest matters#

A Custom Elements Manifest is a machine-readable JSON description of the custom elements in a package: their tag names, properties, attributes, slots, events, CSS parts, and CSS custom properties. It's the one format the whole web components ecosystem has agreed on for describing components to tools.

Once a package ships one, a lot of tooling works without any per-framework glue:

  • Editor support. cem lsp gives you autocomplete, hover docs, and diagnostics for your custom elements right in HTML templates.
  • AI coding agents. cem mcp serves the manifest over MCP, so agents like Claude Code know your components' props, attributes, and slots instead of guessing from grep results.
  • Documentation and design systems. Storybook, API doc generators, and design system catalogs read custom-elements.json to render component references automatically.

Class-based component libraries have had this for years. Le Truc components couldn't join in โ€“ until now.

The problem: there's no class to analyze#

Standard CEM tooling looks for class declarations: class MyCounter extends HTMLElement, decorated members, static observedAttributes. A Le Truc component has none of that in source. It's a function call:

typescript

export default defineComponent<CounterProps>('basic-counter', ({ expose }) => {
  expose({ count: asInteger() })
  return []
})

The class exists at runtime, inside defineComponent โ€“ but the analyzer works on source code, so it sees nothing to report. Run the stock @custom-elements-manifest/analyzer over a Le Truc project and you get an empty manifest.

@zeix/cem-plugin-le-truc teaches the analyzer this pattern. It detects defineComponent calls and extracts:

  • the tag name from the first argument, and a PascalCase class name from it (basic-counter โ†’ BasicCounter)
  • properties from the Props type argument, resolved through the TypeScript type checker โ€“ your types are the source of truth, nothing is duplicated in JSDoc
  • attributes from the expose({}) call, by recognizing Le Truc's as* parsers (asInteger(), asString(), โ€ฆ) as attribute-backed properties
  • slots, events, CSS parts, and CSS custom properties from @slot, @fires, @csspart, and @cssprop JSDoc tags, plus demo links from @demo

Attributes that a component reads once at connect time via host.getAttribute() never appear in expose({}), so they can't be found statically. Declare those with an @attribute JSDoc tag โ€“ the same tag the stock analyzer supports for class-based components.

Using it in your project#

Install the plugin and add a config file:

sh

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

custom-elements-manifest.config.mjsjs

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)],
}

Then run npx cem analyze and you have a custom-elements.json. The overrideModuleCreation boilerplate is unfortunate but necessary: the plugin needs the TypeScript type checker to resolve your Props types, and the analyzer's plugin API doesn't let a plugin set that up itself.

The plugin README documents the full JSDoc contract, including how to write descriptions that score well in documentation-health tools like cem health.

Where this is going#

This is the first release, and we intend to keep improving it. The manifest format is only as useful as the tools that read it, and those tools have different expectations โ€“ an LSP server, a Storybook integration, and an AI agent each stress different corners of the schema.

So if you use Le Truc components with an editor, a documentation tool, or an agent setup and something doesn't work the way the manifest says it should โ€“ or the manifest is missing something your tool needs โ€“ open an issue on the plugin repo. Real integration reports are the fastest way for us to find out what to build next.