I built a compiler so I could stop writing custom element boilerplate
I've been building HTMLPlus Element on and off for a few years. It's at v4 now, 500+ commits, and I've never really shown it to anyone. So this is me finally doing that. The thing I got tired of Custom elements are a good idea. The browser gives you a component model that outlives whichever framework you happen to be using this year. What it does not give you is a pleasant way to write one. Every component ends up carrying the same freight: a call, a static block declaring which properties are observed, attribute-to-property conversion written by hand for anything that isn't a string, a place to hang your styles, and — if anyone else is going to use your elements — a set of TypeScript definitions you maintain separately from the code they describe. None of that is the component. It's the...
I've been building HTMLPlus Element on and off for a few years. It's at v4 now, 500+ commits, and I've never really shown it to anyone. So this is me finally doing that. The thing I got tired of Custom elements are a good idea. The browser gives you a component model that outlives whichever framework you happen to be using this year. What it does not give you is a pleasant way to write one. Every component ends up carrying the same freight: a call, a static block declaring which properties are observed, attribute-to-property conversion written by hand for anything that isn't a string, a place to hang your styles, and — if anyone else is going to use your elements — a set of TypeScript definitions you maintain separately from the code they describe. None of that is the component. It's the tax you pay to have one. What I did instead I moved that work into a compiler. You write the class, and the build step figures out the rest. Here is a complete component: That's the whole file, . A few things happened without me writing them: The tag name came from the class name. becomes , so the name lives in one place instead of two. The styles came from sitting next to it. Same base name, picked up automatically — no import, no styles array, no template literal. Property types were resolved from the TypeScript types you already wrote. A property arrives as a number when someone sets the attribute in HTML, without a converter. And the TypeScript definitions your consumers need were generated as part of the build, from the same source, so they can't drift out of sync with the implementation. What else is in there Everything you'd reach for is a decorator in the same package, so there's nothing extra to install: properties, state, events, listeners, public methods, watchers, shadow DOM queries, slots, and context via and . There are small helpers too — , , for RTL/LTR. The output works in any framework as it is. No wrapper packages to generate and keep in step: events are emitted under the naming convention each framework expects, so React, Vue, Angular, Svelte, Solid, Qwik and others consume the same element without a shim. The runtime is about 8 KB gzipped for a typical element, and under 11 KB for the whole library. It's not theoretical I use it to build htmlplus.io, a UI kit. Those components are written exactly the way the counter above is, and the examples on that site run in Angular, React, Vue and Svelte. If you'd rather look at output than at API design, start there. Where it's the wrong tool It compiles, which means a build step. If you want to drop a tag on a page and start writing an element, or you need two components and not twenty, a runtime-only library is a better fit and I'd rather say so than have you find out later. It's a bundler plugin rather than a toolchain of its own — Vite and Rollup today, built on unplugin, so more can follow. Server-side rendering to Declarative Shadow DOM is in progress but not released. Try it Repo: https://github.com/htmlplus/element (MIT) I'd rather have criticism of the API design than stars. If something here looks wrong to you, I genuinely want to hear it.