My elm-typescript-interop
NPM package generates type definitions that give you pretty solid type-safety and auto-completion for when you wire in an Elm app and ports from TypeScript (here’s a video demo). I’m trying to give some love to the package right now along with getting it ready for use with Elm 0.19.
And based on this discussion and the ideas from Murphy’s elm-conf talk about ports, I am trying to rethink some of the basic assumptions of elm-typescript-interop
. So instead of simply inspecting your Elm source code and finding all the port
s you declare, I’m considering using Custom Types and having the library generate an Elm file with ports for you based on your Custom Type
definition(s).
Goals
- Type-Safety - of course, the primary goal of this package is to make the bridge between Elm and
TypeScript (and probably ReasonML, Flow, etc. down the line) as failsafe
as possible. - Be opionionated to ensure nice code -
If ports are nicer when you share one port for multiple Custom Types variants,
then let’s make that the way you do things with this library. - Simplicity & Principle of least surprise - That means, generate as little code as possible.
Use types that the user defines as much as possible, and leverage the Elm compiler
as much as possible.
I want your feedback and ideas!
I would really really appreciate people’s help in brainstorming. Here are some sketches of what it might look like to generate ports and TypeScript types for Elm interop automaticaly based on a Custom Type definition. (You can see the full working prototype code here).
The user creates two files, src/Ports/LocalStorage.elm
and src/Ports/GoogleAnalytics.elm
.
module Ports.LocalStorage exposing (FromElm(..), ToElm(..))
import Json.Encode
type FromElm
= StoreItem { key : String, item : Json.Encode.Value }
| LoadItem { key : String }
| ClearItem { key : String }
type ToElm
= LoadedItem { key : String, item : Json.Encode.Value }
module Ports.GoogleAnalytics exposing (FromElm(..))
type FromElm
= TrackEvent { category : String, action : String, label : Maybe String, value : Maybe Int }
| TrackPage { path : String }
The user runs the elm-typescript-interop
CLI tool and it generates a Ports.elm
file that exposes some functions so you can send Cmd
s and Sub
s like so:
-- the Cmd looks like this
Cmd.batch
[ Ports.localStorage
(Ports.LocalStorage.StoreItem
{ key = "my-key"
, item = Json.Encode.int 123456
}
)
, Ports.localStorage (Ports.LocalStorage.LoadItem { key = "my-key" })
, Ports.googleAnalytics (Ports.GoogleAnalytics.TrackPage { path = "/" })
]
subscription =
Ports.localStorageSubscription GotLocalStorage
You now automatically get auto-completion and type-safety for the following TypeScript code:
import * as Elm from "./src/Main";
let app = Elm.Main.fullscreen();
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
app.ports.localStorageFromElm.subscribe(data => {
if (data.kind === "StoreItem") {
localStorage.setItem(data.key, JSON.stringify(data.item));
} else if (data.kind === "ClearItem") {
localStorage.removeItem(data.key);
} else if (data.kind === "LoadItem") {
const getItemString = localStorage.getItem(data.key);
if (getItemString) {
app.ports.localStorageToElm.send({
key: data.key,
item: JSON.parse(getItemString)
});
}
} else {
assertNever(data);
}
});
I would love to hear ideas on this design, possible alternatives, things that
are confusing, etc. Thank you!