type Props =
{name: string;}
Full name: index.Props
Props.name: string
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
Multiple items
type FSComponent =
inherit obj
new : props:Props -> FSComponent
override render : unit -> 'a
Full name: index.FSComponent
--------------------
new : props:Props -> FSComponent
val props : Props
val self : FSComponent
type unit = Unit
Full name: Microsoft.FSharp.Core.unit
override FSComponent.render : unit -> 'a
Full name: index.FSComponent.render
val sprintf : format:Printf.StringFormat<'T> -> 'T
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
type Model =
{image: string;
angle: float;}
Full name: index.Model
Model.image: string
Model.angle: float
Multiple items
val float : value:'T -> float (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.float
--------------------
type float = System.Double
Full name: Microsoft.FSharp.Core.float
--------------------
type float<'Measure> = float
Full name: Microsoft.FSharp.Core.float<_>
type Msg =
| Left of float
| Right of float
| Reset
Full name: index.Msg
union case Msg.Left: float -> Msg
union case Msg.Right: float -> Msg
union case Msg.Reset: Msg
val init : img:string option -> Model
Full name: index.init
val img : string option
val img : string
val defaultArg : arg:'T option -> defaultValue:'T -> 'T
Full name: Microsoft.FSharp.Core.Operators.defaultArg
val update : msg:Msg -> model:Model -> Model
Full name: index.update
val msg : Msg
val model : Model
val a : float
val view : model:Model -> dispatch:'a -> 'b
Full name: index.view
val dispatch : 'a
type 'T option = Option<'T>
Full name: Microsoft.FSharp.Core.option<_>
val ImgSpinner : props:'a -> 'b
Full name: index.ImgSpinner
val props : 'a
First,
a little background...
What is React?
- Component based JavaScript view library
- Pioneer of the pure view function
What is Babel?
- Babel is a JavaScript to JavaScript compiler (Wait, what?)
- Lets you use new JavaScript syntax on older browsers
What is Fable?
- Fable is an F# to JavaScript compiler powered by Babel
- Fable is not .NET in the browser (see Bolero)
- Fable leverages the JavaScript ecosystem
Atwood's Law:
any application that can be written in JavaScript, will eventually be written in JavaScript.
What is Elmish?
- Brings the MVU / Elm programming model to F#
-
The recommended (but not only) way to build UI with Fable
- Specifically: Fable.Elmish.React
- The E in S.A.F.E stack
- Also used in Fabulous
What is NPM?
- The package manager for JavaScript
- npmjs.com is the repository
- Yarn is an alternative front end
Lets
Publish React Components with Fable
- Create a React component
- Test it (hopefully?)
- Publish it on NPM
Full Participation
- Fable 1.0 was released in 2017
- Fable leverages JavaScript ecosystem to great effect
- Mostly a "read only" member of the larger JavaScript community
Try Before you Buy
- Elmish is amazing for green field work
- We don't always have that luxury
Low Risk
- Write a single component in F#
- Team doesn't even need Fable
The secret to building large apps is never to build large apps. - Justin Meyer
Spread F#
- Publish something useful.
- Wait for users to look at the source.
- Some will learn enough to contribute back.
React in F#
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
|
open Fable.React
type Props = { name : string }
type FSComponent(props : Props) as self =
inherit PureComponent<Props,unit>(props) with
do self.setInitState()
override __.render() =
div [] [
str (sprintf "Hello %s" props.name)]
|
- Props & State
- Component / PureComponent
- render()
- HTML DSL 👌
App.js
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
|
import React, { Component } from 'react';
import { FSComponent } from 'fs-react-sample-component'
class App extends Component {
render() {
return (
<div className="App">
<FSComponent name="from F#" />
</div>);
}
}
|
Publish to NPM
- yarn build
-
yarn publish
- or -
- npm run build
- npm publish
Consume from NPM
yarn add fs-react-sample-component
- or -
npm install --save fs-react-sample-component
For Development
1:
2:
3:
4:
5:
6:
7:
|
cd fs-react-sample-component
npm link
yarn start
cd js-test
npm link fs-react-sample-component
yarn start
|
- Automatic reload across both projects
How Much Overhead?
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
|
Asset Size Chunks Chunk Names
index.js 16.9 KiB 0 [emitted] main
Entrypoint main = index.js
[0] external "react" 42 bytes {0} [built]
[11] sample/src/fs-react-sample-component.fsproj + 9 modules 106 KiB {0} [built]
| sample/src/sample.fsproj 27 bytes [built]
| sample/src/index.fs 1.51 KiB [built]
| sample/.fable/fable-library.2.2.3/Types.js 7.62 KiB [built]
| sample/.fable/fable-library.2.2.3/Reflection.js 7.14 KiB [built]
| sample/.fable/fable-library.2.2.3/String.js 16 KiB [built]
| sample/.fable/fable-library.2.2.3/Util.js 17 KiB [built]
| sample/.fable/fable-library.2.2.3/Long.js 34.6 KiB [built]
| sample/.fable/fable-library.2.2.3/Date.js 14.3 KiB [built]
| sample/.fable/fable-library.2.2.3/RegExp.js 3.56 KiB [built]
| sample/.fable/fable-library.2.2.3/Int32.js 4.04 KiB [built]
+ 10 hidden modules
Done in 14.64s.
|
17 KiB before gzip
A Simple Elmish App
1:
2:
3:
4:
5:
6:
7:
8:
|
type Model = {
image : string
angle : float
}
type Msg =
| Left of float
| Right of float
| Reset
|
...
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
|
let init img =
let img = defaultArg img "https://southernfriedfsharp.com/sfflogo_large2.png"
{ image = img; angle = 0.0 }
let update msg model =
match msg with
| Left a -> { model with angle = model.angle - a }
| Right a -> { model with angle = model.angle + a }
| Reset -> { model with angle = 0.0 }
let view (model:Model) dispatch =
div[] [
img [
Src model.image
Style [
Width 200
Height 200
Transform (sprintf "rotate(%fdeg)" model.angle) ] ]
]
|
An Adapter
1:
2:
3:
4:
5:
6:
|
open Fable.React
type Props = { src : string option }
let ImgSpinner props =
Adapter.toComponent
(fun p -> Counter.init p.src) Counter.update Counter.view props
|
Adapter.fs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
|
module Adapter
open Fable.React
type GenericComponent<'p,'s,'m>(
init:'p->'s,update:'m->'s->'s,view,props) as self =
inherit Component<'p,'s>(props) with
do self.setInitState (init props)
member __.update msg =
let state' = update msg self.state
self.setState (fun _ _ -> state')
override __.render() =
view self.state self.update
let toComponent init update view props =
GenericComponent(init,update,view,props)
|
Word of Warning
- This is not a well trodden path (yet)
- There may be bumps 🤜🤛
- The community is very helpful
- Find me on F# Slack