Generate type-safe Elm bindings from a web component library's Custom Elements Manifest. One repo, one function: the CEM → Elm generator (CLI + codegen).
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
elm0.19.1 compiler on yourPATH. 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.jsonpinningelm0.19.1and runelm-tooling install); this repo does exactly that for local development; or - use any existing elm-codegen-capable toolchain that already has
elmavailable.
- install it globally —
If elm is missing you will see elm-codegen fail to compile the generator.
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--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 }; arequiredsingle slot becomes a record field onview.required— non-slot required fields. A value ofaction:<caps>(comma- separated, e.g.action:click,link,remove) makes the field an<Lib>.Actionwith those capabilities and suppresses the rawhref/target/rel/downloadsetters;ariaLabelwires the field to anaria-labelattribute.group— variant-split families: several tags folded into one module with one constructor per variant.examples— usage examples appended to the module docs (grouped bysection), 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/analyzerconfigs (globs/exclude/litelement/plugins) for producing a library'scustom-elements.jsonupstream, which you then pass to--flags-from.
- 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 underM3e.Cem.*). - elm-m3e — the hand-written typed
M3e.*library over those atoms.
Extracted from the original elm-custom-elements-manifest monorepo (now archived).
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.