dotnet-websharper / core

WebSharper - Full-stack, functional, reactive web apps and microservices in F# and C#
https://websharper.com
Apache License 2.0
595 stars 50 forks source link

Allow sitelets to return SPAs #1403

Open granicz opened 6 months ago

granicz commented 6 months ago

Now that we have a prototype for sitelet bundling, we have the capability to extend it over SPAs. For instance, given an SPA entry point:

type HomeTemplate = Template<"home.html", ClientLoad.FromDocument>

[<JavaScript>]
let HomePage() =
    ... // Page model, etc.
    MainTemplate()
        ... // Event handlers, placeholders, etc.
        .Bind()

... allow it to be served with its static HTML:

[<Website>]
let Main =
    Application.MultiPage (fun ctx -> function
        | EndPoint.Home ->
            Content.PageFromFile("home.html", Client.HomePage())
        ...

Here, the proposed Content.PageFromFile is akin to the current Page.Content: we generate a page bundle (a .js file named after the HTML file) and entrypoint code from the supplied quoted expression (Client.HomePage() above) to be automatically included in the response (into the usual "scripts" placeholder).

Jand42 commented 6 months ago

@granicz I was thinking we can bring Sitelets/SPAs/Offline sitelets all together. But Offline/SPA would have no dynamic content, it could only load data from server by RPCs to initialize itself. The point to use predefined html (compile-time generated for Offline and hand-written for SPA) is to not touch it when serving. So this piece makes no sense: we can't serve a static file AND dynamic content at the same time - which one is it: Content.PageFromFile("home.html", Client.HomePage())

granicz commented 5 months ago

The point of Content.PageFromFile is to enable serving a potentially non-templated SPA from a sitelet. (Even for templated cases such as the one above, the server can compute the response much faster because it doesn't need to use UI templates for it.) As such, it involves an element of dynamism: while the response is based on a static HTML file, it's actually augmented with a script block that takes care of the after-load initialization, supplied by the second, quoted argument.

In a way, this is similar to serving a page with an OnAfterRender (OAR) function, without the need to add an OAR or even using UI templating.

The intended behavior is that each Content.PageFromFile establishes a unique bundle and the server references it at runtime when generating the response from the HTML file (first argument).

Jand42 commented 5 months ago

@granicz I see, what should the bundle .js be named? From your use case, it seems you don't want to pre-bake the initialization into "home.html", and this would be looked up from app root, not web root (not a public file by itself).

So if you want to use the same html in multiple places, we could do this:

  1. by default put code for functions and argument deserializers used in all "home.html" initializations by Content.PageFromFile into a single home.js bundle
  2. also have an optional argument for bundle name, that would specify the .js name that could be different, and then you have smaller bundles when you reuse same html with different content
granicz commented 5 months ago

There would be near-zero chance for an HTML file to be used with multiple instantiations, so I'd just go with 1).