Skip to content

Commit

Permalink
Merge pull request #348 from fsbolero/interactive-render-mode
Browse files Browse the repository at this point in the history
Enable interactive render modes
  • Loading branch information
Tarmil authored Jan 3, 2024
2 parents 6c376ec + 9bf43ed commit abbb194
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 12 deletions.
1 change: 1 addition & 0 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ nuget FSharp.Core >= 6.0 content: none
nuget Elmish >= 4.0.1 < 5.0
nuget Microsoft.AspNetCore.Components.WebAssembly >= 8.0.0
nuget Microsoft.JSInterop.WebAssembly >= 8.0.0
nuget Microsoft.AspNetCore.Components >= 8.0.0
nuget Microsoft.AspNetCore.Components.Web >= 8.0.0
nuget Microsoft.Extensions.Http >= 8.0.0
nuget FSharp.SystemTextJson >= 0.19.13
Expand Down
16 changes: 15 additions & 1 deletion src/Bolero.Server/Extensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ type ServerComponentsExtensions =
static member RenderBoleroScript(html: IHtmlHelper, config: IBoleroHostConfig) =
html.Raw(BoleroHostConfig.Body(config))

/// <summary>Configure the hosting of server-side and WebAssembly Bolero components.</summary>
/// <summary>
/// Configure the hosting of server-side and WebAssembly Bolero components using Bolero's legacy render mode handling.
/// </summary>
/// <param name="server">If true, use server-side Bolero; if false, use WebAssembly. Default is false.</param>
/// <param name="prerendered">If true, prerender the initial view in the served HTML. Default is true.</param>
/// <param name="devToggle">
Expand All @@ -111,9 +113,21 @@ type ServerComponentsExtensions =
else
this.AddSingleton(
{ new IBoleroHostConfig with
member _.IsInteractiveRender = false
member _.IsServer = server
member _.IsPrerendered = prerendered })

/// <summary>
/// Configure the hosting of Bolero components using interactive render modes.
/// </summary>
[<Extension>]
static member AddBoleroComponents(this: IServiceCollection) =
this.AddSingleton(
{ new IBoleroHostConfig with
member _.IsInteractiveRender = true
member _.IsServer = false
member _.IsPrerendered = false })

/// <summary>
/// Adds a route endpoint that will match requests for non-file-names with the lowest possible priority.
/// The request will be routed to a Bolero page.
Expand Down
24 changes: 20 additions & 4 deletions src/Bolero.Server/HostConfig.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,24 @@ open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Hosting

/// <summary>
/// The Bolero hosting configuration set by <see cref="M:Bolero.Server.ServerComponentsExtensions.AddBoleroHost" />.
/// The Bolero hosting configuration set by <see cref="M:Bolero.Server.ServerComponentsExtensions.AddBoleroHost" />
/// or <see cref="M:Bolero.Server.ServerComponentsExtensions.AddBoleroComponents" />.
/// </summary>
type IBoleroHostConfig =
/// <summary>If true, use server-side Bolero; if false, use WebAssembly.</summary>
/// <summary>
/// If true, use Bolero with interactive render modes, and bypass <see cref="P:IsServer"/> and <see cref="P:IsPrerendered"/>.
/// If false, use Bolero's legacy render mode handling.
/// </summary>
abstract IsInteractiveRender: bool
/// <summary>
/// If true, use server-side Bolero; if false, use WebAssembly.
/// Only applies if <see cref="IsInteractiveRender"/> is false.
/// </summary>
abstract IsServer: bool
/// <summary>If true, prerender the initial view in the served HTML.</summary>
/// <summary>
/// If true, prerender the initial view in the served HTML.
/// Only applies if <see cref="IsInteractiveRender"/> is false.
/// </summary>
abstract IsPrerendered: bool

