Replies: 8 comments 4 replies
-
The more I think about this, the more I dislike React 19's new direction: React users are going to face issues with assignment working differently depending on which elements they stick into their templates, leading to confusion. It is not a very good approach considering that the Lit-based/Pota-based alternative described above is, to be honest, easiest and simplest on all sides with no magic and no chance to fail. |
Beta Was this translation helpful? Give feedback.
-
As an example of the migration that would be needed for users with this new attribute/property handling, with Lume we currently write templates like the following to send values to JS properties which is faster than string attributes: return html`
<lume-box
position=${[1, 2, 3]}
align-point=${[1, 2, 3]}
mount-point=${[1, 2, 3]}
></lume-box>
` If the new above spec is implemented, then to achieve the same result the template would need to be converted to this: return html`
<lume-box
.position=${[1, 2, 3]}
.alignPoint=${[1, 2, 3]}
.mountPoint=${[1, 2, 3]}
></lume-box>
` Note that with Lume specifically, due to how it sets up its attributes, the original dash-case template would still work, because it would then be setting the attributes (instead of JS properties) with the string values |
Beta Was this translation helpful? Give feedback.
-
HeuristicsI do not like that this proposal removes the heuristics. It seems to default to attributes always unless you use For example,
The heuristics (this is incomplete)
|
Beta Was this translation helpful? Give feedback.
-
lit style syntaxIm not sure if I like lit-style-syntax, I would prefer to use
|
Beta Was this translation helpful? Give feedback.
-
Pota Thread
It's because pota joins the template with Document.parseHTMLUnsafe(`
<div one=<pota></pota> two=<pota></pota> />
`).body.innerHTML I will have to think if I can handle this, but I sort of doubt it without some regexp and loops gymnastics which I want to avoid. Currently, it works like this
to
then replaces
Then, it knows if an attribute is an interpolated value because its value is "pota", and it knows if a tag is an interpolated value because the tagName is "pota" |
Beta Was this translation helpful? Give feedback.
-
Boolean AttributeAbout the
I think it should just be removed to make it simpler to think about it. The whole idea is falsy/truthy, and "" (empty string) is falsy.
No, don't think it's a good idea, it's confusing. Having more behaviours attached for things that can be done by other means increases complexity and code size. |
Beta Was this translation helpful? Give feedback.
-
Reference TableWe have been working on this Google doc to have a reference table, its mostly complete, but we may be missing something. https://docs.google.com/document/d/13jc0vu2w2hSACeTx6QHc5fz3giFF9sXDzPZ2VjALW-A/edit?usp=sharing |
Beta Was this translation helpful? Give feedback.
-
I want to put it out there even if we removed heuristics for web components the base heuristics for booleans/value etc would stay. We have reasons to treat known attributes with special characteristics, so I imagine any proposal would only apply to general attributes applied to elements.
So what I'm saying is you'd never need to do 1 because 2 is interesting from an SSR standpoint. |
Beta Was this translation helpful? Give feedback.
-
(sparked by thoughts in ryansolid/dom-expressions#351)
Hmmm, HTML is case insensitive, so in HTML
fooBar=""
,FOOBAR=""
, andfOoBaR=""
set the same attribute:But ...
Rethinking template DOM attributes/props mapping?
... because Solid reads the template before mapping it to DOM, it knows ahead of time what the user wrote.
I've been thinking, maybe it may be good to change the rules to be more consistent with the wider ecosystem (outside of Solid), simplify the template implementation a little bit, and give users 100% control of elements without any compromises (regardless if the elements are custom or builtin).
How?
The following is based on Lit's
html
, Pota's JSX andhtml
, and chats with @titoBouzout (author of Pota):foobar
vsFOOBAR
.foobar=""
always set an attribute regardless of the type of element the attribute is on (custom or builtin)foo=""
sets thefoo
attribute on builtin elements by default, otherwise it sets thefoo
JS property on custom elements (web components).foobar=""
andfOoBaR=""
would always set the same attribute (on custom and builtin elements)attr:foobar=""
andattr:fOoBaR=""
would have the same behavior of always setting the same attributeprop:foobar=""
would set a.foobar
JS property, andprop:fOoBaR=""
would set a.fOoBaR
JS propertyhtml
would have Lit-compatible syntax besidesprop:
syntax:.foobar=""
would set the.foobar
JS property, and.fooBAR=""
would set the.fooBAR
JS property.prop:foo
(in JSX othtml
) and.foo
(inhtml
only) would always be needed to set a JS property regardless if custom or builtin elementbool:foobar=
andbool:FOObar=
would handle the same attribute (but with boolean treatment where truthy means the attribute should exist, and falsy means the attribute should be removed) regardless if custom or builtin elementhtml
would have Lit-compatible syntax besidesbool:
syntax:?foobar=
and?fooBAR=
handle the same attributeboo:foo=""
instead ofbool:foo=${}
), it would work like setting a string value without boolean syntax:bool:foobar=".."
would be equivalent tofoobar=".."
, setting the attribute (case insensitive) to a string to emulate that in HTMLfoobar=""
andfoobar="false"
are both "truthy" in most HTML cases"true"
and"false"
making it an alternative type of "boolean attribute", where they set JS properties tofalse
when the string value is"false"
otherwise they set the JS prop totrue
, and when the attribute is omitted they set the JS property totrue
orfalse
depending on its default.foo
,foo=""
,foo="true"
, andfoo="anything"
all set the JS property totrue
;foo="false"
sets the JS prop tofalse
; and omitting the attribute sets the JS prop totrue
orfalse
based on the JS property's default value (in the case of Lume that's the class field initial value).Why?
This would have some benefits:
style
perhaps), etc._
represents a custom element or builtin element, it would not make a difference):<_ foo=... />
-> always set thefoo
attribute<_ prop:foo=... />
-> always set thefoo
JS property<_ bool:foo=... />
-> always add thefoo
attribute if the interpolated value is truthy, otherwise remove the attribute. If non-interpolated, apply the string value to the attribute as ifbool:
was not used (attribute always exists in that case).<_ fooBar=... />
-> always set thefoobar
attribute<_ prop:fooBar=... />
-> always set thefooBar
JS property<_ bool:fooBar=... />
-> always add thefoobar
attribute if the interpolated value is truthy, otherwise remove the attribute. If non-interpolated, apply the string value to the attribute as ifbool:
was not used (attribute always exists in that case).<_ foo-Bar=... />
-> always set thefoo-bar
attribute<_ prop:foo-Bar=... />
-> always set the"foo-Bar"
JS property<_ bool:fooBar=... />
-> always add thefoo-bar
attribute if the interpolated value is truthy, otherwise remove the attribute. If non-interpolated, apply the string value to the attribute as ifbool:
was not used (attribute always exists in that case).attr:
, but it could stay for back compat..foo
and?foo
syntax forhtml
tagged templates as alternatives toprop:foo
andbool:foo
respectively.html
tagged templates with tools likelit-plugin
, @Matsuuu'scustom-elements-language-server
, and more, for IDE type checking and intellisense, and eslint-plugin-lit for linting.@foo
syntax for events inhtml
tagged templates, allowingonfoo=""
to set anonfoo
attribute,.onfoo
to set a JS property, and@foo
to specifically listen forfoo
events.on:
, but without the colon Solid will currently listen to an event instead of setting an attribute.attr:onfoo
andprop:onfoo
allow full control to be possible, where plainonfoo
might still always handle events if back compat is important, or if its just moreconvenient because all the native HTML events already work that way.@
for being explicit about events.@
, then we at least have a doorway to type checking and intellisense fully compatible with what the Lit community has already been building. This is as similar as adopting TypeScript from React JSX, but now also TypeScript from Lit, and opening doors to possibly compilinghtml
templates ahead of time, not only JSX.[foo] { color: cornflowerblue }
foo
JS property, the style will work.foo
JS property, the style won't work!el.current.setAttribute('foo', '')
The idea is full control for the user.
The approach I've outlined, and which Lit takes, is, simply put, the simplest way to do it, with the least possible confusion, with no compromises and full control of Builtin Elements or Custom Elements.
Oh, hah. And this approach would also have a small side effect of increasing performance by eliminating conditional checks like
isCE
. The gain is negligible, but still.I cannot think of any downside regarding the developer experience: full control, simplified implementation and mental model.
Breaking change
This would be breaking of course, so maybe it would need to be shipped with Solid 2.0?
Or, to release something like this in 1.x, there could be a
babel-preset-solid
option to enable the new mode (documenting that if the option is enabled then it will be compiling any 3rd party JSX code with new semantics and thus 3rd party code may break) as well as a newsolid-js/html2
export that has the newhtml
behavior (rename it tosolid-js/html
in 2.0). Or similar.This also requires updating SSR to work with the new setup, and will require considering how to serialize data so that the HTML payload will properly encode how serialized values get set onto elements (string attribute case insensitive, bool attribute case insensitive, or JS property case sensitive).
Examples
I made a Lit playground example showing the various cases:
Lit playground example
In particular, note the behavior of static attributes (f.e.
.foo="123"
). As noted there, that's because those are verbatim actually-valid HTML attributes, and Lit'shtml
template sticks close to HTML standard.However! I would imagine that sometimes people get confused when
.foo="bar"
does not set the JS property to"bar"
.Solid's could deviate from this a little bit so that
?foo
and.foo
always have boolean and JS prop behavior regardless if they are static or interpolated (but this might mean breaking differences with the type checking and intellisense support from Lit's community (maybe not breaking, but maybe static/literal attributes won't be matched with JS props for type checking/intellisense like interpolated attributes will be)).There could possibly still be
attr:
for when someone actually intends to set an attribute prefixed with a character, which would be the rare case, f.e.attr:.foo="123"
andattr:@notEvent="123"
, although maybe not worth supporting that. Maybe being that close to native HTML like Lit is not as ideal, and this small deviation would eliminate any confusion so that.foo
always means set the property,?foo
always means boolean treatment, and@foo
always means hook into the event.Pota's
html
gets a lot closer to the above spec. Here's a Pota example.Pota playground example 1
In the Pota example, we can see that it always treats
.foo=
and?foo=
as for JS properties and boolean attributes, regardless if the expression is static or not, which is less confusing.el.setAttribute('.foo', '...')
andel.setAttribute('?foo', '...')
both throw runtime errors, so it is very unlikely people use these attributes which can only be set as readonly during parse time, for example,logs "123" but the
.foo
attribute cannot not be set later withsetAttribute
.Pota's behavior is different from the above spec in one in that dash-cased
.foo-bar=
attributes will currently set camelCased.fooBar
JS properties. To match with the idea above,.foo-bar=
would instead set the["foo-bar"]
JS property, and someone would instead use.fooBar=
to set the same-name.fooBar
JS property. This modification would be ideal because currently (in both Solid and Pota) if someone actually wants to set afoo-bar
JS property, they can't. The change is easy, removes a string manipulation, and what we see is what we get which is easy to understand.Lastly, noting that Pota is more closely aligned with the above spec and does not treat
.foo="123"
differently from.foo="${'123'}"
(they both set the.foo
JS property, whereas Lit sets an attribute and a JS property respectively), this Pota example,Pota Playground example 2
shows that having two occurrences of
.foo-Bar=
causes an issue (see the note about the "word" value) because they are both treated the same (set the JS property) hence there's a name collision. In Pota we'd probably want to either accept the last same-name attribute (akin to defining the same property in a JavaScript object and the last one wins), throw an error, or at least show a warning, but overall if we remove the duplicate (f.e. rename it to.foo-Bar2
) we get the behavior of the above spec apart from the dash-case difference.Another difference between Pota and the spec and Lit is that Pota requires quotes around all value interpolations. For example in Pota
.foo=${123}
will not work while.foo="${123}"
will work. I'm not sure how complicated this is to fix (Pota's implementation focuses on simplicity), but from a usage perspective,.foo=${}
is more ideal because less characters and symbolically the string value is replaced with an interpolation.Pota's templating is the closest reference implementation of the above spec so far. Note, I didn't show usage of
prop:
orbool:
which should behave the same as.
and?
.2 votes ·
Beta Was this translation helpful? Give feedback.
All reactions