Allow JSX lines to be recombined#1831
Conversation
|
I'm happy with the direction. The only thing I can think that could go weirdly is people writing paragraphs separated by \n\n, but in practice, they would have to add a or something around. So I think it's a non-issue in practice. |
|
Btw, i'm going to do a big release on Friday, so if you want it in, would be awesome to ship this today or tomorrow :) |
|
I'd love to be able to get this change in for Friday, but I'm off on holiday for the next two days so it will need to wait! That also gives us time to gather more feedback, as I know @rattrayalex had some ideas about recombining lines when I started work on my initial PR for the |
|
Sounds good! I just merged the other one :) Enjoy your vacation! |
| {this.state.bar.qux} | ||
| </div> | ||
| ); | ||
| return <div>{this.state.bar.qux}</div>; |
There was a problem hiding this comment.
I don't like these changes, I found it more readable before
There was a problem hiding this comment.
@karl is there a way to prevent these kinds of changes? or are they intentional?
There was a problem hiding this comment.
This was intentional, in our codebase at Geckoboard we generally put single expressions in a tag on a single line (if they fit).
I'it's easy to tweak this to never inline if an element contains expressions.
There was a problem hiding this comment.
I'll test this out on some open source projects and see which approach gives less changes. Although that doesn't have to be the deciding factor!
There was a problem hiding this comment.
I ran both options (break if an element contains an expression vs. break if an element contains more than one expression) over the Geckoboard codebase to see how they compared.
I noticed that always breaking on expressions caused some less than ideal layout in a few cases:
You currently have <strong>{dashboardStr}</strong> and <strong>{userStr}</strong>
becomes:
You currently have{' '}
<strong>
{dashboardStr}
</strong>{' '}
and{' '}
<strong>
{userStr}
</strong>
I'm not sure yet how we could avoid the above output will still aiming to break elements that contain a single expression. Any ideas greatly appreciated!
There was a problem hiding this comment.
I've run both variations over the following open source projects:
- https://github.com/andrewngu/sound-redux
- https://github.com/WebbyLab/itsquiz-wall
- https://github.com/echenley/react-news/
Allowing elements with a single expression to be inlined generally resulted in less changes than always expanding.
| <HelloLocal /> | ||
| </div> | ||
| ); | ||
| return <div><Hello /><HelloLocal /></div>; |
| Welcome to the <strong>Universal React Starter-kyt</strong>. | ||
| This starter kyt should serve as the base for an advanced, | ||
| server-rendered React app. | ||
| Welcome to the <strong>Universal React Starter-kyt</strong>. This starter |
There was a problem hiding this comment.
stuff like this is perfect though, IMO 🙂
| foo | ||
| {" "} | ||
| <span barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar /> | ||
| {" "}<span |
There was a problem hiding this comment.
here it would be better to have the {" "} after foo, not before <span>. Right?
There was a problem hiding this comment.
I'm interested in your thoughts on this. Based on this issue (#1582) I went with having {" "} at the start of lines. But I don't have strong feeling either way!
There was a problem hiding this comment.
I don't like it because messes up the alignment of opening and closing tags.
There was a problem hiding this comment.
I have a PR that switches {" "} positioning around so they now appear at the end of lines rather than the beginning. I'd be interested in your thoughts.
|
Thanks for the feedback @SimenB! Great to know what bits people like and don't like. I'm hoping to refine this proof of concept over the next few weeks, which should give us lots of time to gather feedback and work out if this is a sensible change to pursue. |
84e21c0 to
c6c686c
Compare
|
I've pushed an updated version of this PR with a new heuristic which renders the opening and closing times of their own lines if the JSX contains any tag elements. This improves some of the formatting issues pointed out by @SimenB. For example: Now becomes: Whereas in the previous version of this PR it would output: JSX containing just text or expressions (e.g. For example: The output might not be good enough yet, but it is starting to look more sane :) |
| </div> | ||
| </div> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
One consequence of the new heuristic is that deeply nested tags now expand to take up more lines. This might be nice as it makes it very explicit that you have a high level of nesting.
If we want to avoid this we could change the heuristic to only expand elements if they have at least two JSX tags.
There was a problem hiding this comment.
I like this 🙂 As you say, it highlights deep nesting, which i fine
There was a problem hiding this comment.
FWIW, the original behavior was very much intentional.
The deep nesting is plenty visible in the inline case, and if it needs to be done, why make it even more annoying?
Especially with Styled Components and the like taking off, I don't think we'd want to effectively discourage things like this:
render() {
return (
<MyList>
{this.props.items.map(item => (
<MyListItem><Text><MyDomainSpecific>{item}</MyDomainSpecific></Text></MyListItem>
))}
</MyList>
)
}
| <div style={styles} key="something"> | ||
| Keep the wrapping parens. | ||
| </div> | ||
| <div style={styles} key="something">Keep the wrapping parens.</div> |
There was a problem hiding this comment.
I still think this was more readable before, but I have no idea which heuristic would make it better...
|
It's definitely better, IMHO. Really looking forward to seeing where this lands! |
|
I've pushed up another tweak to always make JSX elements with attributes and children multiline. This makes the following always multiline: I think this improves the readability in general, at the cost of occasionally making something multiline that doesn't really need to to be. Again, we could tweak this to only apply if there are multiple attributes if we want to reduce the number of unnecessary breaks onto multiple lines. |
| <h2>First</h2><p>The first paragraph.</p><h2>Second</h2> | ||
| <p>The second paragraph.</p> | ||
| </div> | ||
| ); |
There was a problem hiding this comment.
The output here is pretty terrible!
|
I'm fairly worried that this is getting too proscriptive & special-case-y... For example, could very easily be the best formatting in some situations, better than The former keeps the components more atomic and highlights that they are separate siblings. I might propose that this change targets only "paragraphs of text", not "series of elements". I know that sounds difficult to determine, especially when elements like Just spitballing / 2¢ |
|
And a very good 2¢ they are @rattrayalex! I was thinking many of the same thoughts myself. I think a heuristic of requiring at least one text element (that isn't just whitespace) would be a good heuristic to try. It would fix up the terrible formatting in the I agree that this could end up with too many special cases, and if that does happen I'm happy to write it off as an interesting experiment. |
Sounds like a really good heuristic! |
86c1086 to
2bccfc0
Compare
|
I've updated the PR with the heuristic we discussed above. If the JSX element contains meaningful text (something other than whitespace) then we use becomes |
|
I've run this PR over the full Geckoboard codebase and make a couple of tweaks based on the result.
becomes I also tweaked the attributes heuristic to only force a break when a tag has 2 or more attributes. It seemed like a nice balance of allowing some inlining while breaking when complexity increased. |
|
With the example below: <div>
- Updated every 1 second: "
- <RelativeDate date={new Date()} delay={1000} />
+ Updated every 1 second: "<RelativeDate date={new Date()} delay={1000} />
"
</div>I believe the |
e019ae3 to
16f8b19
Compare
|
Can I hit the merge button? :) |
|
I'm just testing a bug fix, I'll give you a ping when I'm happy with it! |
a3524bc to
d241c9b
Compare
d241c9b to
40b3e35
Compare
|
@vjeux I'm all done with my testing, very happy to have this merged in! Once it is merged I'll create new issues to track the follow on work this PR has generated (e.g. better output for the example you posted above, better output of single expressions as discussed with @rattrayalex, and adding a flag to toggle FB translation pipeline compatibility). |
|
🎉 |
|
Woo! 🎉 |
|
Hopefully tomorrow. |
|
I've created a number of follow on issues based on the discussion in the PR, if I've missed anything please feel free to create a new issue and ping me! |
|
Woohoo, awesome work @karl !!! Very exciting! The handful of tricky cases around expressions etc definitely make sense as follow-ons, I'll try to take a look and be helpful there if I can, though you seem to have this stuff on lock 😄 |
This PR is an initial investigation into the feasibility to having Prettier recombine JSX lines that have already been split.
Inspired by: #1583
This still has a number of edge cases that need to be handled, but the basics are there.
For example:
Would become:
Allowing lines of JSX to be recombined is a big change from our current approach where we always retain newlines in JSX.
Issue #1583 mentions just recombining lines split with
{" "}whereas this PR takes the approach of allowing any lines to be recombined.I'd be interested in people's opinions on this. Do we think allowing lines to be recombined, as we do with JS, is a good idea for JSX?
P.S. I've based this PR on top of some other PRs I've been working on, only the last commit contains logic for recombining lines.