tatethurston / embedded-typescript

Type safe embedded TypeScript templates
MIT License
45 stars 1 forks source link

implement whitespace control in front of tags + Async templates #14

Closed foxfriends closed 5 months ago

foxfriends commented 6 months ago

Hoping to use this library, but one thing I need is to be able to control the whitespace a little more, as well as the ability for templates to be async.

Took eta's syntax for whitespace control, and implemented it for in front of tags. Didn't change the after-tag whitespace control since there's already some behaviour there and didn't want to affect that.

For async templates, I have made it so that .async.ets files are generated with async functions, and regular .ets files are as normal (regular function). Also considered:

tatethurston commented 5 months ago

Hey @foxfriends, thanks for the PR.

Could you tell me a bit more about the leading whitespace control you're missing? To date I've tried to avoid whitespace control characters, by instead baking in default whitespace control behavior. I've tried to make the DX adding or deleting explicit whitespace characters instead of using control characters. The documentation around this behavior is very sparse though, so there is certainly an opportunity to flesh this out with examples. The design I've been leaning into here is keeping templates explicit and simple, instead of supporting a broader set of ways to write the same template. Put differently, forcing the template structure as the source of truth for whitespace and the forcing function for code organization. Example:

// all on different lines
This is line 1
<% if (true) { %>
This is line 2
<% } %>
This is line 3

// all on the same line
This is line 1.<% if (true) { %> This is also on line 1.<% } %> This too is on line 1.

Certainly open to thinking more about this. For example, a \ for line continuation could be an interesting primitive to add here. Super rough thought:

// all on the same line
This is line 1.<\>
<% if (true) { %>
This is also on line 1.<\>
<% } %>
This too is on line 1.

// all on the same line
lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum <\>
lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum <\>
lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum <\>
lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum <\>
lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum <\>

Could you share your use case for using await within the template? I'm open to supporting this, but I'd expect callers to simply await any async values before passing them to the generated template function. I'd like to avoid the special file naming here and instead detect await usage. I'm fine with a simple / naive implementation for that detection: the likelihood of an await string literal appearing within a templated region seems low to me, and it's relatively easy to iterate on our detection there to support that use case should it arise. A user that encountered this edge case would need to add an unnecessary await but TS would flag it so this would be obvious.

foxfriends commented 5 months ago

Hi,

I just needed full control over the whitespace, while also not having to add/remove extra newlines to compensate for the default behaviour, as I found that hard to figure out/read. Could be the lack of documentation that made it difficult, but particularly within loops where the body of the loop ended with an expression to be interpolated, I was ending up with everything on one line even though I would have expected the newline between iterations to be kept?

As for async, my use case was to be able to query the database and then format the results all within the template, the reason being my templates were nested, with data used by child templates that is only required depending on parent data, and trying to do it all by pre-loading made it hard when the contents of those templates were changing, we had to update the pre-loading queries that were scattered all over the codebase. Ended up being much easier to just pass in the database client and pull the data in-template.

I actually ended up going a different direction with this on my own fork, where there is no default whitespace behaviour in favour of opt-in whitespace removal, and I don't mind the weirdness of the async file extension, as it is just used internally, so I no longer particularly require this if you don't like it as is. Thanks for the solid starting point though anyway!