Targets
Unframework compiles each component to seven targets. Each one writes a component in its framework's own format, and imports nothing but its framework.
| Target | File | Component |
|---|---|---|
| React | ProductTile.tsx | A function component |
| Vue | ProductTile.vue | A single-file component with <script setup lang="ts"> |
| Svelte | ProductTile.svelte | A component in runes mode, with <script lang="ts"> |
| Angular | product-tile.ts | A component class with signal inputs and an inline template |
| Solid | ProductTile.tsx | A function component |
| Qwik | ProductTile.tsx | A component$ |
| Astro | ProductTile.astro | A component with its props in the frontmatter |
Props shows the seven files for one component.
Capabilities
A capability is a feature a component can use: a kind of node, a kind of attribute, or something a framework does at run time. Each target declares, for every capability, how it supports it:
- native: the framework has the feature, and the output uses it;
- emulated: the output adds a small inline helper, named in the cell, because the framework lacks the feature;
- unsupported: the framework renders the feature differently, or not at all. The compiler reports UF4001 at the feature's first use, with the target's reason, and the cell sets its severity.
So a difference between frameworks is declared here, never found only in a test.
| Capability | What it covers | React | Vue | Svelte | Angular | Solid | Qwik | Astro |
|---|---|---|---|---|---|---|---|---|
element | HTML elements | native | native | native | native | native | native | native |
text | Static text | native | native | native | native | native | native | native |
static-attribute | Attributes with a literal value | native | native | native | native | native | native | native |
listbox | A <select> shown as a single-selection list box | native | unsupported | native | native | native | native | native |
interactivity | Event handlers and updates in the browser | native | native | native | native | native | native | unsupported |
props | A component with props | native | native | native | native | native | native | native |
interpolation | Expressions rendered as text | native | native | native | native | native | native | native |
conditional | c ? <A /> : <B /> and c && <A /> | native | native | native | native | native | native | native |
list | .map with a key: the items' content and order | native | native | native | native | native | native | native |
fragment | A root fragment with several nodes | native | native | native | native | native | native | native |
bound-attribute | Attributes bound to an expression | native | native | native | native | native | native | native |
class-binding | A class of names, toggles and expressions | emulated cx | native | native | native | emulated cx | native | native |
style-binding | A style of static and bound declarations | native | native | native | native | native | native | native |
attribute-spread | A spread of an object whose type declares its keys | native | native | native | native | native | native | native |
svg | An <svg> and the SVG inside it | native | native | native | native | native | native | native |
The cells that are not native:
- React,
class-binding: React'sclassNametakes one string, so an inline helper,cx, joins the static names, toggles and dynamic parts. - Solid,
class-binding: Solid'sclasstakes one string, so an inline helper,cx, joins dynamic parts with the static names and toggles. Static names and toggles alone print asclassandclassList. - Vue,
listbox(an error): Vue's client setsmultiplebut notsizebefore it inserts the options, so it selects the first option, as a drop-down does, where HTML selects none. - Astro,
interactivity(for information): Astro components render on the server only, so their event handlers are inert.
Every other difference between the frameworks is either handled by the target that has it, or rejected for every target by the compiler, with a code that names the framework. Attributes and Templates list them.
The Angular host
Angular renders every component inside a host element, which wraps the template's root. The other frameworks render the root directly. Unframework gives each Angular component an element selector, uf- and its name in kebab case, and styles its host display: contents:
@Component({
selector: "uf-product-tile",
host: { style: "display: contents" },
preserveWhitespaces: false,
A consumer writes <uf-product-tile>, Angular's ordinary element syntax. display: contents keeps the host out of layout, so the component's geometry and pixels match the other targets', and its accessibility tree is the same. The template holds the whole render tree: a single root, the roots of a root fragment, and the conditionals and lists beside them all render inside the one host.
Attribute selectors, which make the consumer's own element the host, are deferred to M3, with composition. Until then:
- a component whose root must sit directly inside its parent, such as an
<li>in a consumer's list or a<tr>in a consumer's table, cannot be written for Angular; - fallthrough attributes and slots, which need the host or a helper directive, land in M3.
The decisions behind this page are ADR-0010, ADR-0015 and ADR-0033.