/// <exclude />
Expand All @@ -56,7 +68,11 @@ type BoleroHostConfig(baseConfig: IBoleroHostBaseConfig, env: IHostEnvironment,
interface IBoleroHostConfig with
member this.IsServer = this.IsServer
member this.IsPrerendered = this.IsPrerendered
member this.IsInteractiveRender = false

static member internal Body(config: IBoleroHostConfig) =
let k = if config.IsServer then "server" else "webassembly"
let k =
if config.IsInteractiveRender then "web"
elif config.IsServer then "server"
else "webassembly"
$"""<script src="_framework/blazor.{k}.js"></script>"""
19 changes: 19 additions & 0 deletions src/Bolero.Server/Html.fs
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,22 @@ module Html =
/// preceded by the standard "html" doctype declaration.
/// </summary>
let doctypeHtml = DoctypeHtmlBuilder()

#if NET8_0_OR_GREATER
/// HTML attributes.
module attr =

/// <summary>
/// Define the render mode to use for this component.
/// Must be used on a component in a server-side page that uses interactive render modes.
/// </summary>
/// <param name="mode">
/// The render mode.
/// Usually one of the modes defined in <see cref="T:Microsoft.AspNetCore.Components.Web.RenderMode"/>.
/// </param>
/// <seealso cref="M:Bolero.Server.ServerComponentsExtensions.AddBoleroComponents(Microsoft.Extensions.DependencyInjection.IServiceCollection)"/>
let renderMode (mode: IComponentRenderMode) =
Attr(fun _ b i ->
b.AddComponentRenderMode(mode)
i)
#endif
1 change: 1 addition & 0 deletions src/Bolero/Bolero.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<Compile Include="Router.fs" />
<Compile Include="ProgramRun.fs" />
<Compile Include="Components.fs" />
<Compile Include="RenderMode.fs" />
<Compile Include="Attr.fs" />
<Compile Include="Node.fs" />
<Compile Include="Virtualize.fs" />
Expand Down
42 changes: 42 additions & 0 deletions src/Bolero/RenderMode.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace Bolero
#if NET8_0_OR_GREATER

open System.Runtime.InteropServices
open Microsoft.AspNetCore.Components
open Microsoft.AspNetCore.Components.Web

type BoleroRenderMode =
/// <summary>
/// Render the component and run its interactivity on the server side.
/// </summary>
| Server = 1
/// <summary>
/// Render the component and run its interactivity on the client side.
/// </summary>
| WebAssembly = 2
/// <summary>
/// Automatically decide where to render the component and run its interactivity.
/// </summary>
| Auto = 3

/// <summary>
/// Define how a component is rendered in interactive render mode.
/// </summary>
type BoleroRenderModeAttribute
/// <summary>
/// Define how a component is rendered in interactive render mode.
/// </summary>
/// <param name="mode">The render mode.</param>
/// <param name="prerender">Whether to prerender the component in the HTML response.</param>
(mode, [<Optional; DefaultParameterValue true>] prerender: bool) =
inherit RenderModeAttribute()

/// <inheritdoc />
override val Mode : IComponentRenderMode =
match mode with
| BoleroRenderMode.Server -> InteractiveServerRenderMode(prerender)
| BoleroRenderMode.WebAssembly -> InteractiveWebAssemblyRenderMode(prerender)
| BoleroRenderMode.Auto -> InteractiveAutoRenderMode(prerender)
| _ -> failwith $"Invalid InteractiveRenderMode: {mode}"

#endif
1 change: 1 addition & 0 deletions src/Bolero/paket.references
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ FSharp.SystemTextJson

group net8
Elmish
Microsoft.AspNetCore.Components
Microsoft.AspNetCore.Components.WebAssembly
Microsoft.JSInterop.WebAssembly
Microsoft.Extensions.Http
Expand Down
26 changes: 19 additions & 7 deletions tests/Remoting.Server/Startup.fs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ open Bolero.Server
open FSharp.SystemTextJson.Swagger

module Page =
open Microsoft.AspNetCore.Components
open Microsoft.AspNetCore.Components.Web
open Bolero.Html
open Bolero.Server.Html

Expand All @@ -45,12 +47,17 @@ module Page =
``base`` { attr.href "/" }
}
body {
div { attr.id "main"; comp<MyApp> }
div { attr.id "main"; comp<MyApp> { attr.renderMode RenderMode.InteractiveAuto } }
script { attr.src "_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js" }
boleroScript
}
}

[<Route "/{*path}">]
type Page() =
inherit Bolero.Component()
override _.Render() = index

type MyApiHandler(log: ILogger<MyApiHandler>, ctx: IRemoteContext) =
inherit RemoteHandler<MyApi>()

Expand Down Expand Up @@ -91,14 +98,16 @@ type MyApiHandler(log: ILogger<MyApiHandler>, ctx: IRemoteContext) =
type Startup() =

member this.ConfigureServices(services: IServiceCollection) =
services.AddMvc() |> ignore
services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents()
|> ignore
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
|> ignore
services
.AddBoleroRemoting<MyApiHandler>()
.AddBoleroHost()
.AddServerSideBlazor()
.AddBoleroComponents()
|> ignore
services.AddSwaggerForSystemTextJson(JsonFSharpOptions()) |> ignore
services.AddEndpointsApiExplorer() |> ignore
Expand All @@ -110,13 +119,16 @@ type Startup() =
.UseSwaggerUI()
.UseRouting()
.UseAuthorization()
.UseBlazorFrameworkFiles()
.UseAntiforgery()
.UseEndpoints(fun endpoints ->
endpoints.MapBlazorHub() |> ignore
endpoints.MapBoleroRemoting()
.WithOpenApi()
|> ignore
endpoints.MapFallbackToBolero(Page.index) |> ignore)
endpoints.MapRazorComponents<Page.Page>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof<MyApp>.Assembly)
|> ignore)
|> ignore

if env.IsDevelopment() then
Expand Down

0 comments on commit abbb194

Please sign in to comment.