@solidjs/babel-plugin 2.0.0-rc.2 → 2.0.0-rc.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,8 @@ $ rollup -c --bundleConfigAsCjs
3
3
  src/index.ts → index.js...
4
4
  (!) Circular dependencies
5
5
  src/shared/transform.ts -> src/dom/element.ts -> src/shared/transform.ts
6
+ src/shared/transform.ts -> src/dom/element.ts -> src/universal/element.ts -> src/shared/transform.ts
6
7
  src/shared/transform.ts -> src/ssr/element.ts -> src/shared/transform.ts
7
- src/shared/transform.ts -> src/universal/element.ts -> src/shared/transform.ts
8
8
  src/shared/transform.ts -> src/shared/component.ts -> src/shared/transform.ts
9
9
  src/shared/transform.ts -> src/shared/fragment.ts -> src/shared/transform.ts
10
- created index.js in 7.7s
10
+ created index.js in 3.1s
package/CHANGELOG.md CHANGED
@@ -1,3 +1,209 @@
1
1
  # @solidjs/babel-plugin
2
2
 
3
+ ## 2.0.0-rc.20
4
+
5
+ ### Patch Changes
6
+
7
+ - 56683ea: SSR templates write characters above U+00FF in static text and quoted attribute values as numeric character references (`→` becomes `&#8594;`), as do the static attributes baked into a spread element's attribute string. One character above U+00FF in a template made V8 store the whole rendered page as a two-byte string, which roughly doubled the cost of flattening and UTF-8 encoding the response (#4328). Latin-1 characters stay literal. Tag internals, comments, raw-text bodies such as `<script>` and `<style>`, and the children of a spread `<script>` or `<style>` keep their characters, since browsers do not decode references there. Client output is unchanged.
8
+
9
+ ## 2.0.0-rc.19
10
+
11
+ ### Patch Changes
12
+
13
+ - 8cff139: Evaluated negative and non-finite numbers (`c={-1}`, `d={NaN}`) are static again. Since Babel 8 support, `evaluateAndInline` built them with `t.valueToNode`, which gives `-1` / `0 / 0` expressions rather than numeric literals, so they stopped being inlined into templates (SSR emitted `ssrAttribute("c", -1)` instead of `c="-1"`) and diverged from `@solidjs/compiler` (the parity probes' "exotic literal holes"). They are now NumericLiteral nodes again, built directly where @babel/types 8's builder would reject the value; both Babel majors print them as before.
14
+ - 55c7329: Support `@babel/core` 8 alongside 7: the peer range is now `^7.20.12 || ^8.0.0`. SSR no longer reads `Scope#uids` (removed in Babel 8), evaluated negative or non-finite numbers are built with `valueToNode`, `import()` parsed as `ImportExpression` stays dynamic, and TSRX emits the AST shape of the running Babel major (type arguments, type parameter names, `import()`, enums, heritage). TSRX also keeps `import()` options and class method type parameters, which it dropped before.
15
+ - 9e1edbc: A lone dynamic spread on an SSR element (`<label {...props()} />`) now defers behind a thunk like a spread beside other attributes, so it runs after the element's hydration key. A spread that created a memo shifted the element's key on the server only, and hydration missed the element (#4240).
16
+
17
+ ## 2.0.0-rc.18
18
+
19
+ ## 2.0.0-rc.17
20
+
21
+ ### Patch Changes
22
+
23
+ - 3e72783: The removed Solid 1.x attribute forms are now compile errors on native elements in the `dom` and `ssr` generates (#4187). `on:`, `oncapture:`, `attr:`, `bool:`, `use:` and `classList` used to compile silently into literal attributes (`<div use:tip="() => {}">`, `classlist="[object Object]"`). Both compilers now reject them with the same message, which names the 2.0 replacement: a camelCase handler or a `ref` callback for `on:`, a plain attribute for `attr:`, a boolean value for `bool:`, a `ref` directive factory for `use:`, and an object or array `class` for `classList`. Components, the universal generate, `prop:` and the XML namespaces (`xlink:`, `xml:`, `xmlns:`) are unaffected. The check runs only at compile time and adds no runtime code.
24
+ - 6cca8b5: Dev builds now warn with `UNCOMMITTED_INPUT_OVERWRITTEN` (#4186) when a stateful DOM property binding (`value`/`checked` on an input, `value` on a textarea, `value`/`selected` on an option, `muted` on media) is about to re-write the value it wrote last time over an edit the user never committed to the bound signal — e.g. `value={props.name} onChange={commit}` with no draft signal, when another dynamic attribute in the same template changes. Runtime behavior is unchanged: the write still happens, once per element and property the warning names the fix (write the signal from `onInput`, use `defaultValue`/`defaultChecked` for an uncontrolled element, or write a rejected edit back with `e.currentTarget.value = next`).
25
+
26
+ Production output is unchanged byte for byte. In dev DOM output both compilers write stateful properties through a new `setStatefulProperty(el, name, value)` helper instead of the inline `el.name = value`; hydratable output keeps `setProperty`, and `spread`/`assign` check in the runtime. The check lives only in `@solidjs/web`'s dev build; the production entry carries a plain `node[name] = value` stub that no production-compiled module imports, so it tree-shakes away.
27
+
28
+ The "Form inputs" section of the DOM RFC, the cheatsheet and the solid-2.0 skill now say that a change to any other dynamic attribute in the same template re-runs a stateful binding, link the new code, and drop `equals: false` as a way to reject input (it is what the warning reports); write the accepted value back with `e.currentTarget.value = next` instead.
29
+
30
+ - 2ba1c4a: A valueless `prop:` attribute (`<el prop:active />`) now compiles as `prop:active={true}`, matching bare plain attributes, instead of being dropped silently (#4180).
31
+
32
+ ## 2.0.0-rc.16
33
+
34
+ ### Patch Changes
35
+
36
+ - 4a99c84: `sourceNames` gains a `locations` kind (and the plugin a `root` option): component and binding labels carry a `file:line:col` for diagnostics. `createComponent(Home, props, "Home", "src/App.tsx:12:5")` points at the `<Home` opening element (DOM and SSR output); a binding's options gain `loc` after `name` — the attribute's start for an attribute effect (a merged effect points at the first attribute it lists), the hole's `{` for an insert — and a spread passes it after the tag (`spread(el, props, false, undefined, "div", "src/App.tsx:20:12")`). The path is relative to `root` (default: the working directory) with `/` separators; line and column are 1-based, so a dev server links the location straight to the source. Like the other kinds, `locations` follows `dev` when unset, `true` turns every kind on, and the object form picks; a location is only emitted beside its label, and never when Babel has no `filename` for the module.
37
+ - 3f36f77: Server components: binding slots are removed. Client components in slots replace them: place a client component through a `Slot` (`<props.x />`) where the server's markup needs client behavior, or a form `action` for a mutation. Removed with them: the `BindingSlot`, `SlotOutput` and `SlotError` types, the lazy `@solidjs/web/frames/bind` entry and its `"bind"` tier, the `_s:*` position markers and the compiled `ssrClaim` hole, the effect of the `serverComponents` compiler option (`@solidjs/compiler` and `@solidjs/babel-plugin` still accept it, so existing build configs keep working, and ignore it), and the `BINDING_SLOT_POSITION` diagnostic code (`@solidjs/signals`' `DiagnosticCode`, `solid-js`'s `diagnostics.json`). Ordinary slots, their `slot:<id>` ranges and records, and `$key` are unchanged.
38
+ - d0ba31c: SSR evaluates a `ref` expression beside a spread, as it already does without one, so a ref factory's owned setup (`ref={tooltip()}`) runs on the server too and the hydration ids after it line up. The ref itself is still never called on the server (#4081). Under `serverComponents` the same holds outside a server component's render, where a ref expression was never evaluated on either path; inside one the claim evaluates it, once.
39
+
40
+ ## 2.0.0-rc.15
41
+
42
+ ### Patch Changes
43
+
44
+ - 617d195: Emit element claims after the element's initial attributes are applied (#3923), at parity across both compilers.
45
+
46
+ A fully static `a[href]` / `form[action]` is claimed at the end of its creation statements (after static writes and refs). An element with dynamic bindings captures its owner at creation (`var _o$ = _$getOwner()`) and is claimed as the last statement of its binding effect's callback — `_$claimElement(_el$, _o$)` — in both the single-binding and the multi-binding shapes, so the claim observes the applied values on the first run whether that run is synchronous or lands from a held flush. An element carrying a spread emits no compiled claim: the `spread` runtime claims it after the first application.
47
+
48
+ - ca15970: Non-hydratable client builds now remove the `value` attribute when a checkbox or radio input binds a nullish `value` or `defaultValue`, matching the runtime fix from #4009, so a checked box submits `"on"` instead of `""`. Both compilers route these bindings through `setProperty` when the input's static `type` is `checkbox` or `radio`, or when its `type` cannot be known at compile time (an expression, `prop:type`, `attr:type`). Text inputs, inputs without a `type`, and textareas still clear to `""` inline (#2957).
49
+ - 43570f8: Reapply a select's bound value when its options arrive after the value was written.
50
+ - 22f942d: SSR: a router can mark links in server HTML (#3878). Element claims never fire during SSR, so plain anchors arrived without `aria-current="page"` / `data-active` — late on hydrated pages, never on pages that do not hydrate. Both compilers now give every candidate `<a href>` in SSR output one hole after its attributes, `ssrLinkClaim(attrs)`, where a render's **link handler** writes the anchor's link state and `""` otherwise. A static anchor's hole is one eager call over a hoisted attributes object (no allocation per render); a dynamic anchor's joins the element's attribute group and reuses the raw value its `href` hole evaluated. Anchors the compiler can rule out — a static non-empty `target`, a `download`, a `rel` naming `external`, an author-written `aria-current`, an empty href or a non-HTTP scheme — get no hole at all. The hole creates no owner and takes no hydration id. A spread anchor's link attributes are collected by `ssrElement` from its winning sources (an anchor's trailing link attributes stay a source instead of baked tail markup).
51
+
52
+ New server API (`@experimental`): `setLinkClaim(handler)` sets the handler of the render the caller belongs to — found through the owner chain like `getHydrationWriter()`, so concurrent renders never share one — and returns `false` outside a render; `undefined` clears it. The handler, `(attrs) => string`, receives the anchor's link-relevant attributes (`href`, `target`, `rel`, `download`, `link`, `aria-current`, raw values as written) and returns its attribute markup. An author-written `aria-current` always wins. Server components inline in the document render under the page's handler; a server component's own frame stream has none unless its render sets one.
53
+
54
+ Trust marker: the first `setLinkClaim` of a render writes the `links` hydration record once (`_$HY.r.links = 1`, with the shell's hydration script on the document face, a data record on a frame stream). `hasServerLinkState()` (client; `false` on the server) reads it non-destructively, so a link consumer can tell "this page's anchors carry the server's link state" from "no handler ran" and trust the HTML at hydration instead of resolving every anchor's URL at claim. Without a handler the HTML and the hydration ids are byte-for-byte what they were.
55
+
56
+ ## 2.0.0-rc.14
57
+
58
+ ### Patch Changes
59
+
60
+ - 59fb244: SSR `hoistProps` skips TypeScript type positions when collecting captures, so on authored TSX (types parsed, stripped later) a `typeof x` query or a type named like a value no longer captures that value, or forces the literal fallback when it is reassigned (#3828).
61
+ - 3c1f809: Preserve `/* istanbul ignore … */` and `/* c8 ignore … */` block comments written as JSX children on the generated component `children` getter. Line comments are not carried.
62
+ - 98b916d: Fix compiled SSR output that renders `style=""` or `class=""` for `null` and `undefined`. Babel and the native compiler now omit these attributes on elements without a spread.
63
+ - 98d35b9: Fix computed keys in compiled SSR `style` objects (builds without `serverComponents`). A computed key's name got no `;`, so `style={{ color: "red", [k]: v }}` rendered `color:redtop:1px` and the browser dropped both declarations. Hydration keeps the server's inline style, so they stayed dropped. A nullish first value also no longer leaves a leading `;`. Both compilers now pass a multi-entry object to a new `ssrStyleProperties(name, value, …)` helper, which writes the separator only between the entries it writes; a single entry keeps `ssrStyleProperty`.
64
+ - 4f67697: **Breaking:** only `on` followed by an uppercase letter (`onClick`, `onPointerDown`) is an event handler. Lowercase `on*` names (`onclick`, `onmouseover`) are plain attributes everywhere:
65
+ - Both compilers compile `onclick={expr}` like any other attribute (`setAttribute`, reactive when `expr` is dynamic) instead of binding a delegated or native event, and SSR renders it as an escaped attribute instead of dropping it. A leftover 1.x `on:click={fn}` is likewise a plain namespaced attribute (it previously compiled to `addEventListener(":click", fn)`).
66
+ - `@solidjs/web` `spread`/`assign` set lowercase `on*` keys as attributes, the server spread walk renders them, and `useHead` applies lowercase `on*` attributes (camelCase handler names stay skipped). `ssrAttribute` escapes a function value instead of interpolating its source raw.
67
+ - `@solidjs/html` and `@solidjs/h` elements wrap a function passed to a lowercase `on*` in a getter like any other attribute; only `onXxx` and `ref` are exempt.
68
+ - `@solidjs/html` components follow `@solidjs/h`'s rule: every zero-argument function prop, `onXxx` handlers and `ref` included, is a getter, so a component handler must declare its event argument (`onClick=${e => …}`).
69
+ - New dev-only check `LOWERCASE_EVENT_ATTRIBUTE` (added to the `DiagnosticCode` union): warns once per attribute name when a function is set on a lowercase `on*` (or `on:`) attribute, naming the camelCase handler to use.
70
+
71
+ - 7513131: Preserve the source location of captured references in SSR props constructors so source maps point to the authored expression, including dynamic TSRX tags.
72
+
73
+ ## 2.0.0-rc.13
74
+
75
+ ## 2.0.0-rc.12
76
+
77
+ ### Patch Changes
78
+
79
+ - ad1ecc5: `serverComponents` (SSR): keep attribute-slot positions bindable on intrinsic elements. A dynamic `class` or `style` now compiles to a whole-attribute `_$ssrElementAttribute` hole instead of a value inside the template's quotes, so a server component's `class={{ selected: filters.all }}` can mark the class name a client attribute slot owns; `ref`/`on*` positions compile, as before, to one guarded `_$ssrClaim` hole per element, which now emits `_s:on:*`/`_s:ref` markers for slot reads (the `_bnd` behavior-claim marker is gone). A spread element compiles its named `ref`/`on*` (`<button {...rest} onClick={row.go} ref={row.el} />`, wherever they sit relative to the spreads) to the same claim map — `{ click: expr, ref: [a, b] }`, duplicate refs merged, a duplicate handler last-wins as on the template path — keyed by the index of the source each attribute sits before (`<b {...a} onClick={go} {...b}>` → `{ 1: { click: go } }`) and handed to `_$ssrElement` as a seventh argument, a thunk the runtime reads only inside a server component's render, so plain SSR never evaluates the expressions (they used to drop with no marker and no finding); the runtime settles each handler position in source order, as the client's `spread`/`merge()` do (the last source that has the key owns it, `undefined` included). A `ref` beside a lone spread goes through the sources loop under the option (a dynamic spread call is thunked there, as it is beside any other attribute). The event name is always the lowercased `on*` remainder (`onClick` → `click`); `on:`/`oncapture:` are not 2.0 syntaxes and are no longer special-cased. Both compilers share the `attributeSlots` server-components fixture; plain SSR output is unchanged.
80
+ - 077c787: A `<textarea>` with a dynamic `value`/`defaultValue` no longer desyncs hydration ids for the siblings after it (#3691). The server folds the value into the textarea's text content, and that fold was taking the `_$scope` hydration-id reservation a dynamic child hole gets — but the client writes the value as a plain property effect that never allocates an id, so every component after the textarea hydrated one id off and its updates targeted detached DOM. The fold is now treated like the `innerHTML`/`textContent` redirects (#3015): opaque content, no id reservation, in both the Babel plugin and the native compiler.
81
+ - c71486d: DOM output: an element's `ref` now runs after its attributes and spread are applied, and still before its children. A ref's own attribute writes now survive client creation the way they already survived hydration (#3685).
82
+
83
+ ## 2.0.0-rc.11
84
+
85
+ ## 2.0.0-rc.10
86
+
87
+ ### Patch Changes
88
+
89
+ - fd36d37: `sourceNames.bindings`: compiled binding effects are named by what they write. An attribute effect gets `<tag>.<attribute>` as written (`span.textContent`, `div.class:active`, `div.style:color`; a template's merged effect lists all of its bindings), a hole's insert is named for the parent it fills (`div.children`), and a spread passes its tag so the runtime labels its attribute effect `div.spread` and its children insert `div.children`. The names travel as a trailing options argument on `effect`/`insert` (`{ name }`) and a trailing string on `spread`; `@solidjs/web`'s `effect`, `insert`, and `spread` accept them and put them on the render effect nodes, where the dev and observe tiers show them in owner paths, attribution chains, and the Propagation track — a binding effect reads `span.textContent ← count` instead of `effect ← count`. Production ignores the names; output with the option off is unchanged. DOM output only; `sourceNames: true` turns it on with `components`.
90
+ - fd36d37: Rename the compilers' `componentNames` option to `sourceNames`, now `boolean | { components?: boolean }`. `sourceNames: true` (or `{ components: true }`) is what `componentNames: true` was — the tag as written in source as `createComponent`'s third argument, on DOM and SSR output. The option is now the home for every kind of source name the compilers can carry into output for the dev and observe runtimes to label the reactive graph with (binding effects and primitives follow); the object form picks kinds. `@solidjs/compiler` rejects `componentNames` as an unknown option, so a stale `@solidjs/vite-plugin` fails loudly rather than compiling without labels.
91
+ - 45407d1: JSX passed through a prop other than `children` (a layout's `header`, an object of slots, a context value) now hydrates when another id-allocating hole follows it (#3567). Both compilers' hole-scope predicate treated only `props.children` as able to build hydratable content, so a `{props.header}` hole compiled to a transparent insert: the server reserved the scoped sibling's slot at registration and gave the header's elements the next id, while the client allocated in source order, and every key after the slot shifted. The predicate is now a denylist: a dynamic hole takes a scope unless its value is provably a primitive (literals, template literals, unary, binary and update expressions), so property reads, optional calls, the left operand of `||`/`??`, sequence and assignment expressions, and array literals with hydratable entries are all scoped. Ids of elements after such a hole shift by one slot on both sides.
92
+ - 6717d35: `sourceNames` follows `dev` in both JSX compilers. Unset, every kind (`components`, `bindings`) is on when `dev: true` and off otherwise; `true`/`false` sets every kind (`sourceNames: false` opts a dev build out); the object form picks, and each kind it leaves unspecified follows `dev` (previously `false`). Production output (`dev: false`) is byte-identical with the option set or unset. `@solidjs/babel-plugin`'s `PluginConfig.sourceNames` is now optional. Dev builds label components (`createComponent(Home, props, "Home")`) and binding effects (`span.children`) out of the box, so diagnostics and the Performance panel read as source without build-tool configuration.
93
+
94
+ Primitive naming (`createSignal(0, { name: "count" })`) stays with `@solidjs/compiler`'s standalone `transformSourceNames` pass, which the build tool runs on every module independently of the JSX compiler; it is not a JSX-transform kind, and the READMEs and RFC now say so.
95
+
96
+ - 1735074: SSR: a component's props literal with getters compiles to a module-level constructor with shared getters (`hoistProps`, default on) instead of an object literal, which V8 builds in dictionary mode with a closure per getter per instance. Same own keys, order, descriptors and prototype; a props getter is now defined only for a read through its own object — copying its descriptor elsewhere throws (dev names the rule). Sites closing over a reassigned or later-declared binding, `this`, or `arguments` keep the literal. Babel and the native compiler emit the same output.
97
+ - e10a4ba: SSR: attributes written after an element's last spread are markup, not a source. `<li {...rest} class="row" data-id={id}>` compiles to `ssrElement("li", rest, …, _sk$, () => ' class="row"' + ssrElementAttribute("data-id", id))` — statics written as the template path writes them, dynamics through `ssrElementAttribute` (the spread walk's rules for one key), the whole string a literal when every part is static — with a hoisted skip predicate (`var _sk$ = k => k === "class" || k === "data-id"`, one per key set) so the spread's own copies of those keys are never read or emitted. No later source can override a trailing attribute, so the output is unchanged; the thunk runs where the trailing source's getters used to be read, so hydration ids keep their order. What goes away per element is the trailing object literal (with getters, a dictionary-mode object and a closure per getter), its key list, and the precedence check on every key of the spread — an element whose only other source is the spread now serializes from one plain object. Attributes before a spread stay a source, and a source literal with getters (`<li data-id={id} class="row" {...rest}>`) now compiles to the same hoisted constructor as a component's props (`hoistProps`). Spread-static-tail server bench, 500 rows: statics after the spread 1.31×; a dynamic and a static after the spread 1.42× (1.22× from the hoisted source form, 1.17× from the thunk on top); the same attributes before the spread 1.21×. Child properties (`innerHTML`, `textContent`, `children`), a textarea's `value`, reserved namespaces and JSX-valued attributes stay sources.
98
+ - 9adf007: Escape static text on SSR spread elements (#3557)
99
+ - Static text children of a spread element (`<div {...props}>a &lt;b&gt;</div>`) are now HTML-escaped at compile time in both compilers, matching the template path; `script` and `style` children stay raw.
100
+ - A static textarea `value` folded into the template (`<textarea value="a &lt;b&gt;" />`) is escaped in both compilers.
101
+ - The Babel plugin no longer folds a textarea `value` into children on an element that carries a spread, so the value stays a prop and precedence follows JSX source order, matching the native compiler.
102
+ - Static string props on the Babel spread paths are rebuilt from their decoded value, so `<div {...props} title="a &amp; b" />` no longer double-encodes entities.
103
+
104
+ ## 2.0.0-rc.9
105
+
106
+ ### Patch Changes
107
+
108
+ - 8d6de07: Native elements with several spread sources compile to the runtimes' array form instead of a `mergeProps()` call, in DOM and SSR output: `<div id="x" {...a} {...b}>` becomes `spread(el, [{ id: "x" }, a, b], …)` on the client and `ssrElement("div", [{ id: "x" }, a, b], …)` on the server. The runtimes read the sources directly — later sources win per key, only the winning source is read — with no merge proxy to build and walk, and a reactive spread is a plain thunk called inside the tracking scope, so it mints no memo and consumes no hydration id on either side (the hydratable SSR `() => mergeProps(…)` wrapper is gone for the same reason). A lone spread still passes straight through (#3105). Requires `@solidjs/web` with the `spread`/`ssrElement` array forms (#3418, #3419).
109
+ - 6d2bdeb: Move delegated event handlers off the `$$<type>` element key Solid 1 uses.
110
+
111
+ Solid 1 delegates from `document` and fires any `$$click`/`$$input`/… it finds while walking up from the target, so a 1.x runtime on the same page — an older embedded widget, a devtools panel built on 1.x — ran every delegated handler in a 2.x app a second time. Compiled output and the runtime now stamp `_$$<type>` / `_$$<type>Data` instead; neither version can see the other's handlers, in either nesting direction.
112
+
113
+ The key, the `_$SOLID_EVENT_OWNER` mark, and the walk rules are documented in `client.ts` as the delegated-event wire contract shared by every Solid copy on a page. Anything reading `el.$$click` directly must switch to `el._$$click`.
114
+
115
+ - 246eeeb: Emit non-identifier getter keys in compiled props literals as string literals (`get "aria-label"() {}`) instead of computed keys (`get ["aria-label"]() {}`). Same property, but a computed key drops the whole object literal off V8's boilerplate path into per-property runtime definition; on a seven-getter props literal the computed form costs ~45% more to build. Applies to every getter site in both compilers: component props, dynamic element attributes (DOM, SSR, universal).
116
+ - 63560a1: `componentNames` now applies to SSR output. Under the option both compilers keep the `createComponent` call they otherwise inline to `Comp(props)` and pass the source tag name — `createComponent(Comp, props, "Comp")` — so the server runtime's observe/dev `createComponent` labels the owner and a server finding's `ownerPath` reads `<App> › <Page>` like the client's. Without the option (prod builds) SSR output is unchanged. `@solidjs/vite-plugin` already passes the option for its dev and observe postures, so app server builds pick this up with no config change.
117
+
118
+ Fixes `ssrScope` under transparent owners: the virtual hole scope swapped the current owner's id counter, but content inside a hole resolves ids by walking past transparent owners, so with one in between (the server-component scope owner; now the labelled component owner) the hole's content took ids from the enclosing counter and disagreed with the client. The scope now swaps the nearest id-bearing owner.
119
+
120
+ - 350f65f: TSRX: `@for … index/key` items and `@catch` errors pass through to `For` / `Errored` as the accessors Solid hands out; the compilers no longer rewrite reads of those bindings into calls (#3474). Author `item()`, `i()` (under a custom key), and `err()` exactly as in JSX. A destructuring pattern in one of those positions is rejected with a diagnostic, since there is nothing to destructure — the default keyed `@for` item is still a raw value and still destructures. `projectTsrxForTypecheck` emits the same `(item, i) =>` / `(err, reset) =>` arrows `@tsrx/solid` does, so the two typecheck projections now agree.
121
+ - 1643d2a: The universal renderer's `spread()` follows the `@solidjs/web` contract (#3388): `ref` folds into the props effect and is re-applied only when its identity changes (refs run with no owner, so nothing they create is disposed by the fold); children keep their own owned `insert` — that effect owns the child subtree — but a plain object whose `children` is a data property inserts the value with no effect at all. Three reactive nodes become two when children flow through the spread, one when they don't. `spread` also resolves a lone function source inside its own tracking scopes and accepts an array of sources — `spread(node, [a, b], skipChildren)` — the union of their keys with later sources winning, only the winning source read, function sources called inline with no merge and no memo. Both compilers' universal output uses it: a lone spread passes straight through (reactive included, no more `mergeProps(() => …)`), and several sources compile to the array instead of a `mergeProps()` call.
122
+
123
+ ## 2.0.0-rc.8
124
+
125
+ ### Patch Changes
126
+
127
+ - 01ac18c: Compiler `componentNames` option: component owner labels that survive minification. With the flag on, DOM output carries the tag as written in source as a third `createComponent` argument — `<Home />` compiles to `createComponent(Home, props, "Home")`, `<Ui.Button />` to `"Ui.Button"`, `<this.Row />` to `"this.Row"` — and the dev and observe runtimes label the component's owner with it (`<Home>` in diagnostic `ownerPath`s, attribution chains, and the devtools `_component.name`), falling back to `Comp.name` as before. Until now an observe-tier production bundle reported hot scopes and holds under whatever the minifier left of the function name (`<Xt> › <Kn>`), and a `lazy()` or HMR wrapper hid the tag name even in dev. Off by default and byte-identical output when off; SSR (which inlines the call) and universal output never emit it; the production `createComponent` ignores the argument. Both compilers implement it in parity (shared fixtures, cross-mode ratchet). `@solidjs/vite-plugin` enables it for the dev and `observe` postures.
128
+ - 7d985b6: Fix SSR XSS: strings yielded by flow-control memos rendered unescaped
129
+
130
+ `<Show when={s}>{s}</Show>`, `<For>{v => v}</For>`, `<Dynamic component={() => s} />`,
131
+ `<Switch>/<Match>`, boundary fallbacks and any component that returns a string through a
132
+ memo rendered that string raw on the server. The server flow controls return memos for
133
+ hydration-id alignment; `escape()` passed functions through by identity, and the resolver
134
+ appended whatever they later produced without escaping.
135
+
136
+ One rule now: `escape(x)` at a hole covers everything reachable from `x` — strings, array
137
+ items, and what a function yields when the resolver calls it (a deferred-escape wrapper).
138
+ Finished `{ t }` nodes pass through. `Loading` escapes its content the way it already
139
+ escaped its fallback. The compilers stop wrapping fragment / mixed component children in
140
+ `_$escape` (they are values; escaping them too double-escaped through
141
+ `<Comp>{props.children}</Comp>`), and a single-expression fragment at a hole keeps the
142
+ hole's wrap. Live-hole tags ride the wrapper and `$slot` survives the array copy, so
143
+ frames behave as before.
144
+
145
+ - ab4c40c: Object-valued `style` / `class` bindings are read in the TRACKED half of their effect. `style()` and `className()` enumerate their object in the effect's untracked commit phase, so a proxy value — a store sub-object (`style={state.style}`, `class={row.classes}`), merged props, anything arriving through a spread — was identity-reactive only: in-place key mutations never re-applied, and every leaf read tripped `STRICT_READ_UNTRACKED` in dev. Both compilers now wrap the compute value of a non-inline `style={expr}` / `class={expr}` in a new compiler primitive, `readShallow()`, and `spread()` applies it to those two keys as it copies. `readShallow` is an identity passthrough for strings, plain objects and proxy-free arrays (a fresh literal is already the compute's own — the common case pays a `typeof`); a proxy is copied with one `ownKeys` trap (its own trap keeps the key set tracked) plus one tracked read per key; arrays are re-mapped only when an element is a proxy. Inline literals are untouched — they already compile per property. Provably-string expressions (string/template literals, concatenation) and literal objects/arrays skip the wrap at compile time. New Tier-1 bench `style-class-object`: plain-object rows at parity; store-backed rows go from identity-only (and, in dev, ~97 ms per 500 elements of diagnostics) to per-key reactive at ~3.5 ms. Octane svg-dashboard (prod build, store-backed style/attrs through spread): mount at parity, style_spread_pulse −6%, select_toggle −7%.
146
+
147
+ `spread()` shares the same enumeration: its compute half copied the source with `for…in` + `hasOwn`, which on a proxy source (`merge()`/`omit()`, `{...props}` in a component, store records — nearly every spread) is an `ownKeys` trap plus two `getOwnPropertyDescriptor` traps per key, each allocating a descriptor and a getter closure. It now takes the key set from one `Reflect.ownKeys` trap (the trap keeps the key set tracked) and reads each string key once; plain sources use `Object.keys`, the exact own-enumerable set the old loop yielded. New Tier-1 bench `spread-enumerate` (500 elements, 8 keys): `merge(static, reactive)` 376 → 537 ops/s (+43%), store record 253 → 415 ops/s (+64%), plain object at parity.
148
+
149
+ ## 2.0.0-rc.7
150
+
151
+ ### Patch Changes
152
+
153
+ - ead7b1a: Keep hydration IDs aligned when an intrinsic element has a ref and one reactive spread.
154
+ - b3586e8: Validate document-shell templates in the document context (#3259). The `validate` pass round-trips templates through a body-context fragment parse, which strips `<html>`/`<head>`/`<body>` wrappers no matter how well-formed the markup — so once #3099 made validate failures compile errors, a root component owning the document shell failed to compile in plain client mode, and merely importing it (the jsdom component-test configuration) was fatal. Shell-rooted templates now parse as a document and the shell element is compared back — the analogue of the synthetic `<table>` wrap for table partials, in both the Babel plugin and the native compiler. Genuine restructuring (an implied `<head>`, flow content in `<head>`, a `<p>` split in `<body>`) still errors. Since `<template>` parsing flattens shells, actually client-creating one now throws a descriptive dev-mode error from `template()` pointing at `hydrate()` — the failure moved from every import to the one broken act.
155
+ - d601119: Remove the experimental patch channel and patch-mode list driver (always opt-in, never default). Graph-native regions own value delivery and the unified-For design owns list structure, so the channel's parallel delivery machinery is retired: `patch.ts`/`patch-driver.ts` deleted, the compiler-contract exports (`registerPatch`/`registerRowOps`/`registerSlotPatch`/`patchableRaw`, `patchDriver`/`rowProof`/`driveList`) removed, the `patchDriver` compiler option dropped from both compilers, the insert `$ll` seam stripped, and the write-side channel struct dieted to the single written-keys bound (`t.wk`) the core fold/notify paths actually use. Store-family app bundles reclaim up to ~900 B brotli; every measured tier shrinks.
156
+
157
+ ## 2.0.0-rc.6
158
+
159
+ ### Patch Changes
160
+
161
+ - 1e7fa73: Reject authored TSRX lazy destructuring in Solid while retaining deferred patterns generated for keyed loops and catch clauses.
162
+ - bbff5e0: Direct `value`/`checked` (and other stateful DOM property) bindings no longer overwrite pre-hydration user input during the hydration claim pass (#3182). Hydratable compiled output now routes locked DOM properties through `setProperty`, which skips writes on hydrating nodes and carries the `<select value>` microtask and input/textarea nullish special cases.
163
+ - cdbd584: Assign static `<select value>` values through the live DOM property so the matching option is selected consistently with reactive values.
164
+ - 5a1abb3: Compile TSRX loops with an index but no explicit key using Solid's non-keyed callback shape.
165
+ - 82868c6: Support recursive lazy destructuring, lazy arrow parameters, per-read defaults, computed keys, rest views, standalone assignments, accessor-backed keyed-loop and catch patterns, and JavaScript-correct writes and updates across the Babel and native TSRX frontends.
166
+ - 774aad5: Add compile-time scoped styles, CSS sidecar output, style class maps, and style refs to both TSRX frontends.
167
+ - c9c16cb: Add an experimental TSRX syntax frontend to both compilers. `.tsrx` sources (routed by filename with the new `syntax: "auto" | "jsx" | "tsrx"` option) desugar `@if`/`@else`, `@for … @empty`, `@switch`/`@case`, `@try`/`@catch`/`@pending`, `@{}` statement containers, and lazy destructuring (`&{}`/`&[]`) into the shared Solid JSX lowering, producing byte-identical output from both compilers. The Babel plugin loads the optional `@tsrx/core` peer dependency lazily; the native compiler ships the frontend behind the default-on `tsrx` cargo feature (statement containers in expression position are rejected with a structured diagnostic pending upstream oxc-tsrx support).
168
+
169
+ ## 2.0.0-rc.5
170
+
171
+ ### Patch Changes
172
+
173
+ - 320f1f5: Universal text is text (#3127). The DOM and SSR generators splice static
174
+ text into an HTML template that a parser later unescapes, so they escape
175
+ static values and keep JSX entities as written. The universal generator
176
+ hands strings straight to the host — `createTextNode`, `setProp` — with no
177
+ parser downstream, so the escaping rendered literally (`{"<b>"}` showed as
178
+ `&lt;b>`) and entities never decoded (`&lt;` showed as `&lt;`), leaving no
179
+ spelling that produced a literal `<` in static text under a custom
180
+ renderer. Universal-rendered element children now pass static values
181
+ through unescaped and decode JSX entities in text and string attributes,
182
+ matching what component children and fragment text always did. The flag
183
+ rides on the element, not the config, so `generate: "dynamic"` decides per
184
+ renderer. Applied to both compilers; the attribute half closed ten pinned
185
+ cross-mode parity divergences between them. Reported with the fix mapped
186
+ out by @antoinevanwel.
187
+ - 5230666: Fix hydration ids drifting after a reactive lone spread (#3105). A lone spread now passes its accessor straight to `spread()` on the client — no `mergeProps`, no memo, no hydration id — matching the server's existing pass-through fast path. The runtime resolves a function props source inside its own tracking scopes.
188
+ - e27dc29: `validate` now fails the compile instead of warning when a template's markup would be restructured by the browser's HTML parser (#3099). Once the validator fires the emitted positional walk is guaranteed not to match the browser-built DOM (crashed or silently misplaced bindings; desynced hydration under SSR), so warn-and-emit shipped certain breakage with the diagnostic buried in server logs. Errors now point at the offending JSX (code frame in Babel, line:col in the native compiler). `validate: false` remains the opt-out.
189
+
190
+ ## 2.0.0-rc.4
191
+
192
+ ### Patch Changes
193
+
194
+ - 8d249c7: Patch-channel contract hardening from the stage-2 re-audit: ordinary `patchDriver` registrations unbind with their owner (entries no longer leak past unmount); merged transitions move their held-patch stash so no patch strands; the optimistic drain shares the normal drain's per-entry error isolation and boundary routing; accessor-bearing records are excluded at admission (scan-before-trust) and records that acquire accessors demote their patches to tracked effect fallbacks; writable projection arrays emit setter row ops at their fold-commit visibility moment; row-ops/slot registrations resolve chained backings to the ultimate owner; duplicate keys match occurrence-aware instead of first-wins; the production dev-token typo (`_DX_DEV_`) is fixed; `patchDriver: true` normalizes identically in Babel and the native loader, the option is typed in `TransformOptions`, and a `dom-patch` parity tier ratchets patch-mode output across both compilers (currently byte-identical on all fixtures).
195
+ - b534733: Scope-wrap bare function children in hydratable mode. A function child (`<main>{() => <App/>}</main>`, including via the `children` attribute) is a deferred hole at runtime, but it never classified as `dynamic`, so neither generate reserved an id scope for it — its owner ids drifted across async retry passes on the server and desynced from the client (the #2900 hydration-id-parity class). Both compilers now treat syntactic function expressions as scope-eligible alongside dynamic values, emitting `_$scope(...)` in the ssr generate and around the matching insert accessor in the dom generate. The native compiler also unwraps TS casts in the allocate-ids predicate, matching Babel (fixes a scope-emission desync for `{call() as any}` children).
196
+
197
+ ## 2.0.0-rc.3
198
+
199
+ ### Minor Changes
200
+
201
+ - 89a0531: Absorb the 0.50 expressions snapshot into this repo: lift compilers as `@solidjs/babel-plugin` and `@solidjs/compiler`, dump runtimes into `@solidjs/web` / `h` / `html` / `universal`. Origin: ryansolid/dom-expressions@e97e4290 (0.50.0-next.44).
202
+ - 89a0531: Collapse the expressions dump: drop the rxcore seam, flatten runtimes into package `src/`, delete `babel-preset-solid`, and publish compiler natives as `@solidjs/compiler-*`.
203
+
204
+ ### Patch Changes
205
+
206
+ - 47a797e: Drop leftover DOM Expressions loader fallbacks and reframe the compiler and Babel plugin as Solid 2.0 packages. Node `transform()` now defaults `moduleName` to `@solidjs/web` and `builtIns` to the Solid control-flow set, matching `@solidjs/babel-plugin`. SSR leaves a sole component child unescaped (it is a value; the callee's insert sites escape) and only HTML-escapes mixed/fragment children and element holes. Local native builds remain usable when an in-repo platform-package stub has not been populated yet.
207
+ - 0d2810a: Fix Babel and native compiler lowering divergences around nested content, custom-element ownership, static attributes, namespaces, and conditional evaluation order.
208
+
3
209
  The Babel compiler implementation joined the SolidJS 2.0 monorepo at `2.0.0-rc.2` under the temporary `@solidjs/babel-plugin-jsx` name. It adopted this syntax-neutral package name before the integration merged. Earlier releases lived in [DOM Expressions](https://github.com/ryansolid/dom-expressions).
package/README.md CHANGED
@@ -4,6 +4,8 @@ Solid 2.0's Babel compiler integration. Configure this plugin once in a Babel pi
4
4
 
5
5
  The native Oxc compiler — [`@solidjs/compiler`](../compiler) — is the default in `vite-plugin-solid`. Use this plugin when you need a Babel pipeline (or as the JavaScript fallback).
6
6
 
7
+ It runs on `@babel/core` 7 (7.20.12 or later) and 8.
8
+
7
9
  > **Solid 2.0 (Release Candidate).** Pin exact versions.
8
10
 
9
11
  ## Features
@@ -56,9 +58,9 @@ const view = ({ item }) => {
56
58
  _el$5 = _el$3.nextSibling,
57
59
  _el$6 = _el$5.firstChild;
58
60
  _$insert(_el$2, itemId);
59
- _el$4.$$click = e => select(item, e);
61
+ _el$4._$$click = e => select(item, e);
60
62
  _$insert(_el$4, () => item.label);
61
- _el$6.$$click = e => del(item, e);
63
+ _el$6._$$click = e => del(item, e);
62
64
  _$effect(
63
65
  () => selected(),
64
66
  _v$ => _$className(_el$, itemId === _v$ ? "danger" : "")
@@ -86,7 +88,7 @@ Omitted options are the Solid 2.0 defaults that used to live in `babel-preset-so
86
88
  hydratable: true
87
89
  }
88
90
  ]
89
- ]
91
+ ];
90
92
  }
91
93
  ```
92
94
 
@@ -99,6 +101,13 @@ Omitted options are the Solid 2.0 defaults that used to live in `babel-preset-so
99
101
 
100
102
  Runtime module the compiled output imports helpers from. Use the same module for SSR; switch `generate` instead.
101
103
 
104
+ ### syntax
105
+
106
+ - Type: `'auto' | 'jsx' | 'tsrx'`
107
+ - Default: `'auto'`
108
+
109
+ Source syntax frontend. `"auto"` routes `.tsrx` files through the TSRX frontend and everything else through standard JSX; `"tsrx"` forces TSRX for every file; `"jsx"` disables TSRX routing entirely. See [TSRX](#tsrx-experimental).
110
+
102
111
  ### generate
103
112
 
104
113
  - Type: `'dom' | 'ssr' | 'universal' | 'dynamic'`
@@ -113,6 +122,33 @@ Runtime module the compiled output imports helpers from. Use the same module for
113
122
 
114
123
  Emit hydratable markers.
115
124
 
125
+ ### dev
126
+
127
+ - Type: `boolean`
128
+ - Default: `false`
129
+
130
+ Development output. With `hydratable`, emits the hydration walk validation helpers.
131
+
132
+ ### sourceNames
133
+
134
+ - Type: `boolean | { components?: boolean; bindings?: boolean; locations?: boolean }`
135
+ - Default: follows `dev` — every kind on when `dev: true`, off otherwise
136
+
137
+ Names as written in source, carried into the output so the dev and observe runtimes can label the reactive graph — in diagnostic `ownerPath`s, attribution chains, and the Performance panel tracks — even after a minifier renames everything. Unset, the option follows `dev`; `true`/`false` sets every kind (`sourceNames: false` opts a dev build out); the object form picks, and each kind it leaves unspecified follows `dev`. The production runtimes ignore the names, and production output (`dev: false`) is byte-identical whether the option is set or not. `@solidjs/compiler`'s `transform()` takes the same option with the same shape and defaults.
138
+
139
+ - `components`: emit the tag as written as a third `createComponent` argument — `<Home />` compiles to `createComponent(Home, props, "Home")`, `<Ui.Button />` to `"Ui.Button"` — so each component's owner reads `<Home>` even when a `lazy()`/HMR wrapper hides the function. Applies to DOM and SSR output; for SSR the compiler keeps the `createComponent` call it otherwise inlines to `Comp(props)`, so the server runtime labels the owner the same way (prod SSR output, without the option, is unchanged). Universal and dynamic output are unaffected.
140
+ - `bindings`: every compiled binding effect is named by what it writes, as a trailing options argument the dev and observe runtimes read and production ignores. An attribute effect gets `<tag>.<attribute>` as written — `<span textContent={label()} />` compiles to `effect(() => label(), v => …, { name: "span.textContent" })`, `class:active` to `div.class:active`, a `style={{ color: c() }}` property to `div.style:color` — and a template's merged effect lists all of its bindings (`"button.class, span.textContent"`). A hole's insert is named for the parent it fills: `<div>{count()}</div>` compiles to `insert(el, count, undefined, undefined, { name: "div.children" })`; a static child (a component call, a literal) creates no effect and gets no name. A spread passes the tag as its trailing argument (`spread(el, props, false, undefined, "div")`), and the runtime labels its attribute effect `div.spread` and its children insert `div.children`. DOM output only.
141
+ - `locations`: where the JSX is written, beside each label the other two kinds emit, as `file:line:col` — the module path relative to [`root`](#root) with `/` separators, line and column 1-based (the stack-trace convention, so a dev server links `http://localhost:5173/src/App.tsx:18:3` to the right place). A component call gets it as a fourth argument: `<Home />` compiles to `createComponent(Home, props, "Home", "src/App.tsx:12:5")`, the location of `<Home`. A binding gets `loc` after `name` in its options: an attribute effect points at the attribute (`{ name: "div.class", loc: "src/App.tsx:14:7" }`; a merged effect at the first attribute it lists; a property split out of a `style={{…}}` / `class={{…}}` object at that object attribute), a hole's insert at its `{`, and a spread passes it after the tag (`spread(el, props, false, undefined, "div", "src/App.tsx:20:12")`), the first spread attribute's start. A location is only emitted where its label is, and only when Babel knows the module's `filename` (a file outside `root` gets a `../` path). The dev and observe runtimes print it in diagnostic reports; production ignores it.
142
+
143
+ These are the JSX-level kinds — what a JSX compiler can see. Naming the primitives themselves (`createSignal(0, { name: "count" })`, `createCounter.value` inside a composed primitive) is not a JSX-transform feature and this plugin does not do it: it is `@solidjs/compiler`'s standalone `transformSourceNames` pass, which the build tool runs on every module — `.ts`, `.js`, and JSX files alike — independently of which JSX compiler handles the file. `@solidjs/vite-plugin` (its `solid: { sourceNames }` option, solid-vite-plugin #371) wires both: the JSX-level kinds through this option and primitive names through the standalone pass, for its dev and `observe` postures.
144
+
145
+ ### root
146
+
147
+ - Type: `string`
148
+ - Default: the working directory (Babel's `cwd`)
149
+
150
+ Project root that `sourceNames.locations` makes the module path relative to — the build tool passes its project root so the location is a dev-server path. A relative `root` is resolved against the working directory.
151
+
116
152
  ### delegateEvents
117
153
 
118
154
  - Type: `boolean`
@@ -206,12 +242,7 @@ Restrict JSX transformation to files whose `@jsxImportSource` pragma matches.
206
242
 
207
243
  ```js
208
244
  {
209
- plugins: [
210
- [
211
- "@solidjs/babel-plugin",
212
- { requireImportSource: "@solidjs/web" }
213
- ]
214
- ]
245
+ plugins: [["@solidjs/babel-plugin", { requireImportSource: "@solidjs/web" }]];
215
246
  }
216
247
  ```
217
248
 
@@ -227,12 +258,62 @@ const template = <div>Hello</div>;
227
258
 
228
259
  Inline style attributes in templates when the value is a string or `Record<string, string>`. Disable for strong CSP configurations; styles are then set at runtime.
229
260
 
230
- ### serverComponents
261
+ ### hoistProps
231
262
 
232
263
  - Type: `boolean`
233
- - Default: `false`
264
+ - Default: `true`
265
+
266
+ SSR-only: a component's props literal with getters compiles to a module-level constructor whose getters are shared across instances, instead of an object literal (which V8 builds in dictionary mode, allocating a closure per getter per instance):
267
+
268
+ ```js
269
+ var _m$ = Symbol();
270
+ var _d$ = {
271
+ get() {
272
+ const props = this[_m$];
273
+ return props.label;
274
+ },
275
+ enumerable: true,
276
+ configurable: true
277
+ };
278
+ function _P$(_p, _p2) {
279
+ this[_m$] = _p;
280
+ this.as = _p2;
281
+ Object.defineProperty(this, "label", _d$);
282
+ }
283
+ _P$.prototype = Object.prototype;
284
+ Comp(new _P$(props, "a"));
285
+ ```
286
+
287
+ The instance has the same own keys, order and descriptors as the literal and `Object.prototype` as its prototype; `Object.keys`, spread, `hasOwn` and `isStatic()` answer as before. One contract follows: a props getter is defined only for a read through its own object (`props.x`, spread, `merge()`/`omit()`). Copying its property descriptor onto another object and reading it there throws — define a getter that reads through the source instead. Sites whose getters close over a binding that is reassigned, declared after the site, or `this`/`arguments` keep the literal. `false` keeps the literal everywhere.
288
+
289
+ ## TSRX (experimental)
290
+
291
+ TSRX (TypeScript Render Extensions) is a syntax for declarative UI. `.tsrx` sources desugar to the same Solid JSX this plugin already compiles: `@if`/`@else`, `@for … @empty`, `@switch`/`@case`, and `@try`/`@catch`/`@pending` lower to the corresponding control-flow components (`Show`, `For`, `Switch`/`Match`, `Errored`, `Loading`), and `@{}` statement containers mix setup statements with rendered elements.
292
+
293
+ ```tsrx
294
+ export function TodoList({ items }) @{
295
+ <ul>
296
+ <style>
297
+ li { padding-block: 0.25rem; }
298
+ </style>
299
+ @for (const item of items; index i; key item.id) {
300
+ <li>{i + 1}. {item.text}</li>
301
+ } @empty {
302
+ <li>No todos</li>
303
+ }
304
+ </ul>
305
+ }
306
+ ```
307
+
308
+ Requirements and behavior:
234
309
 
235
- SSR-only: emit behavior-claim (`_bnd`) markers for `ref` / `on*` on intrinsic elements.
310
+ - Compiling `.tsrx` sources requires the optional peer dependency `@tsrx/core` and Node.js >= 22.12. It loads lazily on first TSRX routing, so plain JSX users never pay for it.
311
+ - Routing is filename-based by default (`syntax: "auto"`), so Babel must receive a `filename`.
312
+ - Desugared constructs rely on the `builtIns` auto-imports, so those components must exist in `moduleName`.
313
+ - Scoped `<style>` blocks are removed at compile time, matching native and dynamic elements receive a `tsrx-<hash>` class, and the scoped/pruned stylesheet is returned as `result.metadata.css` with `result.metadata.cssHash`. Style expressions produce class-map objects, `<style ref={styles}>` initializes a class map, and `:global(...)` opts selectors out of scoping. The plugin emits no runtime style helper; a bundler integration must emit the CSS metadata.
314
+ - Solid rejects authored TSRX lazy destructuring (`&{ … }` / `&[ … ]`). Keep accessor calls and reactive property reads explicit in Solid source.
315
+ - Control-flow bindings pass through exactly as authored. `@for … index` / `@for … key` hand the callback the item as an accessor (and the index as an accessor under a custom key), and `@catch (err, reset)` receives Solid's `ErrorAccessor` — write `item()`, `i()`, `err()` as in JSX. Destructuring a binding in one of those positions is rejected with a diagnostic; the default keyed `@for` item is a raw value and destructures as usual.
316
+ - The native compiler ([`@solidjs/compiler`](../compiler)) compiles the same sources to byte-identical output.
236
317
 
237
318
  ## Special Binding
238
319
 
@@ -251,7 +332,7 @@ const Parent = () => {
251
332
 
252
333
  ### on(eventName)
253
334
 
254
- CamelCase attributes such as `onClick` are event handlers. The compiler delegates events that bubble or compose, otherwise it uses Level 1 `on_____` properties.
335
+ CamelCase attributes such as `onClick` are event handlers. The compiler delegates events that bubble or compose, otherwise it uses Level 1 `on_____` properties. Only `on` followed by an uppercase letter is an event: lowercase names such as `onclick` are plain attributes, compiled (and server-rendered) like any other attribute.
255
336
 
256
337
  Pass an array to bind a value: the second item is the first argument to the handler, the event is second.
257
338