Giter VIP home page Giter VIP logo

safe.react's Introduction

SAFE.React

Full Stack F# powered by ASP.NET Core on the backend and modern React on the frontend.

A lightweight alternative template to the full-fledged official SAFE Template. Lowers the entry barrier by choosing the simplest possible opinionated defaults:

  • Nuget for package management
  • FAKE build script as a console project (see ./build)
  • Saturn as server web framework
  • Fable.Remoting for client-server communications
  • Feliz to build pure React on the front-end
  • Expecto for server unit-tests project
  • Fable.Mocha for client unit-tests project (runs in Node.js when on CI servers or live during development)
  • Serilog for logging server-side stuff
  • Scalable architecture by modelling logical server-side components following Fable.Remoting protocols
  • F# Analyzers support
  • Simple application variable configuration (see below sections)

Getting Started

To start using this template, simply clone this repository or use it as template via Github UI and you are good to go.

You need to have installed:

You can use the editor of your choice to work with the repository. VS Code is recommended with the Ionide extension for F# development but the template will also work just fine with Visual Studio, Rider or Visual Studio for Mac if you prefer to work with any of those.

Running The Application

To work with and develop the application, you need to both the server and the client project running at the same time. The server application is in the server directory and the client is in client directory. To run them both, simply open two shell tabs, each for either applications then:

  Shell tab c:\project\safe-react   Shell tab c:\project\safe-react
 -------------------------------------- --------------------------------------
  > cd server                            > cd client
  > dotnet restore                       > npm install
  > dotnet run                           > npm start

As shown here below

img

The server web application starts listening for requests at http://localhost:5000 where as the client application will be hosted at http://localhost:8080 during developments. All web requests made from the front-end are automatically proxied to the backend at http://localhost:5000. In production, there will no proxy because the front-end application will be served from the backend itself.

That is unless you are hosting the backend serverless and would like to host the front-end project separately.

Available Build Targets

You can easily run the build targets as follows:

  • ./build.sh {Target} on Linux, Mac or simulated bash on Windows
  • build {Target} on Windows
  • Hitting F5 where Build.fsproj is the startup project in Visual Studio or Rider

There are a bunch of built-in targets that you can run:

  • Server builds the server in Release mode
  • Client builds the client in production mode
  • Clean cleans up cached assets from all the projects
  • ServerTests runs the server unit-tests project
  • ClientTests runs the client unit-tests project by compiling the project first and running via Mocha in node.js
  • LiveClientTests runs a standalone web application at http://localhost:8085 that shows test results from the unit tests and recompiles whenever the tests change.
  • HeadlessBrowserTests builds the test project as web application and spins up a headless browser to run the tests and report results
  • Pack builds and packs both server and client into the {solutionRoot}/dist directory after running unit tests of both projects. You can run the result application using dotnet Server.dll in the dist directory.
  • PackNoTests builds and packs both server and client projects into {solutionRoot}/dist without running tests.
  • InstallAnalyzers installs F# code analyzers. You can configure which analyzers to install from the build target itself.

Configuring application variables: Server

The server web application picks up the environment variables by default from the host machine and makes them available from an injected IConfiguration interface. However, it adds a nice feature on top which allows to add more application-specific local variables by adding a JSON file called config.json inside your server directory:

{
  "DATABASE_CONNECTIONSTRING": "ConnectionString",
  "APP_NAME": "SimplifiedSafe",
  "VERSION": "0.1.0-alpha"
}

Just including the file will allow the variables to be picked up automatically and will also be made available through the IConfiguration interface.

Configuring application variables: Client

Even the client can use build variables. Using the Config.variable : string -> string function, you can have access to the environment variables that were used when the application was compiled. Webpack will pick them up automatically by default. To use local variables other than the environment variables, you add a file called .env into the client directory. This file is a dotenv variables file and has the following format:

KEY1=VALUE1
KEY2=VALUE2
WELCOME_MESSAGE=Welcome to full-stack F#

Then from your Fable application, you can use the variables like this:

Config.variable "WELCOME_MESSAGE" // returns "Welcome to full-stack F#"

Since this file can contain variables that might contain sensitive data. It is git-ignored by default.

Scoped CSS modules support

This template uses scoped CSS modules by default. Scoped CSS modules allow you to define specific stylehsheets (modules) for specific React components without using global classes and worrying about whether they will collide or not.

Define your stylesheet in CSS or SASS like this with the extension .module.scss or .module.css:

/* File ./styles/counter.module.css */
.container {
  padding: 20px;
  color: lightblue;
}

Then, from the F# code, you can import the CSS module and use it as follows:

let private stylesheet = Stylesheet.load "./styles/counter.module.css"

