Skip to content

How an Elm Native module may look

Noah edited this page May 25, 2017 · 1 revision

Pre-warning

Write as much as your code as possible in Elm. Elm is safe. Javascript, and therefore Native, is not. Introducing Native code can make your application much harder to debug and rely on. One of the things we love about Elm is that if it compiles, we're pretty sure we won't get runtime errors. Native modules can and will introduce runtime errors.

Make sure to read this first.

Post-pre-warning: You're probably lost!

If you are considering writing a Native module, take a step back, and ask yourself if you should be. Native modules are an implementation detail, and subject to change at any moment in any form. Do you have a valid use case where you couldn't communicate to your JS through a port? Are you willing to make extra work for yourself?

Boilerplate

Every Native module has some boilerplate attached. Below is an annotated example.

This file should be saved in src/Native/MyModule.js

// make is a function that takes an instance of the 
// elm runtime 
// returns an object where:
//      keys are names to be accessed in pure Elm
//      values are either functions or values
var make = function make(elm) {
    // If Native isn't already bound on elm, bind it!
    elm.Native = elm.Native || {};
    // then the same for our module
    elm.Native.MyModule = elm.Native.MyModule || {};

    // `values` is where the object returned by make ends up internally
    // return if it's already set, since you only want one definition of 
    // values for speed reasons
    if (elm.Native.MyModule.values) return elm.Native.MyModule.values;

    // return the object of your module's stuff!
    return elm.Native.MyModule.values = {};
};

// setup code for MyModule
// Elm.Native.MyModule should be an object with
// a property `make` which is specified above
Elm.Native.MyModule = {};
Elm.Native.MyModule.make = make;

You will also need to change elm-package.json to be a bit different.

{
    "version": "1.0.0",
    "summary": "",
    "repository": "",
    "license": "MIT",
    "source-directories": [
        "src/"
    ],
    "native-modules": true,
    "dependencies": {
        "elm-lang/core": "2.1.0 <= v < 3.0.0"
    },
    "elm-version": "0.15.1 <= v < 0.16.0"
}

notice the "native-modules": true, line. Without this, you will not be able to use Native.

Getting started

Let's make a Native module that allows you to increment a number in the Javascript side.

To begin, let's define our Elm code. Below is the file src/MyModule.elm

module MyModule (addOne) where

-- imports are weird for Native modules
-- You import them as you would normal modules
-- but you can't alias them nor expose stuff from them
import Native.MyModule 

-- this will be our function which returns a number plus one
addOne : Int -> Int
addOne = Native.MyModule.addOne

Now, let's actually implement the Native side -

var addOne = function(a) {
  return a + 1;
};

var make = function make(elm) {
    elm.Native = elm.Native || {};
    elm.Native.MyModule = elm.Native.MyModule || {};

    if (elm.Native.MyModule.values) return elm.Native.MyModule.values;

    return elm.Native.MyModule.values = {
        'addOne': addOne
    };
};

Elm.Native.MyModule = {};
Elm.Native.MyModule.make = make;

Tada! You've just written your first native module.

To see it practice, simply copy the bit below into src/Main.elm

module Main where

import MyModule exposing (addOne)
import Graphics.Element exposing (show)

main = show <| addOne 5