Skip to content

Commit bc7e973

Browse files
authored
Merge branch 'master' into v5-docs-various-fixes
2 parents 153c097 + cebcd20 commit bc7e973

File tree

5 files changed

+276
-1
lines changed

5 files changed

+276
-1
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# How do I add build automation to the project?
2+
3+
## FAKE
4+
[Fake](https://fake.build/) is a DSL for build tasks that is modular, extensible and easy to start with. Fake allows you to easily build, bundle, deploy your app and more by executing a single command.
5+
6+
> The standard template comes [with a FAKE project](../../../template-safe-commands) by default, so **this recipe only applies to the minimal template**.
7+
8+
---
9+
#### 1. Create a build project
10+
11+
Create a new console app called 'Build' at the root of your solution
12+
13+
```fsharp
14+
dotnet new console -lang f# -n Build -o .
15+
```
16+
17+
> We are creating the project directly at the root of the solution in order to allow us to execute the build without needing to navigate into a subfolder.
18+
19+
#### 2. Create a build script
20+
21+
Open the project you just created in your IDE and rename the module it contains from `Program.fs` to `Build.fs`.
22+
23+
This renaming isn't explicitly necessary, however it keeps your solution consistent with other SAFE apps and is a better name for the file really.
24+
25+
> If you just rename the file directly rather than in your IDE, then the Build project won't be able to find it unless you edit the Build.fsproj file as well
26+
27+
Open `Build.fs` and paste in the following code.
28+
29+
```fsharp
30+
open Fake.Core
31+
open Fake.IO
32+
open System
33+
34+
let redirect createProcess =
35+
createProcess
36+
|> CreateProcess.redirectOutputIfNotRedirected
37+
|> CreateProcess.withOutputEvents Console.WriteLine Console.WriteLine
38+
39+
let createProcess exe arg dir =
40+
CreateProcess.fromRawCommandLine exe arg
41+
|> CreateProcess.withWorkingDirectory dir
42+
|> CreateProcess.ensureExitCode
43+
44+
let dotnet = createProcess "dotnet"
45+
46+
let npm =
47+
let npmPath =
48+
match ProcessUtils.tryFindFileOnPath "npm" with
49+
| Some path -> path
50+
| None -> failwith "npm was not found in path."
51+
createProcess npmPath
52+
53+
let run proc arg dir =
54+
proc arg dir
55+
|> Proc.run
56+
|> ignore
57+
58+
let execContext = Context.FakeExecutionContext.Create false "build.fsx" [ ]
59+
Context.setExecutionContext (Context.RuntimeContext.Fake execContext)
60+
61+
Target.create "Clean" (fun _ -> Shell.cleanDir (Path.getFullName "deploy"))
62+
63+
Target.create "InstallClient" (fun _ -> run npm "install" ".")
64+
65+
Target.create "Run" (fun _ ->
66+
run dotnet "build" (Path.getFullName "src/Shared")
67+
[ dotnet "watch run" (Path.getFullName "src/Server")
68+
dotnet "fable watch --run npx vite" (Path.getFullName "src/Client") ]
69+
|> Seq.toArray
70+
|> Array.map redirect
71+
|> Array.Parallel.map Proc.run
72+
|> ignore
73+
)
74+
75+
open Fake.Core.TargetOperators
76+
77+
let dependencies = [
78+
"Clean"
79+
==> "InstallClient"
80+
==> "Run"
81+
]
82+
83+
[<EntryPoint>]
84+
let main args =
85+
try
86+
match args with
87+
| [| target |] -> Target.runOrDefault target
88+
| _ -> Target.runOrDefault "Run"
89+
0
90+
with e ->
91+
printfn "%A" e
92+
1
93+
```
94+
95+
#### 3. Add the project to the solution
96+
97+
Run the following command
98+
99+
```bash
100+
dotnet sln add Build.fsproj
101+
```
102+
#### 4. Installing dependencies
103+
104+
You will need to install the following dependencies:
105+
106+
```
107+
Fake.Core.Target
108+
Fake.IO.FileSystem
109+
```
110+
111+
We recommend migrating to [Paket](https://fsprojects.github.io/Paket/).
112+
It is possible to use FAKE without Paket, however this will not be covered in this recipe.
113+
114+
#### 5. Run the app
115+
116+
At the root of the solution, run `dotnet paket install` to install all your dependencies.
117+
118+
If you now execute `dotnet run`, the default target will be run. This will build the app in development mode and launch it locally.
119+
120+
To learn more about targets and FAKE in general, see [Getting Started with FAKE](https://fake.build/guide/getting-started.html#Minimal-Example).
121+

docs/recipes/build/docker-image.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# How do I build with docker?
2+
3+
Using [Docker](https://www.docker.com/) makes it possible to deploy your application as a docker container or release an image on docker hub. This recipe walks you through creating a `Dockerfile` and automating the build and test process with [Docker Hub](https://hub.docker.com/).
4+
5+
#### 1. Create a .dockerignore file
6+
7+
Create a `.dockerignore` file with the same contents as `.gitignore`
8+
9+
##### Linux
10+
```bash
11+
cp .gitignore .dockerignore
12+
```
13+
##### Windows
14+
```bash
15+
copy .gitignore .dockerignore
16+
```
17+
18+
Now, add the following lines to the `.dockerignore` file:
19+
20+
```
21+
.git
22+
```
23+
24+
#### 2. Create the dockerfile
25+
26+
Create a `Dockerfile` with the following contents:
27+
28+
```dockerfile
29+
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
30+
31+
# Install node
32+
ARG NODE_MAJOR=20
33+
RUN apt-get update
34+
RUN apt-get install -y ca-certificates curl gnupg
35+
RUN mkdir -p /etc/apt/keyrings
36+
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
37+
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
38+
RUN apt-get update && apt-get install nodejs -y
39+
40+
WORKDIR /workspace
41+
COPY . .
42+
RUN dotnet tool restore
43+
RUN dotnet run Bundle
44+
45+
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
46+
COPY --from=build /workspace/deploy /app
47+
WORKDIR /app
48+
EXPOSE 5000
49+
ENTRYPOINT [ "dotnet", "Server.dll" ]
50+
```
51+
52+
This uses [multistage builds](https://docs.docker.com/develop/develop-images/multistage-build/) to keep the final image small.
53+
54+
#### 3. Building and running with docker locally
55+
56+
1. Build the image `docker build -t my-safe-app .`
57+
2. Run the container `docker run -it -p 8080:8080 my-safe-app`
58+
3. Open the page in browser at [http://localhost:8080](http://localhost:8080)
59+
60+
61+
62+
> Because the build is done entirely in docker, Docker Hub [automated builds](https://docs.docker.com/docker-hub/builds/) can be setup to automatically build and push the docker image.
63+
64+
#### 4. Testing the server
65+
Create a `docker-compose.server.test.yml` file with the following contents:
66+
67+
```yml
68+
version: '3.4'
69+
services:
70+
sut:
71+
build:
72+
target: build
73+
context: .
74+
working_dir: /workspace/tests/Server
75+
command: dotnet run
76+
```
77+
To run the tests execute the command `docker-compose -f docker-compose.server.test.yml up --build`
78+
79+
> The template is not currently setup for automating the client tests in ci.
80+
81+
> Docker Hub can also run [automated tests](https://docs.docker.com/docker-hub/builds/automated-testing/) for you.
82+
83+
> Follow [the instructions to enable Autotest](https://docs.docker.com/docker-hub/builds/automated-testing/#enable-automated-tests-on-a-repository) on docker hub.
84+
85+
#### 5. Making the docker build faster
86+
87+
> Not recommended for most applications
88+
89+
If you often build with docker locally, you may wish to make the build faster by optimising the Dockerfile for caching. For example, it is not necessary to download all paket and npm dependencies on every build unless there have been changes to the dependencies.
90+
91+
Furthermore, the client and server can be built in separate build stages so that they are cached independently. Enable [Docker BuildKit](https://docs.docker.com/develop/develop-images/build_enhancements/) to build them concurrently.
92+
93+
This comes at the expense of making the dockerfile more complex; if any changes are made to the build such as adding new projects or migrating package managers, the dockerfile must be updated accordingly.
94+
95+
The following should be a good starting point but is not guaranteed to work.
96+
97+
```dockerfile
98+
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
99+
100+
# Install node
101+
ARG NODE_MAJOR=20
102+
RUN apt-get update
103+
RUN apt-get install -y ca-certificates curl gnupg
104+
RUN mkdir -p /etc/apt/keyrings
105+
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
106+
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
107+
RUN apt-get update && apt-get install nodejs -y
108+
109+
WORKDIR /workspace
110+
COPY .config .config
111+
RUN dotnet tool restore
112+
COPY .paket .paket
113+
COPY paket.dependencies paket.lock ./
114+
115+
FROM build as server-build
116+
COPY src/Shared src/Shared
117+
COPY src/Server src/Server
118+
RUN cd src/Server && dotnet publish -c release -o ../../deploy
119+
120+
FROM build as client-build
121+
COPY package.json package-lock.json ./
122+
RUN npm install
123+
COPY src/Shared src/Shared
124+
COPY src/Client src/Client
125+
# tailwind.config.js needs to be in the dir where the
126+
# vite build command is run from otherwise styles will
127+
# be missing from the bundle
128+
COPY src/Client/tailwind.config.js .
129+
RUN dotnet fable src/Client --run npx vite build src/Client
130+
131+
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
132+
COPY --from=server-build /workspace/deploy /app
133+
COPY --from=client-build /workspace/deploy /app
134+
WORKDIR /app
135+
EXPOSE 5000
136+
ENTRYPOINT [ "dotnet", "Server.dll" ]
137+
```
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# How do I add a NuGet package to the Client?
2+
Adding packages to the Client project is a very [similar process to the Server](../add-nuget-package-to-server), with a few key differences:
3+
4+
- Any references to the `Server` directory should be `Client`
5+
6+
- Client code written in F# is converted into JavaScript using [Fable](https://fable.io/docs/index.html). Because of this, we must be careful to only reference libraries which are [Fable compatible](https://fable.io/docs/your-fable-project/use-a-fable-library.html).
7+
8+
- If the NuGet package uses any JS libraries you must install them.
9+
For simplicity, [use Femto to sync](./sync-nuget-and-npm-packages.md) - if the NuGet package is compatible - or [install via NPM](./add-npm-package-to-client.md) manually, if not.
10+
11+
There are [lots of great libraries](../../awesome-safe-components.md) available to choose from.

docs/v4-recipes/build/add-build-script.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ dotnet new console -lang f# -n Build -o .
2020

2121
Open the project you just created in your IDE and rename the module it contains from `Program.fs` to `Build.fs`.
2222

23-
This renaming is't explicitly necessary, however it keeps your solution consistent with other SAFE apps and is a better name for the file really.
23+
This renaming isn't explicitly necessary, however it keeps your solution consistent with other SAFE apps and is a better name for the file really.
2424

2525
> If you just rename the file directly rather than in your IDE, then the Build project won't be able to find it unless you edit the Build.fsproj file as well
2626

mkdocs.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ nav:
7070
- Upgrade from V4 to V5: "recipes/upgrading/v4-to-v5.md"
7171
- Create a new Recipe: "recipes/template.md"
7272
- Build:
73+
- Add build automation: "recipes/build/add-build-script.md"
74+
- Create a docker image: "recipes/build/docker-image.md"
7375
- Remove FAKE: "recipes/build/remove-fake.md"
7476
- Package my SAFE app for deployment: "recipes/build/bundle-app.md"
7577
- UI:
@@ -87,6 +89,7 @@ nav:
8789
- Package Management:
8890
- Add an NPM package to the Client: "recipes/package-management/add-npm-package-to-client.md"
8991
- Add a NuGet package to the Server: "recipes/package-management/add-nuget-package-to-server.md"
92+
- Add a NuGet package to the Client: "recipes/package-management/add-nuget-package-to-client.md"
9093
- Migrate to Paket from NuGet: "recipes/package-management/migrate-to-paket.md"
9194
- Migrate to NuGet from Paket: "recipes/package-management/migrate-to-nuget.md"
9295
- Sync NuGet and NPM Packages: "recipes/package-management/sync-nuget-and-npm-packages.md"
@@ -151,3 +154,6 @@ nav:
151154
- Add Support for a Third Party React Library: "v4-recipes/javascript/third-party-react-package.md"
152155
- Package Management:
153156
- Add a NuGet package to the Server: "v4-recipes/package-management/add-nuget-package-to-server.md"
157+
- Migrate to Paket from NuGet: "v4-recipes/package-management/migrate-to-paket.md"
158+
- Migrate to NuGet from Paket: "v4-recipes/package-management/migrate-to-nuget.md"
159+
- Sync NuGet and NPM Packages: "v4-recipes/package-management/sync-nuget-and-npm-packages.md"

0 commit comments

Comments
 (0)