Using Elm and TypeScript Together
This post is about using TypeScript and Elm together in a project.
I’ll be using the Microsoft Word Add-in from Glvrd Add-in for Microsoft Word as an example. The code is available in the glvrd-addin-2 GitHub repository.
The Elm framework provides ports
that allow to communicate with a JavaScript code. You can use them when you need a functionality that is not available in Elm but is presented in JavaScript. This way you can save your time and use JS libraries instead of writing your own in pure Elm.
I’ll concentrate mostly on the TypeScript’s side of the project. The Elm part would not differ from the communication with a vanilla JavaScript.
Check the introduction to ports from the official Elm guide if you are new to ports. Sending Data to JavaScript and Receiving Data from JavaScript articles are a great source of information about Elm ports as well.
The TypeScript part
The TypeScript part of the solution serves the following goals:
- Bootstrapping the Elm application.
- Working with Elm ports. The TypeScript part acts as a proxy between the Elm application and external APIs In my case—Microsoft Word and Glvrd, an external proofreading service.
- Including Elm files into the bundle. Referencing the Elm application main file, so it would be included in the single production bundle.
The logic related to Elm is defined in the ElmApp.ts file.
1. Bootstrapping the Elm application
The ElmApp.ts file contains the startApplication
function that starts the Elm application:
The function is called once the Office Add-in is initialized.
2. Working with Elm ports
For each port and the Elm application itself there is a type declaration which allows controlling the data that goes into or comes out of the Elm application:
The Index.ts file contains the actual calls to ports using send
and subscribe
functions of ports.
3. Including Elm files into the bundle
The ElmApp.ts references the main Elm application file Main.elm
:
This allows the webpack to add the Elm code to the final bundle during the build. It is done by the elm-webpack-loader loader. The webpack.common.js contains the configuration for the loader:
The Elm part
I’ve got one outgoing port check
(asks JavaScript to perform a proofread), and three incoming ports: suggestions
(receives results of a proofread), textChanged
(notifies that the selected text has changed and provides the new text), and externalError
(notifies about errors that happened on the JavaScript side).
All the ports logic is defined in the Main.elm file.
Here’s all the code related to ports:
Scaling ports in Elm
I’ve defined ports in the main logic file, and I’ve got a separate port for each action. This is a simple approach, but it doesn’t scale well.
If you’ve got a lot of ports, you might want to move them to a separate file for a better control. Also, you may consider using only two ports—one for all incoming messages and one for all outgoing.
Check The Importance of Ports talk by Murphy Randle for details.
If your ports operate with complex data, you might want to decode the data coming from JavaScript by yourselves. By default, Elm decodes data automatically, and will fail silently if a conversion cannot be performed between JavaScript and Elm types. A manual decoding allows controlling this process.
Protecting Boundaries between Elm and JavaScript describes this approach in details.