We are often asked what the deployment story is like for WebSharper applications. Our usual reply has long been that WebSharper applications follow standard formats, in particular:
- Client-Server Web Applications are simply ASP.NET applications, and can be deployed using usual methods such as publishing from Visual Studio or using Azure's git integration;
- HTML Applications are composed of static files located in
bin/htmlby default) and can be deployed from there using the method of your choice.
- Single-Page Applications are also composed of static files,
Contentfolder, so the same methods apply.
However there can be some caveats, in particular with regards to running the F# compiler and referencing FSharp.Core.dll in build-and-deploy environments.
Fortunately, the recently released NuGet package FSharp.Compiler.Tools combined with the excellent package manager Paket now provide a nice and streamlined development and deployment experience for git-hosted, Azure-deployed applications.
This article presents the build and deployment setup for a reimplementation of the popular game 2048, available on GitHub. To try it out, simply click the button "Deploy to Azure" and follow the instructions.
This particular project was created as a Single-Page Application; this project type was chosen because the application runs on a single page and is only composed of client-side code. The solution,
2048.sln, contains a single project located at
If you want to recreate this setup, you can create a Single-Page Application from Visual Studio or Xamarin Studio / MonoDevelop with the WebSharper extension installed. This deployment setup will also work if you create a Client-Server Application or an HTML Application instead. For Self-Hosted Client-Server Applications, you will additionally need to set up an HttpPlatformHandler to run the generated executable, similarly to Scott Hanselman's Suave setup.
For package management, the project uses Paket. It offers many advantages over traditional NuGet, which you can read about here.
paket restore is run in the build script before running MSBuild. Indeed, since we will be importing several
.targets files that come from packages, the packages must be restored before running MSBuild or opening the project in an IDE. So for your first build after cloning the 2048 project, you can either run the full
build.cmd, or if you only want to restore the packages, you can run:
.paket/paket.bootstrapper.exe && .paket/paket.exe restore
If you want to reproduce this setup for your own project as created in the previous section, here are the steps:
Remove the WebSharper NuGet package from the project and delete the file
<your_project_name>/packages.configif it exists.
paket.targetsfrom here into the folder
To ensure that you build with the right package versions after a git pull, add the following to
<Import Project="..\.paket\paket.targets" />
Run the following commands:
1 2 3 4 5 6
# Download paket.exe: .paket/bootstrapper.exe # Initialize paket.dependencies: .paket/paket.exe init # Install the WebSharper package into your project: .paket/paket.exe add nuget WebSharper project <your_project_name>
<your_project_name>/paket.referencesmust be committed.
The F# Compiler
fsc is not available on Azure, we retrieve it from NuGet. We reference the package
FSharp.Compiler.Tools which contains the compiler toolchain. By importing
tools/Microsoft.FSharp.targets from this package in our project file, we instruct MSBuild to use the F# compiler from the package. This means that even when building locally,
fsc from the package will be used. This ensures consistency between local and deployment builds.
If you want to apply this change to your own project, here are the steps:
Install the F# compiler package:
.paket/paket.exe add nuget FSharp.Compiler.Tools
- Use it in your project: in
Remove any references to
FSharp.Core.dll. In a Visual Studio-created project, this means removing this whole block:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<!-- F# targets --> <Choose> <When Condition="'$(VisualStudioVersion)' == '11.0'"> <PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')"> <FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath> </PropertyGroup> </When> <Otherwise> <PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')"> <FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath> </PropertyGroup> </Otherwise> </Choose> <Import Project="$(FSharpTargetsPath)" />
and add this line instead:
<Import Project="..\packages\FSharp.Compiler.Tools\tools\Microsoft.FSharp.targets" />
You now have a project that should build and run fine locally. Try it out!
Now, on to the deployment setup itself. We will be using a custom build script, so we need to tell so in the
[config] command = build.cmd
build.cmd script itself is in three parts:
Package restore: we retrieve
paket.exeif it hasn't already been retrieved, and run it to
1 2 3 4 5
if not exist .paket\paket.exe ( .paket\paket.bootstrapper.exe ) .paket\paket.exe restore
Build: Azure conveniently points the environment variable
MSBUILD_PATHto the path to
MSBuild.exe; in order to be also able to run this script locally, we check for it and set it to the standard installation location if it doesn't exist. Then, we run it.
1 2 3 4 5
if "%MSBUILD_PATH%" == "" ( set MSBUILD_PATH="%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild.exe" ) %MSBUILD_PATH% /p:Configuration=Release
Deploy: Deploying the application simply consists in copying the application files to the Azure-provided
DEPLOYMENT_TARGETfolder. The actual file in the 2048 repository is a bit more complex than necessary for Azure because it is also used on AppVeyor to deploy the application to github-pages. But a simple implementation can just copy all files and subdirectories from the project directory to
1 2 3
if not "%DEPLOYMENT_TARGET%" == "" ( xcopy /y /e <your_project_name> "%DEPLOYMENT_TARGET%" )
As a recap, here is the full
build.cmd with some extra error management:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
@ECHO OFF setlocal echo ====== Restoring packages... ====== if not exist .paket\paket.exe ( .paket\paket.bootstrapper.exe ) .paket\paket.exe restore if not %ERRORLEVEL% == 0 ( echo ====== Failed to restore packages. ====== exit 1 ) echo ====== Building... ====== if "%MSBUILD_PATH%" == "" ( set MSBUILD_PATH="%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild.exe" ) %MSBUILD_PATH% /p:Configuration=Release if not %ERRORLEVEL% == 0 ( echo ====== Build failed. ====== exit 1 ) if not "%DEPLOYMENT_TARGET%" == "" ( echo ====== Deploying... ====== xcopy /y /e <your_project_name> "%DEPLOYMENT_TARGET%" ) echo ====== Done. ======
And there you have it! A WebSharper application easily deployed to Azure with a simple configuration and consistent build setup between local and deployed.
Note that this particular example is a Single-Page Application, but the same setup can be used for Client-Server Applications and HTML Applications. For the latter, make sure to copy the WebSharperHtmlDirectory (
<your_project_name>/bin/html by default) in the final step rather than the project folder itself.
Thanks to Steffen Forkmann and Don Syme for their quick response on creating the FSharp.Compiler.Tools NuGet package, and to Scott Hanselman for his Suave Azure deployment tutorial which has been of great help to create this one despite the fairly different final setup.