Html.div [
    prop.className stylesheet.["container"]
    prop.children [
        Html.h1 "Counter"
        Html.button [ ]
    ]
]

Use private values for the stylesheets to avoid problems with React refresh

You can still use global classes for your application and use them everywhere but you have to specify that they are indeed global as follows:

/* File ./styles/global.css */
.container {
  padding: 20px;
  color: lightblue;
}

You can then import this file in your F# entry point like this:

open Fable.Core.JsInterop

importSideEffects "./styles/global.css"

Which loads the stylesheet globally.

Note that all these configured default can be easily changed in your webpack.config.js file, specifically in the rules sections of the webpack loaders. Feel free to adjust as needed.

Injecting ASP.NET Core Services

Since we are using Fable.Remoting in the template, make sure to check out the Functional Dependency Injection article from the documentation of Fable.Remoting that goes through the required steps of injecting services into the functions of Fable.Remoting APIs

F# Analyzers support

When developing the application using Ionide and VS Code, you can make use of F# analyzers that are built to detect certain types of specific pieces of code. By default the template doesn't include any analyzers but it is easy to add and install them using the InstallAnalyzers build target defined in build/Program.fs as follows:

Target.create "InstallAnalyzers" <| fun _ ->
    let analyzersPath = path [ solutionRoot; "analyzers" ]
    Analyzers.install analyzersPath [
        // Add analyzer entries to download
        // example { Name = "NpgsqlFSharpAnalyzer"; Version = "3.2.0" }
    ]

To install for example the NpgsqlFSharpAnalyzer package, simply uncomment the entry to make the code look like this:

Target.create "InstallAnalyzers" <| fun _ ->
    let analyzersPath = path [ solutionRoot; "analyzers" ]
    Analyzers.install analyzersPath [
        // Add analyzer entries to download
        { Name = "NpgsqlFSharpAnalyzer"; Version = "3.2.0" }
    ]

Then run the build target InstallAnalyzers again where it will delete the contents of analyzers directory and re-install all configured analyzers from scratch. Restart VS Code to allow Ionide to reload the installed analyzers. If you already have analyzers installed and adding new ones, you might need to do that from the terminal outside of VS Code because Ionide will lock the files in the analyzers path preventing the target from deleting the old analyzers.

IIS Support

The bundled application you get by running the Pack build target can be used directly as an application inside of IIS. Publishing on IIS requires that you make a separate Application Pool per .NET Core application with selected .NET CLR Version = No Managed Code. Then creating a new IIS Application which into the newly created Application Pool and setting the Physical Path of that Application to be the dist directory.

safe.react's People

Contributors

avestura avatar iltaysaeedi avatar mebrein avatar samuel-dufour avatar zaid-ajaj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

safe.react's Issues

Type name case sensitivity - "Identifier has already been declared" compiler error

This probably should go to Feliz issues, but it was easier for me to report it here as I had an example by hand.

When you have a ReactComponent defined in your source file like this one:

[<ReactComponent>]
let counter() = Html.div []

and then you define a Counter type above:

type Counter = { Dummy : unit }

then you end up with a compilation error even though you don't have any warnings / errors in editor:

ERROR in ./src/Components.fs.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /Users/theimowski/oss/SAFE.React/client/src/Components.fs.js: Identifier 'Counter' has already been declared (26:16)

  24 | }
  25 |
