Skip to content

jackhp95/elm-cem

elm-cem

Generate type-safe Elm bindings from a web component library's Custom Elements Manifest. One repo, one function: the CEM → Elm generator (CLI + codegen).

Prerequisites

At runtime the CLI (bin/elm-cem.js) invokes elm-codegen run, which compiles the generator with the Elm 0.19.1 compiler. elm-codegen itself ships as a dependency of this package, but the elm binary does not — you must provide it. You need:

  • Node.js (to run the CLI).
  • The elm 0.19.1 compiler on your PATH. Any of these works:
    • install it globally — npm install -g elm@0.19.1-6; or
    • manage it per-project with elm-tooling (add an elm-tooling.json pinning elm 0.19.1 and run elm-tooling install); this repo does exactly that for local development; or
    • use any existing elm-codegen-capable toolchain that already has elm available.

If elm is missing you will see elm-codegen fail to compile the generator.

Generated module layout

Modules are emitted under the library's own top-level namespace (<Lib>, derived from the component tag prefix — e.g. m3e-*M3e). For each component you get a gradient of three layers, plus shared vocabulary:

Module Layer
<Lib> one-import barrel — re-exposes every component constructor + the shared attribute/event vocabulary
<Lib>.<Component> (e.g. M3e.Button) top: the strict, phantom-typed view + per-component setters
<Lib>.Cem.<Component> middle: the loose, phantom-gated setters (escape hatch)
<Lib>.Cem.Html.<Component> bottom: the raw elm/html builder, no phantom typing
<Lib>.Value the enum token vocabulary (filled, outlined, …)
<Lib>.Cem.Vocab / <Lib>.Cem.Html.Vocab the shared, component-agnostic middle/bottom vocabulary

The interior Cem segment marks the loose/raw escape-hatch layers; the strict, everyday API lives at the un-prefixed <Lib>.<Component>.

node bin/elm-cem.js --flags-from=path/to/custom-elements.json --output=src

Declarative config (--config-from)

--flags-from alone gives the generic, manifest-only output. An optional --config-from=<json> adds per-component refinements the CEM can't express. Keys are component module names (e.g. Button), and every field is optional:

{
  "Button": {
    "slots": {
      "icon":    { "kinds": ["any"], "required": false, "multi": false },
      "default": { "kinds": ["any"], "multi": true }
    },
    "required": { "action": "action:click,link", "ariaLabel": "ariaLabel" },
    "group":    { "linear": "m3e-linear-progress", "circular": "m3e-circular-progress" },
    "examples": [
      { "title": "Basic", "code": "Button.view [] [ text \"Go\" ]", "section": "Usage" }
    ],
    "docMeta":  { "category": "actions" }
  }
}
  • slots — typed slot children. Each slot maps to { kinds, multi, required }; a required single slot becomes a record field on view.
  • required — non-slot required fields. A value of action:<caps> (comma- separated, e.g. action:click,link,remove) makes the field an <Lib>.Action with those capabilities and suppresses the raw href/target/rel/download setters; ariaLabel wires the field to an aria-label attribute.
  • group — variant-split families: several tags folded into one module with one constructor per variant.
  • examples — usage examples appended to the module docs (grouped by section), each also emitted as a machine-readable marker.
  • docMeta — opaque key/value doc metadata, passed through untouched.

--config-from may be passed multiple times; the files are deep-merged per component (component key, then each component's field object — later files add or override individual fields). Omitting --config-from keeps the config-free path, so the generator stays generic across libraries.

Note: the files under cem-configs/ are not elm-cem configs — they are @custom-elements-manifest/analyzer configs (globs/exclude/litelement/plugins) for producing a library's custom-elements.json upstream, which you then pass to --flags-from.

The elm-cem ecosystem (1 repo = 1 function)

  • elm-cem-decoder — decode CEM JSON → Elm types.
  • elm-cem (this repo) — generate <Lib>.* Elm modules (see the layout above) from a CEM.
  • elm-cem-template — scaffold a publishable bindings package.
  • elm-cem-m3e — generated m3e atoms (M3e.*, with the loose layers under M3e.Cem.*).
  • elm-m3e — the hand-written typed M3e.* library over those atoms.

Extracted from the original elm-custom-elements-manifest monorepo (now archived).

Releasing

Everything needed to cut a release lives in this repo:

  • RELEASE-CHECKLIST.md — the ordered, owner-only steps (fix the consumer install hook, version bump, CI, tag, npm publish, flip public).
  • CHANGELOG.md — release notes + the stability policy for the two public surfaces (exposed Elm modules and generated output).
  • CONTRIBUTING.md — setup and the verify gates (npm test).
  • SECURITY.md — private vulnerability reporting.

About

Generate type-safe Elm bindings from any web component's Custom Elements Manifest: a strict <Lib>.* layer over loose <Lib>.Cem.* layers.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors