Closed lorefnon closed 1 year ago
I agree 100% (also a Solid fan here!). We had some discussions about this in the Discord, and I was planning to take a stab at implementing it (as Daniel doesn't use JSX himself).
I think the right way to do this is to support both syntaxes, via a parser flag in the first line (the existing "civet ..."
directive). That way we can still offer CoffeeScript compatibility. For example:
"civet coffeeCompat"
- closing tags, not indentation based"civet coffeeCompat indentJSX"
- CoffeeScript but with indentation JSX"civet closeJSX"
- Civet as it is now, requiring closing tags@STRd6 I haven't looked at how JSX is handled, but I assume there's significant support in the lexer. Will it be feasible to support both modes?
It should be possible to support both, and it may even be possible to support optional closing tags by default. With the caching implemented in the parser there is much less cost to distant lookaheads. Since optional closing tags is a superset of explicit closing tags then it could make a very compatible default that people could opt into by usage. Perhaps the only necessary directive would be "civet closeJSX"
to opt into mandatory explicit JSX closing.
@edemaine take a look at the NestedBlockStatements
rule for an example of how the parser currently handles nesting. You can add a production to JSXElement
that uses nesting and inserts a synthetic closing tag (similar to how it checks for mismatched tags). Let me know if you have any other questions, thanks!
So the indentation based syntax in imba has a few more nuances other than optional closing tag. I am not suggesting we adopt them all, but one thing of interest is that because strings need to be explicitly quoted, we can avoid the need to wrap expressions in braces as in JSX.
This enables us to write code like:
<div.tiles> for tile,i in game.tiles
<Tile data=game nr=i @click=game.place(i)> tile
which is more succinct compared to:
<div.tiles>{for tile,i in game.tiles
<Tile data={game} nr={i} @click={game.place(i)}> {tile}}
@lorefnon I agree about removing (or optional) braces for attribute values but for element content non-braced characters would collide with text children.
Yes, unless text children in indented jsx mode have to be always quoted eg.
<div>"Foo"
Text content within jsx is increasingly uncommon in most apps that support i18n
@lorefnon I'm definitely open to exploring it. I don't use JSX too much myself but I do see the value in Civet providing a much more concise, mostly compatible JSX that improves the common cases and supports best practices (i18n). Worst case we can add a directive like "civet jsxCompat"
for people migrating from existing JSX codebases.
<div> "hi" if i > 2
---
<div>{i > 2?"hi": void 0}</div>
So far seems like it would be 👍
It'd be helpful to pull apart the various features of imba to see what makes sense here.
I think we all agree this is good, especially if we can also still support closing tags.
.className
I'm a big fan of Pug so I like the idea of div.foo
as shorthand for <div class="foo">
. However, I don't think it's a good idea in JSX context, because <div.foo>
already has a meaning. For example, Motion One for Solid uses <Motion.div>
to dereference the div
property of the Motion
object.
Maybe <div .foo>
could work? But this looks less like a CSS selector...
I'm a little more hesitant on this one, as it makes the notation not feel like HTML anymore. JSX has such clear semantics that braces mean JS content, whether they're in attributes or children. JSX also has the nice feature that you can copy/paste HTML and you're generally good (especially with Solid).
On the other hand, I can see preferring this. Maybe it makes sense to offer both options via a civet
flag?
Also, I don't think I understand how imba actually treats children. They're not just JS expressions. There's also the css
thing (it's clearly not a function), and multiple tag children can be "returned" without having to wrap in <>...</>
as you do in React or Solid. I'm not sure how this would be achieved in Civet. An option for implicitly wrapping multiple consecutive tags into a fragment would be really convenient (can't tell you how many times I've forgotten to do this in CoffeeScript).
Yes, I wasn't suggesting adopting all parts of imba template syntax. It is very feature rich, has evolved over quite a long time, and some of it (eg. scoped css, slots etc.) are closely tied to their implementation and will not translate well to arbitrary jsx providers.
I was primarily highlighting the expression support because needing to wrap code in { }
looks a bit weird in an indented language which otherwise doesn't use braces as block delimiter.
Do we really need JSX in Civet? Civet is expressive enough for markup I think. Previously we also have https://github.com/mauricemach/coffeekup
Do we really need JSX in Civet?
Yes. There are many frameworks that work much better with JSX. Big examples are React (the most popular front-end framework), Preact, and Solid. JSX is also used in many other smaller projects, for example my own SVG Tiler (JSX is simply the best way to express programmatic SVG).
Omitting JSX would be limiting Civet to a much smaller audience. JSX is also a key feature of CoffeeScript, so it makes sense for compatibility as well.
Just to add to the above, unlike React where jsx always transforms to a plain function call, frameworks like solid, qwik have different and more involved compile time support for jsx - if we don't support jsx, to be able to consume these libraries in a civet we will have to do one of the following:
But if we support jsx, civet can just rely on whatever jsx specific tooling these libraries maintain
Since optional closing tags is a superset of explicit closing tags then it could make a very compatible default that people could opt into by usage.
I don't think this is right, because entering JSX normally (in CoffeeScript) means "ignore all indentation until we re-enter code mode via {
". In testing #25, I finally found an example where this actually matters:
return
<div>
foo bar
</div>
In CoffeeScript, this is a <div>
element with some text. If we allow <div>
to become <div/>
automatically when it has nothing intended inside it, then we end up turning foo bar
into the function call foo(bar)
instead of treating it like text.
So I think a toggle may be useful, for backward compatibility. But hopefully this kind of use is pretty rare (text is normally intended within the tag) so we can have indentation JSX on by default.
Actually, the </div>
in that example forces it to parse correctly... I was testing wrong. So maybe there isn't actually a bad case?
Indentation-based JSX is now live in 0.4.28! (Currently, co-existing with explicitly closed tags.)
Examples (from solidjs.com):
https://playground.solidjs.com/
function Counter()
[count, setCount] := createSignal 1
increment := -> setCount count() + 1
<button type="button" onClick={increment}>
{count()}
https://www.solidjs.com/tutorial/introduction_jsx
return
<>
<div>
Hello {name}!
{svg}
https://www.solidjs.com/tutorial/flow_for
<For each={cats()}>{(cat, i) =>
<li>
<a target="_blank" href={`https://www.youtube.com/watch?v=${cat.id}`}>
{i() + 1}: {cat.name}
}
With the other imba suggestions I could see supporting (with a compiler flag):
<For each={cats()}>
(cat, i) =>
<li>
<a target="_blank" href={`https://www.youtube.com/watch?v=${cat.id}`}>
`${i() + 1}: ${cat.name}`
Maybe I'll try that next.
It might also be worth exploring optional braces for attributes:
<For each=cats()>
(cat, i) =>
<li>
<a target="_blank" href=`https://www.youtube.com/watch?v=${cat.id}`>
`${i() + 1}: ${cat.name}`
Great! Thanks a lot for working on this.
For those tracking this GitHub issue:
It might also be worth exploring optional braces for attributes:
Now supported in Civet v0.5.0! Any attribute values with no "unwrapped" whitespace should now work without braces. For example: cats()?.names[i]
or (=> console.log 'hello world')
.
Also added is computed property names like [propName()]=propValue()
. (This compiles to {...{[propName()]: propValue()}}
.)
And previously added is the shorthand {foo}
for foo={foo}
. This works more generally with any braced object literal, including getters and setters.
Resolved in: https://github.com/DanielXMoore/Civet/pull/25
@STRd6 @edemaine Are we still open to a language level flag to skip braces for embedded code (basically https://github.com/DanielXMoore/Civet/issues/20#issuecomment-1341487100) ?
@lorefnon I'm open to it, at least as an optional flag. It might make sense to open a new issue about only that.
I also plan to work on it, but I'm doing lower-hanging JSX fruit first. 🙂
Hello, thanks a lot for creating Civet - This looks great. I spent some time playing around with it yesterday and loved the DX.
Since this language is quite early, I was wondering if we could eliminate the need for closing xml tags in jsx, and adopt an indentation based syntax similar to imba. I think this will make it more coherent with the rest of the language as well.
Imba is tied to its own dom/rendering impl. where as I am trying to use civet with solidjs which has its own jsx preprocessor.