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

Publish React Components with Fable

Eric Harding
@digitalsorcery
https://blog.digitalsorcery.net

BTS SFF

First,

a little background...

What is React?

  • Component based JavaScript view library
  • Pioneer of the pure view function

What is F#?

What is Babel?

  • Babel is a JavaScript to JavaScript compiler (Wait, what?)
  • Lets you use new JavaScript syntax on older browsers

Compiling

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

OK, now what?

Oh, yea

Lets

Publish React Components with Fable

  • Create a React component
  • Test it (hopefully?)
  • Publish it on NPM

But Why?

  • Is this a good idea?

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#

  1. Publish something useful.
  2. Wait for users to look at the source.
  3. Some will learn enough to contribute back.

But How?

I'm glad you asked.

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

  • Peer dependency on React

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

What About Elmish?

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
  • React and MVU 🤗

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)

Demo

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

Questions?

The End