Closed texastoland closed 2 months ago
Yes, we've discussed something similar in the #jsx
channel on Discord. Thanks for putting this proposal together though!
One exception with your proposed example: I don't think "some link title: " item.title
should work. (This looks like a potential function call...?) More intuitive to me would be "some link title: " + item.title
or `some link title: ${item.title}`
.
But I think the bigger issue is how to have multiple code children. Digging up old posts, here's an example I put together with a proposal for treating each top-level line as its own braced block:
<div>
user := getUser()
if user?
{name} = user
<h1> `Welcome ${name}!`
<h2>Posts</h2>
for post of posts
<div .post> post.jsx()
↓↓↓
let user
<div>
{user = getUser(), void 0}
{user != null ?
{name} = user,
<h1> {`Welcome ${user.name}!`} </h1>
: void 0
}
<h2>Posts</h2>
{posts.map(post =>
<div class="post"> {post.jsx()}
}
</div>
Basically the rule is "wrap each non-JSX child of JSX with braces". If you wanted multiple lines to correspond to a single braced child, you could wrap in do
. The tricky work here is hoisting the let user
out of the user := getUser()
code, which should maybe be the second thing to do. But even that should be doable; we do similar things with if user := getUser()
today.
Another issue is whether we want same-line children to behave differently from different-line children. For example, you could imagine <h1>Hello
produces a string when Hello
is on the same line as <h1>
. Or it could be a flag...
Another proposal I see is to automatically wrap strings and tags in a fragment, like so:
<span>
if message.deleted
" "
<span .label.label-danger>Deleted
↓↓↓
<span>
{message.deleted ?
<>
{" "}
<span .label.label-danger>Deleted</span>
</>
: void 0}
</span>
Or maybe " "
and <span .label.label-danger>Deleted
need to be on the same line? But then it's harder to add a trailing space.
Let us know what you think about these further ideas/extensions!
My original XY problem is I want to map
(or for
🤯)/if
/switch
without pairing (or remembering) braces (curlies or round). Any solution that accomplishes that would be a substantial DX improvement over JSX 🍾
I reread your feedback 🤓
I don't think
"some link title: " item.title
should work.
Agreed 👍🏼
Basically the rule is "wrap each non-JSX child of JSX with braces".
Your example makes sense!
The tricky work here is hoisting ...
Example (I didn't follow)?
Let us know what you think about these further ideas/extensions!
Treating raw strings like Hello
as identifiers and quoted strings as … well strings solves ambiguities/spacing. It's how ReScript currently works based on multiple prior JSX proposals (including 1 by Seb).
<div>
user := getUser()
if user?
{name} = user
<h1> `Welcome ${name}!`
<h2> "Posts"
for post of posts
<div .post> post.jsx()
For context the h2
in ReScript would almost be:
<h2> "Posts" </h2>
Except it must be wrapped to satisfy types which requires braces again:
<h2> {"Posts"->React.string} </h2>
I have a new related proposal, which is to use >
to enter an indented code block — essentially a new way to go from XML mode to Civet mode, like {
but that doesn't need a closing }
. This is similar to the proposal "Alternatively signal code children with a marker like do
" in the root post, but with >
in place of do
. The motivations for >
are:
>
is invalid in JSX text, so this isn't ambiguous. (The same reason we can automatically wrap arrow functions in braces.)>
is like the second half of an arrow, which denotes code.>
is like Markdown's quoting syntax (which quotes until the next dedent or blank line).>
intuitively means "do this thing to the right". FWIW, I actually came up with it first when thinking what a natural character. It's used in a lot of prompts etc.Examples:
Component := (items: Item[]) =>
<ul>> // reminds me of Pug's trailing dot
for item of items
<li>>
if item.type === "link"
<a href=item.url>some link title: >item.title
else
<p>>item.content
<div>
> if props.warning
<div .warning>
>props.warning
> if loggedIn()
<Inbox>
<Logout>
> else // special case to allow an else at same indentation level
<Login>
↓↓↓
<div>
{props.warning ?
<div class="warning">
{props.warning}
</div> : void 0
}
{loggedIn() ?
<>
<Inbox/>
<Logout/>
</>
: <Login/>
}
</div>
<label>
Comment
>if count > 1
's'
:
↓↓↓
<label>
Comment
{count > 1 ? 's' : void 0}
:
</label>
We could allow >
for property values too. This looks a little weird, because >
also means "end the tag", but it's not ambiguous after an =
:
<Show when=>
data = getData()
data.has data.stuff
fallback=>
<div>Nothing to see here.
>
Welcome!
↓↓↓
<Show when={
data = getData(),
data.has(data.stuff)
} fallback={
<div>Nothing to see here.</div>
}>
Welcome!
</Show>
I think I would personally prefer explicit blocks like this, as opposed to implicitly treating all children as code, because it makes it easy to be explicit about when you want multiple code blocks (as above) vs. when you want one code block that requires multiple lines:
>do
data := getData() |> formatData
data.sort()
for item of data
<li>>item.title
The other advantage is that it's fully backward compatible, so we can do it without a flag like "civet jsxCode"
. That said, I think there's room for both approaches; I'm just more clear on what the >
feature looks like.
In "civet jsxCode"
mode, there's a question of whether the children block should be treated as one code block or as multiple. Above I proposed that each top-level statement become its own block, which is usually what I want, but I admit this isn't particularly intuitive. (You could always wrap in do
to combine statements.) Alternatively, Daniel seems to be leaning toward a single code block, which could return an array if you want to return multiple results. (This works for React but not fine-grained systems like Solid.) If we go this way, I think a nice add-on would be for top-level bulleted arrays (#803) to automatically be treated like multiple code blocks:
<div>
. if props.warning
<div .warning>
. 'Warning: '
. props.warning
. if loggedIn()
<Inbox>
<Logout>
else
<Login>
↓↓↓
<div>
{props.warning ?
<div class="warning">
{'Warning: '}
{props.warning}
</div> : void 0
}
{loggedIn() ?
<>
<Inbox/>
<Logout/>
</>
: <Login/>
}
</div>
My 2¢ with a grain of salt because I'm not actively using Civet 🙏🏼
I like the concept of entering curly mode via some marker. But >>
and >=
look confusing to me. I think as programmers we've formed an intuition they're always balanced like quotes or brackets. I'm sure there's a reason do
wouldn't work here but couldn't dig it up in the thread?
Alternatively (brainstorming inspired by Pug/Vue hybrid) :=
might be rare enough to not conflict with user code:
<div>:=
if props.warning <div .warning>:= props.warning
if loggedIn()
<Inbox>
<Logout>
else <Login>
<Show
when:=
data = getData()
data.has data.stuff
fallback:= <div> Nothing to see here.
> Welcome!
when:=
looks quite nice for blocks of code assigned to attributes. On the other hand, I find :=
a little weird for children within tags. I'm also not sure it would work well for multiple code-block children; I think your first example would have to be:
<div>
:= if props.warning then <div .warning>:= props.warning
:= if loggedIn()
<Inbox>
<Logout>
else <Login>
(otherwise the code block would be treated as a single code block with one return value, whereas you need multiple here)
Note that :=
has a specific meaning in Civet, which is const
definition. I'm not sure it's the right connotation here, but maybe we can come up with some different notation that everyone would enjoy...
Unrelated, I had another idea for "civet jsxCode"
mode (where children are code by default), which is to use ---
separators between code blocks. Thus:
<div>
if props.warning
<div .warning>
'Warning: '
---
props.warning
---
if loggedIn()
<Inbox>
<Logout>
else
<Login>
Currently ---
has no meaning in code, and it's somewhat natural given YAML/Markdown separators. (I used one earlier on in this message!) Conversely, ---
does take a lot of space. It also might be easy to forget one (but >
and other notations have the same issue).
By the way, back to an unambiguous backward-compatible notation, an option other than >
would be &
, which in JSX can only be used in special ways (character codes, in particular with a semicolon within the same word). Does this look any better?
<Show when=&
data = getData()
data.has data.stuff
fallback=&
<div>Nothing to see here.
>
Welcome!
<div>
& if props.warning
<div .warning>
&props.warning
& if loggedIn()
<Inbox>
<Logout>
else
<Login>
I like that it's a different character from <>
of tags.
The obvious connection is to Civet's &
shorthand function blocks. On the one hand, these are code blocks, so it seems like a reasonable similarity. On the other hand, this use of &
behaves very differently; &
currently refers to the function parameter, which it isn't doing here.
Some other ideas for other symbols:
&:
(backward compatible too, but not sure it's very natural i.e. it's ugly):
(Pythonic, not backward compatible)=
(Excel vibes! not backward compatible).
or •
(as above mentioned, naturally extends bullet notation #803, also like the opposite of Pug's .
notation; .
is not backward compatible)I'm wondering whether we could have two options for breaking backward compatibility:
"civet jsxBullets"
to enable notation like say .
to enter code mode, at the cost of .
not being writable in JSX text without quoting (probably .
is nicest; otherwise, {'.'}
or .'.'
)."civet jsxCode"
to make children code by default, so you use strings for text content, and multiple code children can be specified via multiple bullets (.
) and possibly ---
separators.I think do
is most compelling to me. ---
takes second though. A lot of your examples add a character to the beginning of each line. My intuition would be that a character after a tag would apply to the entire nested scope. Re. backwards compatibility you aren't 1.0 yet right? Not sure if you could provide a code mod for yourself and other users. Anyway keep up the interesting work!
I haven’t fully delved into the problems of this issue, but I think that it is still necessary to somehow make the following syntax possible:
<div>
if items
<h1> "List of items:"
<ul> for item in items
<li> <span> item
else
<span> "No items found"
or as the author of the issue suggested:
const Component = (items: Item[]) =>
<ul>
for item of items
<li>
if item.type === "link"
<a href=item.url>some link title: {item.title}
else
<p>{item.content}
Here's how in Imba language.
Of course, it’s not jsx
, but this syntax is much nicer than prefixes of different characters (. , & = := ; -) in each line of an expression.
Is it very difficult to implement this?
<div>
user := getUser()
if user?
{name} = user
<h1> `Welcome ${name}!`
<h2>Posts</h2>
for post of posts
<div .post> post.jsx()
↓↓↓
let user
<div>
{user = getUser(), void 0}
{user != null ?
{name} = user,
<h1> {`Welcome ${user.name}!`} </h1>
: void 0
}
<h2>Posts</h2>
{posts.map(post =>
<div class="post"> {post.jsx()}
}
</div>
If I understand correctly, this is the same as my original proposal. Yes, I think this should be an option, via a directive of "civet jsxCode"
or "civet jsxCodeMulti"
or something (to distinguish between wanting one code block child vs. multiple code block children).
Yeah, even if it is an additional option like jsxCode
, it will be cool.
My main desire is to avoid of any characters(.
,
&
:=
;
etc.) at the beginning of each line.
like this:
<div>
& if props.warning
<div .warning>
&props.warning
& if loggedIn()
<Inbox>
<Logout>
else
<Login>
or this:
<div>
if props.warning
<div .warning>
'Warning: '
---
props.warning
---
if loggedIn()
<Inbox>
<Logout>
else
<Login>
<div>
. if props.warning
<div .warning>
. 'Warning: '
. props.warning
. if loggedIn()
<Inbox>
<Logout>
else
<Login>
Anyway, thanks for your work. I really like civet 🙂
Here's how in Imba language
FWIW Imba is another Coffee fork I quite enjoy for toy projects that probably influenced my expectations how Civet would behave (as well as ReScript mentioned in the description).
Hi all! An idea..
const Component = (items: Item[]) =>
<ul>
{for item of items}
<li>
{if item.type === "link"}
<a href=item.url>some link title: {item.title}
{else}
<p>{item.content}
A new syntactic construct is introduced: JSX {} with indented children. It treats the children as if it was a <></> fragment indented into the contents of {}. But if an indented {} contains a continuation of a statement (else
, case
/when
, patterns in switch
, while
after do
), it is treated as part of that statement.
Other examples:
<ul>
{if !items.length}
No items.
{else items.map (item) =>}
<li>{item.text}
<ResizeObserver>
{({width, height}) =>}
{width}x{height}
<span>
{&.toLowerCase()}
{name}
<div>
{switch value}
{{type: "user"}}
{value.name}
{else}
Invalid type
I ran into a confusing DX issue experimenting with JSX in the playground.
Working example:
Despite being a React instructor (but also CoffeeScript in the past) I intuitively expected Civet to work without braces.
Broken example:
I think code would be a better default for all JSX nodes not only https://civet.dev/cheatsheet#function-children as in:
JSXText
)Proposed example:
Alternatively signal code children with a marker like
do
: