Coder Social home page Coder Social logo

Comments (3)

deterdw avatar deterdw commented on June 2, 2024

I definitely think this approach has possibilities. We already do something similar (just for static rendering) in our app, where we have templates shared between Laminar on the front end and Scalatags on the backend - a few platform specific import objects on each side mean I can use the same code in both cases. But that does nothing for hydration, etc. which of course is the big problem.

One possibility that might help ease some of the pain for the library user is to have another library/extension on the JVM side that accepts all the Laminar syntax, but then does nothing for stuff like onClick or onMount, and just generates the sentinel nodes/markers needed for other cases. You could even have a fake library that implements the AirStream syntax for the JVM, but basically does nothing, so that the client code compiles without changes. That's quite ugly architecturally, but I think it definitely beats having to manually look up and wire things from the client side.

from laminar.

raquo avatar raquo commented on June 2, 2024

@deterdw I'm not sure how this would work on a technical level. I assume the goal is to be able to write a Laminar component once, put it into shared, and cross-compile it for both the JVM and JS targets. On the JVM it would be used to render static HTML, and on JS it would be used as usual.

If I understand your idea correctly, you're saying I could define all Laminar types in a parallel JVM library – all with the same package names and class names etc. – but on the JVM side the types would be slightly different, e.g. our ReactiveElement would have no .ref (because it's a JS type), and no type param (again, JS type), so in your shared code you won't be able to use .ref, and you would need to use type aliases like HtmlElement that don't reference JS types explicitly, and on the JVM they would in fact alias to a different, JVM-compatible type, whereas in JS they would stay what they are (ReactiveHtmlElement[dom.html.Element]).

For that, I would need to reimplement the entirety of Laminar and Airstream API in JVM, but the entirety of Airstream would essentially be noop, and all the parts that don't directly affect the produced HTML (such as event handlers) would also be noop.

Is that approximately what you meant?

On its own this would let us generate the HTML code for Laminar components on the backend, but would not immediately provide efficient hydration functionality.

Regarding hydration in this model... well, I guess it would be possible if we can make certain assumptions like "the backend has already created all the exact same static elements that this Laminar component creates, so all we need to do is hang event listeners and insert dynamic children that we know the backend didn't do". It could work with sentinel nodes etc. as you said, but I imagine it would be rather fragile when the backend unexpectedly generates different HTML (e.g. because it passed different input arguments to the component on the backend, compared to the arguments we pass to the same component on the frontend). Well, If we ever get to this point I would need to research other libraries' hydration algorithms.


I wonder if any other libraries do this kind of thing, where the user writes code in shared, but that code ends up compiling to use different types and implementations on the frontend and on the backend, with no shared trait common to those types. OTOH I suspect that Scala might be ok with that, but IDEs might throw a fit.

I feel like maybe code-generating macros would be a better technical solution to this. We don't really care that the frontend and backend copies of the component use the same source code, right? We just want them to produce the same HTML to make server side HTML generation and hydration possible without source code redundancies.

So, perhaps we could write our Laminar components as usual, none of that JVM stuff, and have them annotated with some macro that would parse the component source code and create a JVM-compatible version of the component, and perhaps also modify the frontend version of the component to add the necessary hydration helpers like sentinel nodes (for example, if it sees a child <-- foo, it could wrap it into hydrationSlot("1")(...) on the frontend, and also create the same hydrationSlot("1") in the JVM version. Maybe the hydration will be less fragile that way.

Another bonus of this macro approach is that we can start with the simple approach outlined in the ticket, and then add this on top later as time allows. The downside is that I know nothing about Scala macros, so I can't even be sure that this approach is workable, but overall it does look more promising to me than reimplementing Laminar APIs with JVM types. Although it's still a lot of work. I would really need to be on a different level in terms of how much time I can devote to Laminar development if I am ever to attempt this. But yet again on the upside, this does not require any changes to Laminar APIs, so other developers with more time and experience with macros can try to take a shot at this as well.

So anyway, thanks for your comment, even if I misunderstood it, it gave me more promising ideas.

from laminar.

deterdw avatar deterdw commented on June 2, 2024

Thanks for the friendly reply. I think you got the gist of the idea. I agree it's a lot of work in any way that one approaches it.

The proposal at the top remains the necessary first step. Just generate a text-outputting library with the same structural API as Laminar has. Then one can trivially output the static HTML, which serves some important use cases. As I outlined, this can already be done today with a Scalatags-based hack. Based on that hack, I can confirm that IDEs really don't like this approach, but Scala is OK with it :-)

And one can follow a pattern of defining the component statically in the shared sources and then using .amend on the front end to add the dynamic functionality.

Regarding hydration, it's not necessary to implement all of Airstream. You can have a minimal set of types, e.g. Source[A] and Sink[A] and stipulate that shared code is limited to <-- Source and --> Sink or something like that. The downside is that one has to do some of the wiring independently of the rendering, but that's not worse than React, is it?

The point is that there is no need to support all of Laminar and all of Airstream. The library author gets to stipulate what is supported in shared code and users who want more are free to contribute it :-)

A macro-based approach or even transforming source files with an SBT plugin is another way to do it for sure. I think even in that case there will be some limits as to what is supported and what not, because otherwise parsing might become too complicated.

Regarding other libraries for the domain, the only non-virtual dom one that I know that supports shared templates is WebSharper (C#/F#). But that uses a bunch of custom attribs in the HTML, so it's a very different beast.

from laminar.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.