Skip to content

Commit

Permalink
Merge pull request #306 from zaaack/ssr
Browse files Browse the repository at this point in the history
Add Server-Side Rendering (SSR)
  • Loading branch information
forki authored Mar 22, 2018
2 parents c4b19c6 + 6e2acb7 commit e5f9da1
Show file tree
Hide file tree
Showing 29 changed files with 345 additions and 151 deletions.
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"*.suo":true,
"*.suo":true,
"*.user":true,
"*.sln.docstates":true,
"*.userprefs":true,
Expand All @@ -25,4 +25,4 @@
"src/**/obj":true,
"src/Client/out":true
}
}
}
4 changes: 4 additions & 0 deletions BookStore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{95D2789C-8B80-49E7-915A-AC670C36D3FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The following document describes the [SAFE-Stack](https://safe-stack.github.io/)
SAFE is a technology stack that brings together several technologies into a single, coherent stack for typesafe,
flexible end-to-end web-enabled applications that are written entirely in F#.

![SAFE-Stack](src/Client/images/safe_logo.png "SAFE-Stack")
![SAFE-Stack](src/Client/Images/safe_logo.png "SAFE-Stack")

You can see it running on Microsoft Azure at http://fable-suave.azurewebsites.net.

Expand Down Expand Up @@ -206,7 +206,7 @@ Add the `src/Client/pages/Tomato.fs` to your .fsproj file and move it above `App
4. Change the `Tomato.view` function (and add in required packages):
```fsharp
open Fable.Helpers.React
open Fable.Helpers.React.Props
//...
Expand Down
32 changes: 30 additions & 2 deletions build.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,15 @@ Target "InstallClient" (fun _ ->

Target "BuildClient" (fun _ ->
runDotnet clientPath "restore"
runDotnet clientPath "fable webpack --port free -- -p"
runDotnet clientPath "fable webpack --port free -- -p --mode production"
)

// --------------------------------------------------------------------------------------
// Rename driver for macOS or Linux

Target "RenameDrivers" (fun _ ->
if not isWindows then
run npmTool "install phantomjs-prebuilt" ""
run yarnTool "add phantomjs-prebuilt" ""
try
if isMacOS && not <| File.Exists "test/UITests/bin/Debug/net461/chromedriver" then
Fake.FileHelper.Rename "test/UITests/bin/Debug/net461/chromedriver" "test/UITests/bin/Debug/net461/chromedriver_macOS"
Expand Down Expand Up @@ -185,6 +185,7 @@ Target "RunClientTests" (fun _ ->

let ipAddress = "localhost"
let port = 8080
let serverPort = 8085

FinalTarget "KillProcess" (fun _ ->
killProcess "dotnet"
Expand Down Expand Up @@ -216,6 +217,30 @@ Target "Run" (fun _ ->
)


Target "RunSSR" (fun _ ->
runDotnet clientPath "restore"
runDotnet serverTestsPath "restore"

let unitTestsWatch = async {
let result =
ExecProcess (fun info ->
info.FileName <- dotnetExePath
info.WorkingDirectory <- serverTestsPath
info.Arguments <- sprintf "watch msbuild /t:TestAndRun /p:DotNetHost=%s /p:DebugSSR=true" dotnetExePath) TimeSpan.MaxValue

if result <> 0 then failwith "Website shut down." }

let fablewatch = async { runDotnet clientPath "fable webpack --port free -- -w --mode development" }
let openBrowser = async {
System.Threading.Thread.Sleep(10000)
Diagnostics.Process.Start("http://"+ ipAddress + sprintf ":%d" serverPort) |> ignore }

Async.Parallel [| unitTestsWatch; fablewatch; openBrowser |]
|> Async.RunSynchronously
|> ignore
)


// --------------------------------------------------------------------------------------
// Release Scripts

Expand Down Expand Up @@ -354,4 +379,7 @@ Target "All" DoNothing
"InstallClient"
==> "Run"

"InstallClient"
==> "RunSSR"

RunTargetOrDefault "All"
6 changes: 4 additions & 2 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ nuget System.Net.NetworkInformation
nuget jose-jwt

nuget Fable.Core
nuget Fable.React 3.0.0
nuget Fable.Elmish
nuget Fable.Elmish.React
nuget Fable.Elmish.Browser
nuget Fable.Elmish.Debugger
nuget Fable.Elmish.React
nuget Fable.Elmish.HMR
nuget Microsoft.Azure.WebJobs prerelease
nuget Microsoft.Azure.WebJobs.Extensions prerelease
Expand All @@ -41,4 +43,4 @@ group UITests
group Build
source https://nuget.org/api/v2
framework >= net461
nuget FAKE
nuget FAKE
8 changes: 4 additions & 4 deletions paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ NUGET
System.Collections.Immutable (>= 1.4) - restriction: >= netstandard1.6
System.Reflection.Metadata (>= 1.5) - restriction: >= netstandard1.6
System.Runtime.Loader (>= 4.3) - restriction: >= netstandard1.6
Fable.Core (1.3.8)
Fable.Core (1.3.11)
FSharp.Core (>= 4.2.3) - restriction: >= netstandard1.6
NETStandard.Library (>= 1.6.1) - restriction: >= netstandard1.6
Fable.Elmish (1.0.1) - restriction: >= netstandard1.6
Fable.Elmish (1.0.1)
Fable.Core (>= 1.2.4) - restriction: >= netstandard1.6
Fable.PowerPack (>= 1.3) - restriction: >= netstandard1.6
FSharp.Core (>= 4.2.3) - restriction: >= netstandard1.6
Expand All @@ -37,7 +37,7 @@ NUGET
Fable.Core (>= 1.2.4) - restriction: >= netstandard1.6
Fable.Elmish (>= 0.9.2) - restriction: >= netstandard1.6
FSharp.Core (>= 4.2.3) - restriction: >= netstandard1.6
Fable.Elmish.React (1.0.1)
Fable.Elmish.React (1.0.2)
Fable.Core (>= 1.3.8) - restriction: >= netstandard1.6
Fable.Elmish (>= 1.0.1) - restriction: >= netstandard1.6
Fable.PowerPack (>= 1.3.2) - restriction: >= netstandard1.6
Expand All @@ -53,7 +53,7 @@ NUGET
Fable.Core (>= 1.3.8) - restriction: >= netstandard1.6
Fable.Import.Browser (>= 1.0) - restriction: >= netstandard1.6
FSharp.Core (>= 4.2.3) - restriction: >= netstandard1.6
Fable.React (2.1) - restriction: >= netstandard1.6
Fable.React (3.0)
Fable.Core (>= 1.3.7) - restriction: >= netstandard1.6
Fable.Import.Browser (>= 0.1) - restriction: >= netstandard1.6
FSharp.Core (>= 4.2.3) - restriction: >= netstandard1.6
Expand Down
94 changes: 31 additions & 63 deletions src/Client/App.fs
Original file line number Diff line number Diff line change
@@ -1,47 +1,31 @@
module Client.App

open Fable.Core
open Fable.Core.JsInterop

open Fable.Import
open Fable.PowerPack
open Elmish
open Elmish.React
open Fable.Import.Browser
open Elmish.Browser.Navigation
open Elmish.HMR
open Client.Shared
open Client.Pages
open ServerCode.Domain

JsInterop.importSideEffects "whatwg-fetch"
JsInterop.importSideEffects "babel-polyfill"

/// The composed model for the different possible page states of the application
type PageModel =
| HomePageModel
| LoginModel of Login.Model
| WishListModel of WishList.Model

/// The composed model for the application, which is a single page state plus login information
type Model =
{ User : UserData option
PageModel : PageModel }

/// The composed set of messages that update the state of the application
type Msg =
| LoggedIn of UserData
| LoggedOut
| StorageFailure of exn
| LoginMsg of Login.Msg
| WishListMsg of WishList.Msg
| Logout of unit

/// The navigation logic of the application given a page identity parsed from the .../#info
let handleNotFound (model: Model) =
Browser.console.error("Error parsing url: " + Browser.window.location.href)
( model, Navigation.modifyUrl (toPath Page.Home) )

/// The navigation logic of the application given a page identity parsed from the .../#info
/// information in the URL.
let urlUpdate (result:Page option) model =
let urlUpdate (result:Page option) (model: Model) =
match result with
| None ->
Browser.console.error("Error parsing url: " + Browser.window.location.href)
( model, Navigation.modifyUrl (toHash Page.Home) )
handleNotFound model

| Some Page.Login ->
let m, cmd = Login.init model.User
Expand Down Expand Up @@ -69,11 +53,17 @@ let deleteUserCmd =

let init result =
let user = loadUser ()
let model =
{ User = user
PageModel = HomePageModel }

urlUpdate result model
let stateJson: string option = !!Browser.window?__INIT_MODEL__
match stateJson, result with
| Some json, Some Page.Home ->
let model: Model = ofJson json
{ model with User = user }, Cmd.none
| _ ->
let model =
{ User = user
PageModel = HomePageModel }

urlUpdate result model

let update msg model =
match msg, model.PageModel with
Expand All @@ -89,7 +79,7 @@ let update msg model =
| Login.ExternalMsg.NoOp ->
Cmd.none
| Login.ExternalMsg.UserLoggedIn newUser ->
Cmd.ofMsg (LoggedIn newUser)
saveUserCmd newUser

{ model with
PageModel = LoginModel m },
Expand All @@ -104,53 +94,31 @@ let update msg model =
{ model with
PageModel = WishListModel m }, Cmd.map WishListMsg cmd

| WishListMsg _, _ ->
| WishListMsg _, _ ->
model, Cmd.none

| LoggedIn newUser, _ ->
let nextPage = Page.WishList
{ model with User = Some newUser },
Cmd.batch [
saveUserCmd newUser
Navigation.newUrl (toHash nextPage) ]
Navigation.newUrl (toPath nextPage)

| LoggedOut, _ ->
{ model with
User = None
PageModel = HomePageModel },
Navigation.newUrl (toHash Page.Home)
PageModel = HomePageModel },
Navigation.newUrl (toPath Page.Home)

| Logout(), _ ->
model, deleteUserCmd

// VIEW

open Fable.Helpers.React
open Fable.Helpers.React.Props
open Client.Style

/// Constructs the view for a page given the model and dispatcher.
let viewPage model dispatch =
match model.PageModel with
| HomePageModel ->
Home.view ()

| LoginModel m ->
[ Login.view m (LoginMsg >> dispatch) ]

| WishListModel m ->
[ WishList.view m (WishListMsg >> dispatch) ]
open Elmish.Debug

/// Constructs the view for the application given the model.
let view model dispatch =
div [] [
Menu.view (Logout >> dispatch) model.User
hr []
div [ centerStyle "column" ] (viewPage model dispatch)
]
let withReact =
if (!!Browser.window?__INIT_MODEL__)
then Program.withReactHydrate
else Program.withReact

open Elmish.React
open Elmish.Debug

// App
Program.mkProgram init update view
Expand All @@ -159,7 +127,7 @@ Program.mkProgram init update view
|> Program.withConsoleTrace
|> Program.withHMR
#endif
|> Program.withReact "elmish-app"
|> withReact "elmish-app"
#if DEBUG
|> Program.withDebugger
#endif
Expand Down
10 changes: 1 addition & 9 deletions src/Client/Client.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,8 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<Import Project="../Shared/Shared.props" />
<ItemGroup>
<Compile Include="ReleaseNotes.fs" />
<Compile Include="../Server/Shared/Domain.fs" />
<Compile Include="../Server/Shared/ServerUrls.fs" />
<Compile Include="Pages.fs" />
<Compile Include="Style.fs" />
<Compile Include="views/Menu.fs" />
<Compile Include="pages/Home.fs" />
<Compile Include="pages/WishList.fs" />
<Compile Include="pages/Login.fs" />
<Compile Include="App.fs" />
</ItemGroup>
<Import Project="..\..\.paket\Paket.Restore.targets" />
Expand Down
File renamed without changes
File renamed without changes
14 changes: 7 additions & 7 deletions src/Client/Pages.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ open Elmish.Browser.UrlParser

/// The different pages of the application. If you add a new page, then add an entry here.
[<RequireQualifiedAccess>]
type Page =
type Page =
| Home
| Login
| WishList

let toHash =
let toPath =
function
| Page.Home -> "#home"
| Page.Login -> "#login"
| Page.WishList -> "#wishlist"
| Page.Home -> "/"
| Page.Login -> "/login"
| Page.WishList -> "/wishlist"


/// The URL is turned into a Result.
let pageParser : Parser<Page -> Page,_> =
oneOf
[ map Page.Home (s "home")
[ map Page.Home (s "")
map Page.Login (s "login")
map Page.WishList (s "wishlist") ]

let urlParser location = parseHash pageParser location
let urlParser location = parsePath pageParser location
Loading

0 comments on commit e5f9da1

Please sign in to comment.