rust-sailfish / sailfish

Simple, small, and extremely fast template engine for Rust
https://rust-sailfish.github.io/sailfish/
MIT License
796 stars 54 forks source link

Consider a markup.rs-like alternative syntax or an API to aid compile-time template generation #41

Open ssokolow opened 3 years ago

ssokolow commented 3 years ago

With all the iteration I've been doing on my test project, I've become very aware of how much the client-side parts of the project are a hole in the strong typing Actix provides me.

Traditionally, I would mitigate that by using TypeScript for the JavaScript and having development builds use a runtime-loading API for HTML templates to speed iteration when I do run into bugs because, in the end, there will always be some things a type system can't fix. (eg. Sailfish can't protect me from using the wrong kind of element)

However, doing this in Rust and using things like Actix's header-name constants for protecting against typos, I'm starting to feel that it should also be possible to get the templating performance of Sailfish with error-prevention matching or exceeding that of markup.rs by writing a build.rs which converts a syntax like that of markup::define! to Sailfish .stpl before Sailfish sees it... and having to do it in a build.rs to pull the wool over a template system that expects to read directly from a file in a derive macro feels like an ugly hack.

(Back when I was using a Python framework named Pylons years ago, I would have used a templating engine named Genshi, which ran its templates through an XML parser to ensure a minimum level of well-formedness, if it weren't one of the slowest templating engines because it did it at runtime.)

How receptive are you to the idea of either having some kind of alternative syntax which rules out malformed HTML (eg. <h2 title=""my editor's quote/bracket auto-pairing goofed">Oops. This should be an <code>h2<code> element.</h1>) at compile time or providing a proper way to hook in a preprocessor so others can do it?

(Ideally, which can catch typos in tag names, either by distinguishing between standard and custom tags syntactically (eg. h1 vs. "lightbox") or by requiring non-standard tags to be registered with some kind of "add tag" annotation before they'll be accepted or one where it's possible to hook some kind of validator function in between it and the rest of Sailfish.)

Such a syntax would also resolve another issue I have with traditional HTML templating's string-based (as opposed to AST-based) nature... I often find myself in situations where I'm doing stuff like this because the presence of whitespace between two elements will cause a styling bug.

<thing title="blah blah blah long thing that needs to wrap"
>thing that must not have space between it and the previous element

An alternative syntax like that of markup.rs would make it possible to directly and easily generate minified output where the only whitespace is the whitespace you explicitly ask for while still having templates that are formatted for human consumption.

Personally, I'm partial to making it easy to hook in a custom preprocessor, because designing a proper markup.rs-style syntax which does the smartest job of drawing a line between mistakes and useful but hard to validate stuff feels like it'd need a lot of thought and experimentation and I wouldn't want to stabilize the syntax too quickly.

For example, what's the most broadly useful equivalent to this that feels nice to write and still ensures a well-formed tree of elements at compile-time?

<a><b>
<% if foo %><strong><span class="icon_alert"></span><% } %>
<d><e>hello</e></d>
<% if foo %></strong><% } %>
</b></a>

(Yes, I know, in that particular case, it could be done with a CSS class and a ::before. I can't remember which of my projects had the actual example with no better option.)

ssokolow commented 3 years ago

Random brainstorming for the "catches tag typos" part of such an idea.

Markup.rs has @markup::doctype() so, if such a system used the same syntax, it could have:

(If available, a forbid_tag would be better done outside the template.)

Kogia-sima commented 3 years ago

@ssokolow Thanks for your feedback and detailed explanation!

(I am not very good at English, so I apologize in advance.)

Actually there was an idea that Sailfish allows Pug syntax before but it was discarded because it requires evaluating JavaScript syntax in conditionals and iterations. Also I found that Pug allows JavaScript expression in many place. Pug renderer achieves this by simple wrapping the generated code by Function object (which requires JavaScript runtime)

Traditionally, I would mitigate that by using TypeScript for the JavaScript and having development builds use a runtime-loading API for HTML templates to speed iteration when I do run into bugs because, in the end, there will always be some things a type system can't fix. (eg. Sailfish can't protect me from using the wrong kind of element)

I think that modern editor can prevent developers from writing invalid tag names. Here is the screenshot from my vim.

html-invalid-tag

In this case the invalid HTML tag name (string, which should be strong) is highlighted in different color. And I believe we can get most of benefits from pug or markup.rs syntax by properly configuring the editor or installing editor extension.

Also markup.rs syntax does not allow inline <script> or <style> as of yet. We need to somewhat extend the original syntax to support these tags.

An alternative syntax like that of markup.rs would make it possible to directly and easily generate minified output where the only whitespace is the whitespace you explicitly ask for while still having templates that are formatted for human consumption.

Sometimes I intentionally insert a whitespaces or line breaks between spans in order to separate them. Stripping this whitespaces is not desirable for me. In fact EJS was reported the bug about this behaviour more than 2 years ago. For this reason I think whitespaces should be kept and not stripped out from rendering results.

If you like to write templates in another format, the better solution would be developing a preprocessor to convert from another format (such as pug, markup.rs) as an external tool. Then you can write the preprocess phase in your build.rs. There is no need to preprocess in the Sailfish compilation phase.

Also If your HTML can be tested on local machine, I think, this RFC can solve all of the issue stated here.

ssokolow commented 3 years ago

Actually there was an idea that Sailfish allows Pug syntax before but it was discarded because it requires evaluating JavaScript syntax in conditionals and iterations.

I wonder if Pug was inspired by Haml for Ruby. Either way, despite being a Python programmer, I'm not a big fan of things like Haml, YAML, and CoffeeScript. It's too hard to design a syntax with significant indentation without making it a footgun.

I think that modern editor can prevent developers from writing invalid tag names. Here is the screenshot from my vim.

I use Vim too, but I wouldn't want to try to bodge Vim into the CI runs for PRs.

Also markup.rs syntax does not allow inline Githubissues.

  • Githubissues is a development platform for aggregating issues.