Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable the use of ergonomic rust idioms to construct JSX through macro expansion. #1168

Closed
Ben-PH opened this issue Apr 29, 2020 · 16 comments
Closed
Labels
feature-request A feature request

Comments

@Ben-PH
Copy link

Ben-PH commented Apr 29, 2020

Is your feature request related to a problem? Please describe.
JSX syntax is, in my opinion, not an ideal form to write a markup specification, particularly in the context of a .rs file - it introduces rust rules to a non-rust syntax. For example, rustfmt does not recognize the following for correction:

        html! {    
            <div>
                <button onclick=self.link.callback(|_| Msg::AddOne)>{ "+1" }</button>
                <p>{ self.value }</p>
                </div>
        }

Note: The above example was a result of auto-indentation by my IDE.

Ideally, the above could be expressed in a 'rusty' way.

Describe the solution you'd like
Given that there is a limited set of JSX elements, and a formal specification for it is in the works, the use of macros that expand into JSX text will maintain the JSX expressions to be given to the compiler, while using rust syntax. The above example would become this:

        html! {
            div! { // expands to <div>$content</div>
                button!(onclick = self.link.callback(|_| Msg::AddOne)){ +1 }
                p! { self.value }
            }
        }

or:

        html! {
            div! { // expands to <div>$content</div>
                button!{
                    attribute!(button) {self.link.callback(|_| Msg::AddOne)} { +1 }
                }
                p! { self.value }
            }
        }

Both of these proposals would expand to have the same semantics as the original example

for custom tags, you could add html::custom_tag!(foo) {bar} which would expand to <foo>bar</foo>

Describe alternatives you've considered
Alternatively, an ergonomic means to import JSX at compile-time, such as this:

import_html!("relative/path/to/file.jsx");

and that path+file contains the JSX text inside the html!

Additional comment

This would be a good first issue for this project and involved contributers. The implementation ranges from trivial (simple tags such as

to potentially complex (app defined tags, standard, custom attributes, etc), would require productive collaboration, and careful management in order to introduce it into yew.

@Ben-PH Ben-PH added the feature-request A feature request label Apr 29, 2020
@teymour-aldridge
Copy link
Contributor

teymour-aldridge commented May 2, 2020

I think this would be cool (file based components)!
Perhaps file-based parsing could allow people to contruct components in the same way that Vue.js's file-based components work? [note: I've never used Vue]

@teymour-aldridge
Copy link
Contributor

You might also be interested in taking a look at yew_dsl (still a work in progress).

@TheNeikos
Copy link
Contributor

For example, rustfmt does not recognize the following for correction

This is indeed a limitation of rustfmt, it doesn't format any macro (except a very select few), and AFAIK doesn't have any way to add an 'extension' to it. Thus the issue would persist no matter how the macro looks.

Note: The above example was a result of auto-indentation by my IDE.

This is something you could fix in your IDE usually! You would have to check and see how it evaluates its indentation and change its behavior whenever it encounters an html! macro.

the use of macros that expand into JSX text will maintain the JSX expressions to be given to the compiler, while using rust syntax.

Could you expand on what is 'JSX text'? From looking at the link I didn't quite catch that.

@Ben-PH
Copy link
Author

Ben-PH commented May 12, 2020

This is indeed a limitation of rustfmt, it doesn't format any macro (except a very select few)

This is objectively untrue. There is no difference between macro and non macro syntax as far as the text editor is concerned. For example:

fn main() {
    println!(
        "my IDE auto-indented my cursor to where this string starts"
    );
    // cursor for this line was also auto-indented, exactly the same as for non-macro stuff
}

To be honest, I have no idea where this notion is coming from.

This is something you could fix in your IDE usually!

I personally don't feel there's any merit to expecting users to patch their IDE for it to be compatible with a library. There are some exceptions to this, though I don't see how this could be one of them. At the very least, an ergonomic way to bring in the content of another file so the JSX-like syntax can be edited without being analysed as Rust code.

Could you expand on what is 'JSX text'?

this should explain. Yew describes the content of the html macro as "JSX-like"

@teymour-aldridge
Copy link
Contributor

teymour-aldridge commented May 12, 2020

@Ben-PH it's not important to pass JSX-like expressions to the compiler (because the result of these expressions have to be further expanded by the html! macro). In fact, compile times would (quite probably) decrease if you didn't need to use the html! macro. More nested macros will make compile times longer (I reckon) because of how Rust handles macro expansions.

Something being considered is an approach to building instances of Html using functions (have a look inside yew-dsl if you want to find out more).

This is objectively untrue. There is no difference between macro and non macro syntax as far as the text editor is concerned. For example:

