fsbolero / Bolero

Bolero brings Blazor to F# developers with an easy to use Model-View-Update architecture, HTML combinators, hot reloaded templates, type-safe endpoints, advanced routing and remoting capabilities, and more.
https://fsbolero.io
Apache License 2.0
1.06k stars 54 forks source link

How to organize several Bolero pages #15

Open amieres opened 5 years ago

amieres commented 5 years ago

What would be the best way to organize several pages that use Bolero. Should we have a shared _bin directory?

Tarmil commented 5 years ago

A shared _bin directory would be problematic because of the linker. The build process calls illink, which strips assemblies of types and methods that aren't used by the main assembly. So if you share _bin between two pages, one of them is going to hit MissingMethodException or other similar errors because it's trying to use an assembly that was linked for the other page. I've searched if there was a way to illink against several entry points, but I didn't find one.

I would say try as much as possible to write these pages in the same assembly and use Bolero routing to switch between them; but if you can't (eg because they have different layout in the static part of the page), then I don't see a better way than making them in completely separate subfolders. If you have an ASP.NET Core server side, you need to put at least one of the UseBlazor calls in an IApplicationBuilder.Map:

app
    .Map("/page1", fun app -> app.UseBlazor<Page1.Startup>() |> ignore)
    .Map("/page2", fun app -> app.UseBlazor<Page2.Startup>() |> ignore)
    .UseBlazor<RootPage.Startup>()
|> ignore
willnationsdev commented 5 years ago

@Tarmil

  1. Is this .Map(routePath, initialization) workflow effectively the equivalent to "mounting a route" in other frameworks (just for clarification)?
  2. Based on the custom route workflow in the documentation, it seems like it would be possible to procedurally generate mounted routes, albeit in a verbose manner. Is this correct?
  3. Is there a simplified workflow overall for mounting routes using the [<Endpoint "path">] attribute where a route references a record that that, itself, also has endpoints (so mounting doesn't have to be done at the "app" level)?
  4. Does this linker problem mean that if I create F# libraries and wish to use them in both my Client and Server Bolero projects, then I will run into issues?
Tarmil commented 5 years ago
  1. Is this .Map(routePath, initialization) workflow effectively the equivalent to "mounting a route" in other frameworks (just for clarification)?

Just to be clear: the .Map(...) I showed above is a server-side ASP.NET Core feature and completely unrelated to Bolero's routing. And yeah it's pretty much equivalent to mounting a route in other frameworks.

  1. Based on the custom route workflow in the documentation, it seems like it would be possible to procedurally generate mounted routes, albeit in a verbose manner. Is this correct?

You're talking about "Custom router" in the documentation, right? This is a Bolero feature, so it's client-side and will only trigger if the containing page was returned by the server in the first place.

  1. Is there a simplified workflow overall for mounting routes using the [<Endpoint "path">] attribute where a route references a record that that, itself, also has endpoints (so mounting doesn't have to be done at the "app" level)?

I'm not sure if this is what you mean, but you can combine routes like this:

type SubApp1 =
    | [<EndPoint "/page1">]
      Page1_1
    | [<EndPoint "/page2">]
      Page1_2

type SubApp2 =
    | [<EndPoint "/page1">]
      Page2_1
    | [<EndPoint "/page2">]
      Page2_2

type FullAppPage =
    | [<EndPoint "/sub1/{page}">]
      Sub1 of page: SubApp1 // -> /sub1/page1, /sub1/page2
    | [<EndPoint "/sub2/{page}">]
      Sub1 of page: SubApp2 // -> /sub2/page1, /sub2/page2
  1. Does this linker problem mean that if I create F# libraries and wish to use them in both my Client and Server Bolero projects, then I will run into issues?

No, this won't cause any problems. The "illinked" assemblies are used on the WebAssembly side, but the original full assemblies will be used by the server side if you reference them from there.

Bananas-Are-Yellow commented 3 years ago

A shared _bin directory would be problematic because of the linker. The build process calls illink, which strips assemblies of types and methods that aren't used by the main assembly. So if you share _bin between two pages, one of them is going to hit MissingMethodException or other similar errors because it's trying to use an assembly that was linked for the other page. I've searched if there was a way to illink against several entry points, but I didn't find one.

I would say try as much as possible to write these pages in the same assembly and use Bolero routing to switch between them; but if you can't (eg because they have different layout in the static part of the page), then I don't see a better way than making them in completely separate subfolders. If you have an ASP.NET Core server side, you need to put at least one of the UseBlazor calls in an IApplicationBuilder.Map:

app
    .Map("/page1", fun app -> app.UseBlazor<Page1.Startup>() |> ignore)
    .Map("/page2", fun app -> app.UseBlazor<Page2.Startup>() |> ignore)
    .UseBlazor<RootPage.Startup>()
|> ignore

This reply is quite old and the startup code has changed a lot since then so I'm not sure how it should look today.

I think I have the same situation. I have an application for creating an SVG graph which is displayed in an <iframe> element. You can embed the finished graph in an unrelated web page but I don't want this to download the code for the main application, just the code for the displaying the graph. The main application will have a _Host.cshtml file with some static layout and I assume I'll need another _Graph.cshtml with a minimal layout. I'm imagining one server project and two client projects.

Is this how to do it? How should the startup code look?

Bananas-Are-Yellow commented 3 years ago

After some experimentation, here's how to do it.

I have a second client project, called Graph.Client.fsproj and the server has a separate _Graph.cshtml page to render it.

(1) _Graph.cshtml has two changes.

@page "/graph"

<base href="/graph/">

(2) Graph.Client.fsproj has the static web asset base path set so that static resources (css and js) will be found under the base href /graph

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <StaticWebAssetBasePath>graph</StaticWebAssetBasePath>
  </PropertyGroup>

(3) Startup.fs in the server project now looks like this:

member _.Configure (app: IApplicationBuilder, env: IWebHostEnvironment) =
    app
        .UseAuthentication()
        .UseRemoting()

        .UseBlazorFrameworkFiles()
        .UseStaticFiles()

        .MapWhen(
            (fun ctx -> ctx.Request.Path.Value.StartsWith "/graph"),
            (fun app ->
                app
                    .UseBlazorFrameworkFiles(PathString "/graph")
                    .UseStaticFiles("/graph")

                    .UseRouting()
                    .UseEndpoints(fun endpoints ->
                        endpoints.MapFallbackToPage("/graph/{*path:nonfile}", "/_Graph") |> ignore)
                |> ignore
         ))

        .UseRouting()
        .UseEndpoints(fun endpoints ->
#if DEBUG
            endpoints.UseHotReload()
#endif
            endpoints.MapBlazorHub() |> ignore
            endpoints.MapFallbackToPage("/_Host") |> ignore)
    |> ignore

This all works, but I have a residual issue with Bolero remoting, which is documented here:

https://github.com/fsbolero/Bolero/issues/204

Also, with DotNet SDK version 5.0.102, the published app has an issue, which is documented in the following thread and will be fixed in version 5.0.3. There is a manual patch which fixes the issue until that time.

https://github.com/dotnet/aspnetcore/issues/29179#issuecomment-760404863