ToElm Ports

  1. 1. What Is elm-ts-interop?
    2:57
  2. 2. Setup
    2:10
  3. 3. Flags
    2:52
  4. 4. FromElm Ports
    5:44
  5. 5. Encoding Custom Types
    9:54
  6. 6. Exhaustive Ports
    6:30
  7. 7. ToElm Ports
    11:10
  8. 8. Migrating to Pro
    9:32

Chapter Notes

The source of truth is the exposed interop record in InteropDefinitions.elm.

import TsJson.Decode as TsDecode exposing (Decoder)
import TsJson.Encode as TsEncode exposing (Encoder)

interop :
  { toElm : Decoder ToElm
  , fromElm : Encoder FromElm
  , flags : Decoder Flags
  }

The names of the types, ToElm, FromElm, and Flags are just a convention. elm-ts-interop only needs a record with an Encoder (fromElm) and Decoders (toElm and flags).

Wire in ToElm in subscriptions#

toElm : Sub (Result Json.Decode.Error InteropDefintions.ToElm)

  • We still need to handle decoding errors because it's possible to bypass the TypeScript compiler, use any types, etc, see [TypeScript's Blind Spots].

  • It's possible to use Elm types besides Json.Decode.Value directly in vanilla ports and flags. But your application will crash if your flag types are incorrect, and you then have no way to handle those errors in your Elm code. It also doesn't allow you to handle custom types, or decouple the Elm types from the shape of the JSON. So decoding using JSON decoders is considered a best practice, and elm-ts-interop builds on this approach and adds extra type-safety and convience.


type Msg =
    AuthenticatedUser (Result Decode.Error InteropDefinitions.ToElm)
    -- | ...


subscriptions : Model -> Sub Msg
subscriptions _ =
    InteropPorts.toElm |> Sub.map AuthenticatedUser

Decoding the avatarUrl field in ToElm#

type ToElm
    = AuthenticatedUser User


type alias User =
    { username : String
    , avatarUrl : String
    }


toElm : Decoder ToElm
toElm =
    TsDecode.discriminatedUnion "tag"
        [ ( "authenticatedUser"
          , TsDecode.map AuthenticatedUser
                (TsDecode.field "user"
                    (TsDecode.map2 User
                        (TsDecode.field "username" TsDecode.string)
                        (TsDecode.field "avatarUrl" TsDecode.string)
                    )
                )
          )
        ]

ViteJS and TypeScript#

  • For performance reasons, ViteJS transpiles your TypeScript code but doesn't typecheck it in the dev server. This lets your editor tooling and local setup run the TypeScript compiler however it's convenient in your workflow without slowing the dev server down. So be sure to run the TypeScript compiler on your builds before your code goes to production.

More Resources#