I'm not sure this is the case (I also think that the choice of wording in "objectively untrue" was a little harsh and would be better phrased as a statement of experience, e.g. "in my experience I've found that rustfmt can format macros" or even better "here's a like to rustfmt explaining how they handle macro formatting").

@teymour-aldridge
Copy link
Contributor

teymour-aldridge commented May 12, 2020

The rustfmt readme has some relevant details.

"does not work ... Macro declarations and uses (current status: some macro declarations and uses are formatted)."

@Ben-PH
Copy link
Author

Ben-PH commented May 13, 2020

I'm not sure this is the case

I've never had any trouble with IDE tools recognising macro syntax.
I've never heard of that being the case.
Nor do I see any reason for it to be the case, except maybe for some edge cases that are not really applicable to the general case.

The opposite is true of the JSX-like syntax. Analysing text using rust syntax rules on text that follows a completely different rule-set is broken to a fundamental degree.

Granted, I'm no expert. The fact remains, though, that the behavior of formatting tools working on use of macro and non-macro code is, for all intents and purposes, indistinguishable. Generally, I make a best-effort to reserve my statements similar to how you suggest. I rarely use the word objectively, but I did so in this case as I thought it was self-evident.

rustfmt

  • In a perfect world, it would work on a source file that doesn't parse. Not working on a file that doesn't parse is an occasional inconvenience. It never works on JSX.
  • You don't declare macros as a user of yew
  • Comments are not rust syntex

Overall, the auto-formatting limitations that come from bringing non-rust syntax into a rust file essentially mean that formatting tools are pretty much entirely broken. The limitations you linked to seem to be minor edge cases only. Given that the difference is so profound, I fail to see why this is even being discussed. Am I missing something?

yew-dsl

That's one way to address the problem. I would argue that offloading compile-time work to run-time risks introducing runtime performance regressions. Given that it's related to DOM element generation, such regressions are likely to impact user-perceived performance to a greater degree. Of course, this could end up not being the case and we have a win-win scenario, or other issues at play that ore more important.

@teymour-aldridge
Copy link
Contributor

I agree that doing work at run-time that could be done at compile time seems a waste. Rather than having macros which expand and are then passed into the html! function, my opinion is that it would be better to have macros which expanded directly to instances of Html/VNode rather than to JSX-like syntax which then has to be further processed by the html! macro.

I like the idea of file-based components though!

@Ben-PH
Copy link
Author

Ben-PH commented May 13, 2020

Expanding dirctly into a canonical representation of vdom elements at compile time, with the only run-time concern being the things that cannot be determined statically should be the end goal, I imagine. One possible caveat is binary size, something that is more of an issue when the binary has to be delivered through a series of tubes filled with cats.

Whether or not we go for file-based components, go for a rust-based syntax, JSX-like syntax in a .rs is a show-stopper for me personally.

@teymour-aldridge
Copy link
Contributor

I think there are simillar arguments about JSX in Javascript. At least, there should be the option to not use JSX. My personal feeling is that yew-dsl would need to be replaced with macros instead of functions to make it more efficient.

Passing the result of macro expansions for specific tags to the html! macro is not a very nice solution; it's better if the macro expansions directly produce virtual dom elements.

@teymour-aldridge
Copy link
Contributor

I like the idea of a file-based syntax because I think it makes it easy for us to handle scoped CSS (in a similar way to Vue). I guess it depends on what @jstarry thinks.

@teymour-aldridge
Copy link
Contributor

@Ben-PH are you interested in working on something like this?

@Ben-PH
Copy link
Author

Ben-PH commented Jun 11, 2020

Macro development is way outside my forte. I'm happy to be involved if anything comes u that I can help with, though.

@teymour-aldridge
Copy link
Contributor

I have produced a crate which does something which is not exactly this, but is reasonably close (and enables formatting!)

It's called Malvolio.

@intendednull
Copy link
Contributor

This problem can also be mitigated by keeping logic outside html!. Everything inside it can then be formatted using your editor's HTML formatter, and rust-analyzer will be able to find/rename your variables correctly.

let onclick = self.link.callback(|_| Msg::AddOne);
let value = self.value;
html! {    
    <div>
        <button onclick=onclick>{ "+1" }</button>
        <p>{ value }</p>
    </div>
}

@ranile
Copy link
Member

ranile commented Dec 28, 2021

Closing this since this has been adding to Yew (in form of yew-dsl) and then removed. It is too much of a maintenance burden to keep in Yew. Right now, the best solution is to use yew-vdom-gen (#1999).

Feel free to comment here/re-open this if you have a better solution that you would like to propose.

@ranile ranile closed this as completed Dec 28, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request A feature request
Projects
None yet
Development

No branches or pull requests

5 participants