(Re)Introducing HydroActive #4
Replies: 2 comments 1 reply
-
Thanks for the feedback @thescientist13, definitely appreciate your thoughts here! Apologies for the long response, you gave me a lot to think about here. 😅
I'm a little unclear on the problem here. Why can't WCC just throw an error in the case of multiple
This is an interesting idea, and one I haven't seen explored too much. I do like that it decouples the need from running JavaScript on the server, though in my experience typically WASM is used to execute non-JavaScript code in a JavaScript runtime, meaning most users are probably using a JS runtime on the server. I imagine most servers would probably target native compilations for their server platform and execute the module that way. That's not a strict requirement though, and you certainly can run WASM on the server without a JS runtime, just an observation about the way I've typically seen it used. There's probably more value to cross-compiling into WASM in order to run on the browser, which could give an opportunity to writing non-JS code which is able to SSR and CSR together. Blazor works this way I think, cross-compiling to WASM on the client and .NET on the server. From HydroActive's perspective, I don't think this fundamentally changes the calculation however. While this approach might use a different language, it still requires a single authoring format to support both CSR and SSR together and has to deal with incompatibilities between the two domains mentioned in the video (stateful vs stateless, long-lived vs short-lived, etc.). It also still fundamentally couples the client and the server together in a potentially undesirable way. For example, Blazor still requires a .NET server and all the MicroSoft tooling that necessitates. It might not be JavaScript, but you're still forced into certain design decisions based on your choice of frontend framework. HydroActive tries to decouple these two things. I would interpret any WASM-based tooling which renders on the client and the server as fundamentally using the same cross-compilation approach as existing web frameworks, just with potentially less JavaScript.
Yes, you're absolutely right HydroActive can be brittle to this kind of change. I think this is a fundamental design trade-off made by decoupling the client and server from each other and I can't see any total "solution" to this problem which doesn't necessitate coupling the two together. How bad of a problem this is likely depends on how generic your selectors are. One possible alternative is something like Hotwire Stimulus which has you "name" elements via HTML attributes and then reference them in JS. I'm not a fan of this approach as it doesn't work well with TypeScript IMHO, but I think HydroActive is generic enough such that you could use a similar model yourself. Actions also invert the dependencies and make HTML depend on JS, which is backwards IMHO, but I can see the appeal at least. I'm still thinking this through myself, but the primary solution I have here is to write sufficient tests to make sure components hydrate correctly based on your SSR'd content. This unfortunately gets tricky because of the nature of the server decoupling. Users need a way to:
Numbers 2. and 3. are relatively straightforward to solve with some simple testing APIs. I do have tests of my current implementation and am able to do this relatively easy. However 1. is fundamentally impossible for HydroActive to solve strictly because we are decoupled from your server. I have no idea how to SSR a component in your application. I'm planning to do another video in the future on testing specifically, because I do think this is the biggest unsolved problem HydroActive currently has. Even the tests I do have actually duplicate the HTML content because I didn't want to overcomplicate the setup for my examples. I certainly could restructure things to use a real SSR server and invoke that for testing, but I wanted to keep things as simple as possible for the demos. I think some higher-level abstractions could help here. For example, One potential future solution here: From experience with e2e testing, I've generally come to the conclusion that selectors like this should align with the accessibility tree, given that's how users will interact with the page, and I think that might be a good approach to use for HydroActive here. For example, just selecting I think philosophically, HydroActive's narrow scope as a simple library for hydrating web components means that it works best for developers and projects which integrate small pieces of infrastructure together to solve unique problems. As opposed to using a single piece of tooling which solves a wide variety of problems you may not have or struggle to adapt to your use case. This philosophy won't work for everyone or all projects, but it is a mentality which resonates with me personally. I do think it may be possible to build out that "single piece of tooling which solves a wide variety of problems" as a higher level abstraction (metaframework?) on top of HydroActive. Although for such use cases, I'm not sure how useful the decoupled nature of HydroActive would actually be. If such a system requires JS on the server anyways, then why not use a single authoring format for CSR and SSR? I'd definitely like to provide some tools to help with the testing problem, but I'm still figuring out what the right abstractions are here. For example, maybe I could do a Web Test Runner plugin which delegates to some JS tooling to render test cases for a specific component? However that assumes you're using a JS server and Web Test Runner. Fundamentally I doubt I could cover all use cases, but I wonder if it might be possible to support tooling for at least the most common use cases, while being flexible enough to allow users to integrate their own custom serving solutions when necessary.
That's interesting, I'd definitely like to take a look when you have something to show here. I'm definitely curious to understand how you would do solve this problem without a runtime script, but I might not be understand what you're getting at here. What you're describing sounds like a cross-compilation solution to me in terms of compiling a JSX component into something which can be rendered on the server and then hydrated on the client. My two major complaints with that approach (not that they're really deal breakers, just design trade-offs I'm not personally a fan of):
To be clear, I don't think there's anything wrong with your approach here, just that it's making different trade-offs than HydroActive in this area. Actually, Alex Rickabaugh actually gave a talk at
I assume you're talking about the idea that HTML/JS/CSS separated structure/interactivity/presentation into distinct languages, and that JSX by its nature merges the structure of the page into the JS piece? I don't have super strong feelings about this but to me that separation of concerns went out the window as soon as we figured out components were a good way of breaking up applications. The value of components is that they provide a "vertical slice" of structure/interactivity/presentation for a small part of your application. Within a small component, separating these concerns isn't super important since a single component has much less complexity than an entire web page. In that light, I don't really see JSX as "breaking" separation of concerns, it's just defining a different DX for authoring a component which supports reactivity in a way plain HTML can't. Angular and Vue by contrast separate out their templates into distinct constructs, which maybe follows the separation of concerns a little more closely but also means interoperating between the JS and templates gets more complicated. For example, Angular needs Basically I think "separation of concerns" these days applies more to separating components from each other (the login component shouldn't care about the footer component) than it does from separating structure/interactivity/presentation from each other (HTML, JS, CSS should be independent). The part I do have strong feelings about is that CSS solves styling pretty well these days and I'm personally not a fan of any solution which throws that out. How you author that CSS can certainly be adjusted or improved by frameworks or tools (ex. style isolation, Sass mixins, etc.) but anything which tries to hide the underlying CSS is just making things more complicated IMHO.
I don't think any examples reuse the same component twice on a page, but everything in
The ceddea9#diff-9578e2ac5a7c74d23e02b0aa664024b75c73e3dd5ea393a7cfe21060d936f553 As much as I like a "shadow DOM by default" approach, I feel like this would be too strong a stance to take today as I consistently see a lot of feedback that shadow DOM is just too restrictive for many use cases. The
😁
It was actually inspired by class MyElement extends HTMLElement {
private foo!: Resource;
public connectedCallback(): void {
this.foo = getSomeResource();
}
public disconnectedCallback(): void {
cleanupSomeResource(this.foo);
}
} The
This is good feedback. I'm actually demoing the examples in the repository, so I need those to be somewhat self-explanatory as well for people just looking at the repository directly. However most devs aren't going to read those in the right order to make sense and a separate documentation site would probably describe them better anyways, so it might make sense to decouple what I'm showing in the video from the examples in the repository itself. That would also address the "verbose comments" problem you're describing. Thanks for the suggestion here. Glad you enjoyed the video and I definitely appreciate your thoughts here! I'm working on another video which dives into the hydration part of HydroActive, but I've been busy with |
Beta Was this translation helpful? Give feedback.
-
Yeah, mainly because on a server the "browser" sandbox is shared amongst all requests / users coming into that server, so multiple SSR calls would be happening over the uptime of the server invoking repeated Thus, isolation mode in Greenwood gives each request handler its own sandbox to execute in, basically reproducing the sandbox like execution context you would get in a serverless function (where this is not really an issue) to avoid repeated failures from WC libs that do enforce the single use
Fair. I think in terms of SSR WCs, its probably a spectrum where you have something like WCC which all the way on one side adhering to web standards through and through on the server, to things that "look" like WCs but maybe remove the actual custom elements boilerplate for a general render function that targets WCs (with the main "target" being DX in this case), to something like HydroActive which does not care about the server at all.
Ultimately I think this quote / section really goes back to thinking of SSR WCs as a spectrum, and short of compiling down a SSR WC framework from JavaScript to your language via WASM, I'm sure there is a place for folks who just aren't into authoring in Go or Ruby or PHP and still want "sprinkles of reactivity" ( ✨ ) on the client though. For them, templating is the preferred solution there, which I think is also why htmx is so popular, it only cares that your backend can return HTML. So, in that case, maybe that caveat is no big deal. I think the opportunity here is to embrace this side of the spectrum and work on smoothing out that caveat of aligning the client and SSR template. While most people are "resigned" to JavaScript on the client, I think appealing to web devs who have stronger preferences on the backend would a great community to help out with here. Maybe this could be the start of the H2 stack? (HTMX + HydroActive) 🤔 (Let's get OCaml in there and you got the H2O stack 🧠 ) That said, I think the approach you are advocating for here with testing like a user and aligning that sort of E2E testing strategy seems quite intriguing, being someone who has always disliked the use of test IDs in components and apps. This could be a really good way to make sure the content is focused around semantic and meaningful markup, focusing on that as an overall goal anyway, rather than just as necessary "glue" binding.
The idea behind it it is to look at the dynamic parts in the JSX template and map those all the way back to attributes, automatically reflecting them, and then use that information to inline the needed custom element lifecycle methods into that component definition as part o the WCC compilation process. Basically a combination of Svelte (compiler) and Solid (components only render once) approaches. I was watching a lot of Ryan Carniato streams at the time, heh. But yes, I think both of your call outs are valid and while I think I have something basic to handle the first call out, I think the second would possibly come after, but the hope is that by knowing the template, we would know the state of the component, and potentially if it doesn't have state, or only needs to render once, WCC could send compiler hints and or a reduced size custom elements definition. Lots of ideas at this point, and so hopefully in the back half of this year I will get around to working on it again, once I get the new Greenwood website out. I don't expect it actually take on any of the major frameworks in this approach, but the expressiveness of JSX at least feels like it can get WCC to as close to a "render" function DX for even just basic static templating on the server. export const default Footer extends HTMLElement {
render() {
return (
<footer>
<h5>© {new Date().getFullYear()</h5>
)
}
}
Agreed, and for what it's worth, I even plan on emphasizing this choice on the new Greenwood website. |
Beta Was this translation helpful? Give feedback.
-
Just wanted to say I really liked the new video on HydroActive! Had a general grab bag of thoughts and comments for you if you are inclined to read it. 😄
Intro / Hybrid Rendering
I liked the breakdown at the beginning, and about hybrid rendering in particular because I've been thinking about the JavaScript only limitation on the server that comes with it as you mentioned. In addition to not being bound by a particular server technology, in the case of Greenwood where we want to honor a hybrid model in the pursuit of web standards, I found this to be challenging in the case of something like shimming
CustomElementsRegistry
. As you know, it is specified to only allow one component definition otherwise it will error. Lit SSR adheres to this, and WCC's DOM Shim kind of no-ops this away, but to normalize both (and future renderers) when running a server with Greenwood, Greenwood will just run it in a Worker threads, or as I call it, "isolation mode". (Lit team recommends VM Modules, although I hope to find something more standards compliant ideally so its not tied to a particular JS runtime, but that's another topic for another day, hah!)Anyway, back to Hybrid rendering, I wonder if something like WASM / Extism could work here, at least at providing a portable web standards environment / shim? I opened a topic in the WCCG about a shared DOM Shim where the Enhance team showcased using WASM to bring their renderer to any server environment, removing the JS only runtime constraint, which I was wondering if you thought that could also be possible with a DOM Shim too maybe? (I have yet to follow up to the comments in that thread, but figured I would ask here too since it seemed relevant either way).
I know HydroActive is not active (pun intended) in server rendering but the topic of runtime portability is too intriguing to me to not know if you think it would help move the needle or not for you. 😁
On that topic of de-coupled server and client responsibilities...
Demo
I know we've talked about this in the past, but one sticking point to me is the implicit contract on the backend to provide the template in a necessary shape for the client, effectively having to know / set contracts for specific "hydration" points. For example, in the counter examples, a
<span>
is cited as a bounding box needed for the counter's interactivity "hook" point.I know these are selectors and from the component side it can generally adapt to any structure, but the necessity of that wrapping
<span>
seems like it could be fragile if both sides of the stack are not always in sync with any changes, in particular the backend HTML? Not sure if you envisioned this in the context of a full-stack app developer working on client / server together in delivering a cohesive feature by adding "sprinkles of reactivity" as they go?For example, this one slight change could potentially blow up the component, right? (using a wrapping
<span>
instead of<p>
for count).I do like this in concept for sure in the full-stack workflow, as in that case you would likely be developing / testing the changes / feature at once it would totally make sense.
It's still WIP, but for an upcoming feature of WCC's JSX capabilities it will effectively use the expressions in the JSX as a means of serializing initial state into HTML attributes and auto-generate the reactivity dynamically when compiling the JSX by creating the
observedAttributes
andonChangedCallback
implementations based on those expressions, including instructions for how to use the serialized attributes for finding the specific DOM node to use as a selector for targeting a content update. Unlike Qwik or Svelte (though certainly not as robust) I hope to at least avoid needing a runtime script for it all to work. 🤞(I still owe you a review of the Resumeability issue you opened in Greenwood as I think there is a lot opportunity in this space as well!)
Also curious if you put much value in the way React framed that putting your HTML into your JSX was not breaking the principle of separation of concerns, as what was being promoted before was actually a separation of technologies?
Misc Thoughts / Questions
defineComponent
uses the selector passed into it as a starting point for resolving selectors used in things likebind
? Kind of like a Shadow Root?<template>
tags?live
binding API, fond memories of my jQuery days, heh. But the typing of it is really slick!connected
and using thereturn
for clean up work, very reminiscent ofuseEffect
in ReactLooking forward to more videos on HydroActive! 💧
Beta Was this translation helpful? Give feedback.
All reactions