11 ms·
What should a native DOM templating API look like?
- sakesun 1y agoI really don't expect anybody can contribute anything more to this topic. Too many people spent their brain cycles on this already. Just roll the dice and pick any existing one. It cannot be significantly worse or better.
- rictic 1y agoDiscussion on the previous post in this series: https://news.ycombinator.com/item?id=44390452 https://news.ycombinator.com/item?id=44390452
- svieira 1y agoThe piece that I don't like about this is trying to standardize how to "spell" setting attributes, properties, and events in a single namespace by using sigils. JavaScript isn't Perl or K and the whole `@` vs. `.` vs `?` feels alien to the language (in my opinion). It also doesn't feel like something that could be used correctly by a component author who wanted to pass props _through_ the component they own to another component they don't. I would expect to use something more like namespaces, where the default would be shorthand `on:`, `attr:`, `prop:`, `propOrAttr:` (maybe this is the default?) but where an explicit namespace declaration could move them if you for whatever reason needed to support XHTML 5.1 and your document already used the `on:` namespace for something else (not that _that_ is a thing right now, but why not be prepared?)
- jfagnani 1y agoAuthor here. Property and event disambiguation syntax is definitely far on the easy to change side of things. I personally think the sigils are easier to read and write than perfixes, and these particular sigils have popped up independently in multiple libraries like Vue, Lit, and LighterHTML. But this API could use prefixes, or like Vue, it could support both.
- RadiozRadioz 1y agoI think it would look a lot like XSLT
- righthand 1y agoI got into XSLT after the HN grug brain article last week. It is wonderful and I have my whole site (100 pages/files) migrated in less than an hour (w/o using LLM) and have it served off a 4 line caddy file. Seriously browsers just need to adopt XSLT 3.0 and stop all this wheel reinvention already.
- LegionMammal978 1y agoEh, I also thought that at one point (regarding XSLT 3.0), but it turns out that parts of it are an underspecified mess. E.g., you can specify static parameters to be supplied to the document, but it has nothing on when or how any type-conversion errors would be signaled [0]. And in general, it has very imperative features like shadow attributes that purists would turn up their noses at. I don't see it becoming widespread anytime soon. [0] https://github.com/Paligo/xee/issues/32#issuecomment-2762343209 https://github.com/Paligo/xee/issues/32#issuecomment-2762343...
- monknomo 1y agoThat sounds interesting, and I'd like to hear more
- righthand 1y agoWell it’s all in a single directory: ``` static/ js/ css/ log/ my-post.xml my-post.xsl index.html ``` Caddyfile: ``` :8080 try_files {path}.xml {path}.html file_server { index index.xml index.html root static } ``` XSLT work is all based off of a repository someone wrote with a grug brain explainer: https://github.com/pacocoursey/xslt https://github.com/pacocoursey/xslt The next step is to generate all of the `static` dir from markdown files and probably a new markdown-like format for registering templates to generate xsl/xml files for special html blocks and template parts or generating blog listing pages.
- hk1337 1y agoKudos to the large text. I was thinking of doing similar on my blog.
- maxloh 1y agoI don't understand why Svelte isn't even mentioned. Its syntax is more readable for me. <div> {#each colors as color, i} <button style="background: {color}" aria-label={color} aria-current={selected === color} onclick={() => selected = color} > {i + 1} </button> {/each} </div>
- sieabahlpark 1y ago[dead]
- roywiggins 1y agoI'm somewhat partial to Alpine, which sort of crams it into html. <div x-data="{ open: false }"> <button @click="open = true">Expand</button> <span x-show="open"> Content... </span> </div>
- cluckindan 1y agox-anything is just painful like Angular. UI state is better off being non-declarative, otherwise the builtin behaviors become a limit and unmaintainable workarounds ensue. Nobody wants to express complex conditions all around the UI when derived state can be handled in a single store.
- roywiggins 1y agoYou can drive an entire Alpine app through a class, which works fine- the class handles all the behavior and the x- tags are mostly wiring up events to methods, and state to visible behavior. But it definitely doesn't take that big an app before you start to get a bit cramped. I think I'm fond of it because I've never grown to love html-in-js. Sometimes you definitely need that, but for apps that are simple enough that you don't, I find Alpine is just nice to use. No build steps at all, every editor knows how to color html, etc. It probably shouldn't become a web standard though.
- 1y ago
- timewizard 1y agoMy favorite templating library has always been amrita[0]. I really don't like template languages that try to mix code and elements into a single block of text. It mixes together two concerns and creates exceptionally fragile blobs that generally need to be fully rewritten to extend their functionality. Whereas with amrita you can generally keep the two entirely separate which makes a huge difference in practice. These PHP style template ideas I have never understood. I thought we all agreed 30 years ago that while exceptionally functional for a "quick and dirty" approach they are horrible for long term product or application development. You have a DOM. Use it as such. [0]: https://github.com/rud/amrita https://github.com/rud/amrita
- bevr1337 1y ago> Nested function calls (like React's createElement() or Hyperscript's h()) or object-based builder-style APIs just don't look enough like HTML. What is "enough" here? It is good enough for the unified community. I know npmtrends.com isn't the best metric, but unified is popular. Unified has made it trivial to move between HTML, MDX, MD, JSX and even combinations of the above. I'm learning it's an opinion, but hyperscript and hast are HTML enough being that they're models of HTML Hooking into a tagged template is a challenge. Functions will receive a list of unparsed html strings and already evaluated expressions to zipper up themselves before forwarding to the downstream template API. https://stackoverflow.com/questions/39971088/evaluating-template-literals-dynamically https://stackoverflow.com/questions/39971088/evaluating-temp... Unlike an expression or tag, a developer can hack on any AST or hyper script provided by a library. Im not sure proxies or reflection are useful working with string primitive and they're unergonomic. This was something I mentioned on the other post so I appreciate seeing it called out clearly in the reply. (Not to assume I inspired the mention.) Thank you for sharing.
- jfagnani 1y agoAuthor here: Those don't look like HTML enough because they're not markup. Every single one of the top frameworks uses markup with interpolations as their basic template format: React, Vue, Angular, Preact, Lit, Svelte, Solid, Quik, Stencil, Marko, Polymer, FAST, and on and on. Frameworks and rendering libraries that don't have a markup-based template syntax are just very rare. And I think for understandable reasons: Markup-based templates look like the output, and web developers know HTML, so the templates are easier to read and write. That StackOverflow question seems unrelated because it's asking about untagged template literals. With tagged template literals, depending on the return type, you can absolutely get to the underlying values.
- bevr1337 1y ago> That StackOverflow question seems unrelated because it's asking about untagged template literals. The first answer reinvents tagged templates and the second suggests them. I thought it was a good example to show the complexity of hacking in this approach. I'll add more context in the future. I'm agreed that the average developer would hate working with an an AST -- JSX covers 99.9% use-case and React codebases are full of functional programming escape hatches -- but it is the foundation. An HAST is an interoperable, extendable representation we can move between any framework or tooling. I'm likely projecting too much on the proposal. Templating is the top priority but it's the ancillary bits you mention that excite me most. > Markup-based templates look like the output I made the mistake of drinking functional programming laced koolaid, so in my mind they really do translate well, but I respect that you have a broader perspective. Thanks for taking the time to reply. I'm a smidge honored that you'd even review my feedback!
- rglover 1y agoI think I've cracked this [1]. A lot of the popular frameworks just copy each other by using funky attributes, templating hacks, and compilers. You don't need that (yes, they have certain positives and negatives but you don't need them). Instead, what's hinted at in this article (using a plain HTML string) works great. Add in a little abstraction for the sake of structure and simplicity and you've got a surprisingly robust UI framework without a ton of complexity. [1] https://cheatcode.co/joystick https://cheatcode.co/joystick (a full-stack JS framework that has its own components API)
- cluckindan 1y agoAt a glance, this seems like a clone of React with react-router v6. How is it different?
- rglover 1y agoNot really like React at all (and certainly not React Router). Not sure how you made that connection... The component method in the framework is inspired by React v1 (really just that it's a function you call—my approach to everything else is different), but all of the routing is on the server-side (a light abstraction that maps back to Express.js routes).
- cluckindan 1y agoMaybe it was the lifecycle hooks. :)
- rglover 1y agoIf you're going to evaluate a piece of technology at least take the time to play with it. This isn't just unhelpful to me, it's doing a disservice to yourself (assuming that I've created something that will help you and improve how you do your work).
- cluckindan 1y ago
- deleted 1y ago[deleted]
- dvh 1y agoIt should be secure by default, no more .innerHTML = user_name and gluing strings together like with SQL in the '90s
- jfagnani 1y agoThis API is definitely secure by default, and that's one of the constraints and requirements I mention in the post. The API is secure because it separates static developer controlled strings from dynamic and possibly user-controlled values by JavaScript syntax. Values from text bindings are written to the DOM by setting TextNode.data, which escapes the value first.
- polyrand 1y agoAfter trying a bunch of ways of creating client-side apps, I've now also settled on using lit-html (not even the full Lit framework). Just the `html` and `render` functions. It's simple, convenient, and fast. Since it "caches" the rendered parts, I haven't had performance issues just re-rendering everything on state changes (with some basic scheduling). I find it easier to reason about than React since I can use Vanilla JS everywhere, and components are just functions using the `html` tagged template, which can be composed.
- deleted 1y ago[deleted]
- vlucas 1y agoGood writeup. I am in the camp that Tagged Template Literals are the ultimate answer for easy templating that is native to JavaScript. The bit about using native Signals with them is spot on too. Nothing else really allows the clean separation of HTML strings with other random JavaScript objects and pieces as easily as Tagged Templates do. On the remarks about fear of strings, server-rendered markup is just one giant string sent to the browser anyways. I don't know why some people get so worked up about why they think templates should not be strings. That is literally what HTML shipped to the browser is. It's also the easiest and most straightforward way to define templates in native JavaScript without any compile step. I did many of my own experiments with templates that are native JS with no compile step, and reached basically the same conclusion as this post. Use tagged templates and keep it simple. My angle was server-focused, with async bits in a template doing automatic streaming while server rendering with them. Works super well and it takes so little code to achieve. Check it out if you're interested: https://www.hyperspan.dev/ https://www.hyperspan.dev/ or the CodeSandbox demo: https://7lpdpl.csb.app/ https://7lpdpl.csb.app/
- jfagnani 1y agoInteresting server framework! It looks very similar in some ways to a server I started last year out of frustration with Koa and Express called Zipadee: https://github.com/justinfagnani/zipadee?tab=readme-ov-file#built-in-html-templating https://github.com/justinfagnani/zipadee?tab=readme-ov-file#... The templates look basically identical. html-tagged template literals that support streaming and async values and automatically escape values. What Koa does will allowing strings by be returned with a default HTML mimetype is security malpractice, IMO. It's way to easy to just interpolate user-controlled values.
- vlucas 1y agoWow that is uncanny. We arrived at almost exactly the same solution and started working on it at almost exactly the same time. I went a little bit further with client islands, file-based routing, custom placeholders for async loading content in templates, etc. since I was looking to replace my own use of Next.js. But man. So close to the same thing. Small world. Cheers!
- MrJohz 1y agoMy biggest criticism of this design is that the author doesn't seem to have explored how Svelte, Vue Vapor or SolidJS work at all. The design seems largely based on the idea that rendering the template is a cheap operation, and that it can be done repeatedly whenever values change. So it's fine to have something like html`<div>${arr.map(el => html`...${el}...`)}</div>`, because each time the array changes, rendering every item in the list is a cheap operation. And it's typically cheap because it's rendering to a VDOM-like structure, and the more expensive diffing happens later. But modern signal-based frameworks tend to work on the basis that doing these of rendering for each change is unnecessary, and so you should only update the relevant part of the DOM. This means rendering as seldom as often, in particular when handling conditionals and lists. This is why both SolidJS and Vue Vapor both have helpers for rendering lists and conditional elements, because if you just use `.map` and the ternary operator, things aren't going to work as you'd expect. Svelte has its own syntax but in principle works very similarly. This proposal feels like it's very oriented around the React/Lit VDOM mechanism, with a handful of Preact-like concessions to signals where they are relatively easy to retrofit into a VDOM-like system. The problem is that these sorts of frameworks are heavyweight, slow, cumbersome things, because they're doing so much extra work. Codifying that sort of inefficiency directly into the browser seems like a terrible idea. My second biggest criticism of this proposal is that I'm really struggling to see the benefit of it, i.e. who it's for. It can't be for framework compatibility, because it's not addressing any of the issues that actually make frameworks incompatible with each other (different state/context mechanisms, different lifecycles, etc). It can't be to codify common rendering idioms and optimise them by letting the browser implement them natively, because, as discussed, the proposal doesn't leave a lot of room for well-optimised frameworks. So I guess the idea is to ensure that the browser has a built-in web framework to make it easier for people to build complex web applications without needing to import lots of dependencies. But if you're building a complex web application, the sort that needs specialised rendering like this, the overhead of a web framework is probably pretty minimal anyway. And if you're building simpler sites with minor interactivity, this sort of rendering system is complete overkill. You'd be better if writing a wrapper around document.createElement and going from there.
- throwitaway1123 1y ago> This is why both SolidJS and Vue Vapor both have helpers for rendering lists and conditional elements Haven't these tagged template libraries coalesced around the html`<${SomeComponent}><//>` syntax for custom components (including control flow components like Solid's `For` component)? The readme for Ryan Carniato's Lit DOM Expressions library includes this example for instance [1]: const view = html` <table class="table table-hover table-striped test-data"> <tbody> <${For} each=${() => state.data} >${row => html` <tr> <td class="col-md-1" textContent=${row.id} /> <td class="col-md-4"> <a onClick=${[select, row.id]}>${() => row.label}</a> </td> <td class="col-md-1"> <a onClick=${[remove, row.id]} ><span class="glyphicon glyphicon-remove" /></a> </td> <td class="col-md-6" /> </tr> `}<// > </tbody> </table> `; The author of the article mentions this very briefly, where he writes "For JSX-style references you would need to use binding syntax like <${MyComponent}>". The Preact author's htm tagged template library uses this convention as well [2]. [1] https://github.com/ryansolid/dom-expressions/tree/7fd9f86f1b3cb23594f124bb3f2d2295db4494c9/packages/lit-dom-expressions https://github.com/ryansolid/dom-expressions/tree/7fd9f86f1b... [2] https://github.com/developit/htm https://github.com/developit/htm