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.elmis where you maintain your type-safeelm-ts-jsonEncoders andDecoders. 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.elmis a file to help you safely use yourelm-ts-interopports 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
.elmextension (Webpack usually does this style)import { Elm } from "./src/MyMainModule"orrequire("./src/MyMainModule") - With
.elmextension (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.decodeFlagsto decode your flags - Create
Cmd msgports by passingInteropDefinitions.ToElmvariants toInteropPorts.toElm - Set up subscriptions with
InteropPorts.fromElm InteropDefinitions.elmis 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,
});
});
});