Community Edition Setup Guide
Install the CLI
npm install --save-dev elm-ts-interop
Run the init
Command
npx elm-ts-interop init
This will give you two files, InteropPorts.elm
and InteropDefinitions.elm
.
InteropDefinitions.elm
is where you maintain your type-safeelm-ts-json
Encoder
s andDecoder
s. This is the source of truth for the generated TypeScript types. This generated file is just a starting point for you to build off of.InteropPorts.elm
is a file to help you safely use yourelm-ts-interop
ports and flags. You probably don't want to modify it, but do keep it in your version control system.
Choose a filename for your generated TypeScript Declarations
elm-ts-interop --output <FILE-NAME-FROM-INSTRUCTIONS-BELOW>
Figure out the style of your imports you use to pull Elm in from JavaScript/TypeScript.
- No
.elm
extension (Webpack usually does this style)import { Elm } from "./src/MyMainModule"
orrequire("./src/MyMainModule")
- With
.elm
extension (ViteJS usually does this style)import { Elm } from "./src/MyMainModule.elm"
orrequire("./src/MyMainModule.elm")
- HTML script tag
<script src="/path/to/compiled/elm.js">
Based on the import style, use the corresponding setup:
elm-ts-interop --output src/MyMainModule/index.d.ts
. The types should be automatically picked up from your import/require statement.elm-ts-interop --output src/MyMainModule.elm.d.ts
. The types should be automatically picked up from your import/require statement.- You can generate the file anywhere. For example,
elm-ts-interop --output elm.d.ts
. Then use a Triple Slash Reference Directive Comment at the very top (must be first line) of your TypeScript or JavaScript file to include the generated types as global types/// <reference path="./elm.d.ts" />
Whichever style you use, add the generated .d.ts
file to your .gitignore
file. That way you can be confident that your builds are using a fresh copy, not a stale one from version control that hasn't been re-generated.
Using Type-Safe Flags and Ports
- Use
InteropPorts.decodeFlags
to decode your flags - Create
Cmd msg
ports by passingInteropDefinitions.ToElm
variants toInteropPorts.toElm
- Set up subscriptions with
InteropPorts.fromElm
InteropDefinitions.elm
is user-maintained. Change it any time you want to add/remove/change ports or flags.
For example, to send our Alert
port, we can use this Cmd
in our Elm app's init
.
greet : Cmd msg
greet =
"Hello from elm-ts-interop!"
|> InteropDefinitions.Alert
|> InteropPorts.fromElm
Using Flags from JavaScript/TypeScript
elm-ts-interop
uses a single port pair: interopFromElm
and interopToElm
. Other than the type information
in your flags and the port pair, the wiring is like any other Elm app.
You will typically have a union type for your interopFromElm
, which you can use a switch
statement to
match and get typed data for each branch. This is convenient because you can do an exhaustiveness check
to make sure that you've handled every message in interopFromElm
.
Note: TypeScript doesn't have errors for non-exhaustive switch
statements, but you can add an eslint
rule,
@typescript-eslint/switch-exhaustiveness-check
and
configure it as an error like the elm-ts-interop-starter repo does to make sure you've handled every case.
document.addEventListener("DOMContentLoaded", function () {
const app = Elm.Main.init({
node: document.querySelector("main"),
flags: {
os: "Windows", // flags are added as normal, just with types to guide you
},
});
app.ports.interopFromElm.subscribe((fromElm) => {
switch (fromElm.tag) {
case "alert": {
alert(fromElm.data.message);
break;
}
}
});
myAuthenticationService.onAuthenticated((user) => {
app.ports.interopToElm.send({
tag: "authenticatedUser",
username: user.username,
});
});
});