> 26 | export function Counter() {

I think the issue is that the Feliz Fable plugin renames counter to Counter to make React happy, but it collides with already existing type Counter defined above.

Not sure if you've encountered that, but if not I think this will hit a lot of users.

Auto reload when source changes

What's the tool in F# ecosystem devs typically use to have their frontend/backend auto-recompile and auto-restart when the source changes?

For eg., when I changed the counter value 10 in the backend to something else, I'd expect dotnet run to restart, but it didn't.

Suggestion - make it clear how to interact with API

Hi - thanks for sharing the template!

This is a suggestion rather than a bug.

It'd be good if there was a wired up example of client/server communication. I believe we need to use Server.api.Counter with useDeferred or similar, but I've played with various Fable templates over the last year and this wasn't obvious to me, so an example might make this easier for newcomers.

On the Fable.Remoting page the examples use Elmish, so it's hard to understand how that translates to this template.

Build is failing

Hi,

Thanks for you work on this, I just tried to run it and got this issue

image

Best approach to using Tailwind CSS?

I'm a fan of https://tailwindcss.com/ - but it is not clear how to use it in this template.

I see that there is something called 'Scoped CSS' which enables some type safety when using CSS from F#, and that's a good thing. I don't need SCSS though. Is there a way to use Tailwind CSS in my project? Tailwind extends CSS with certain syntax (notably the @apply directive), that will require preprocessing, or compiling, to final CSS.

FWIW, there is https://github.com/BamButz/msbuild-tailwindcss which does a simple compilation, but I'm not sure how well it plays with the nodejs'ish hot-reload stuff in SAFE.React.

Server fails to run on NixOS Linux

Again, on NixOS:

❯ dotnet run
System.ComponentModel.Win32Exception (2): No such file or directory
   at System.Diagnostics.Process.ForkAndExecProcess(String filename, String[] argv, String[] envp, String cwd, Boolean redirectStdin, Boolean redirectStdout, Boolean redirectStderr, Boolean setCredentials, UInt32 userId, UInt32 groupId, UInt32[] groups, Int32& stdinFd, Int32& stdoutFd, Int32& stderrFd, Boolean usesTerminal, Boolean throwOnNoExec)
   at System.Diagnostics.Process.StartCore(ProcessStartInfo startInfo)
   at System.Diagnostics.Process.Start()
   at Microsoft.DotNet.Cli.Utils.Command.Execute(Action`1 processStarted)
   at Microsoft.DotNet.Cli.Utils.Command.Execute()
   at Microsoft.DotNet.Tools.Run.RunCommand.Execute()
   at Microsoft.DotNet.Tools.Run.RunCommand.Run(String[] args)
   at Microsoft.DotNet.Cli.Program.ProcessArgs(String[] args, ITelemetry telemetryClient)
   at Microsoft.DotNet.Cli.Program.Main(String[] args)

Possible cause seen from strace output:

8369  execve("/home/srid/code/Baseline/server/bin/Debug/netcoreapp3.1/Server", ["/home/srid/code/Baseline/server/"...], 0x563eee81d4b0 /* 133 vars */) = -1 ENOENT (No such file or directory)

This binary exists, but it is not linked properly to libstdc++.so.6

❯ ldd bin/Debug/netcoreapp3.1/Server
        linux-vdso.so.1 (0x00007ffe741f5000)
        libpthread.so.0 => /nix/store/hp8wcylqr14hrrpqap4wdrwzq092wfln-glibc-2.32-37/lib/libpthread.so.0 (0x00007eff59fb7000)
        libdl.so.2 => /nix/store/hp8wcylqr14hrrpqap4wdrwzq092wfln-glibc-2.32-37/lib/libdl.so.2 (0x00007eff59fb2000)
        libstdc++.so.6 => not found
        libm.so.6 => /nix/store/hp8wcylqr14hrrpqap4wdrwzq092wfln-glibc-2.32-37/lib/libm.so.6 (0x00007eff59e6f000)
        libgcc_s.so.1 => /nix/store/hp8wcylqr14hrrpqap4wdrwzq092wfln-glibc-2.32-37/lib/libgcc_s.so.1 (0x00007eff59e55000)
        libc.so.6 => /nix/store/hp8wcylqr14hrrpqap4wdrwzq092wfln-glibc-2.32-37/lib/libc.so.6 (0x00007eff59c92000)
        /lib64/ld-linux-x86-64.so.2 => /nix/store/hp8wcylqr14hrrpqap4wdrwzq092wfln-glibc-2.32-37/lib64/ld-linux-x86-64.so.2 (0x00007eff59fda000)

To resolve this, I had to set LD_LIBRARY_PATH the Nix-way,

export LD_LIBRARY_PATH=$(nix eval --raw nixpkgs.stdenv.cc.cc.lib)/lib:$LD_LIBRARY_PATH

After doing this, ldd can resolve libstdc++.so.6 - but the binary still doesn't run.

❯ bin/Debug/netcoreapp3.1/Server
bash: bin/Debug/netcoreapp3.1/Server: No such file or directory

(Note: ldd on this binary resolves all dependencies).

Not sure what the problem here is. I'll go back to Windows for now, but leaving this here as a record of my attempts.

Error: EMFILE: too many open files, watch

First of, great work!

I have some problems when starting the app. I've followed the instructions and started the server and client separately, but I get a lot of Error: EMFILE: too many open files messages. It looks like it is watch node_modules for some reason. I'm running it on linux, can that impact watch? Is there a way to configure it?

(side comment, it doesn't looks as the gif in the readme but I guess you've made the template "slimmer")

SCSS integration doesn't work

Hello Zaid, I've tried this template and encountered a strange behaviour.

when you add a new div with some random class like

Html.h1 [
    prop.className "foo"
    prop.text count
]

and then in the main.scss

.foo {
  background-color: black;
}

the styles are no applied to the elements in the browser. But styling for body works. So I guess it is somehow connected to react fast refresh or something.

the style is written like this in the head part of the webpage

._1IMXHl1_d5q7_2L3Ic6UCR {
  background-color: black;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.