Permalink
38e0659 May 12, 2016
@eeue56 @mordrax
276 lines (197 sloc) 8.79 KB

Upgrading to 0.17

Upgrading should be pretty easy. Everything is quite mechanical, so I would not be very afraid of this process.

Update elm-package.json

Some core packages have been renamed:

  • evancz/elm-html is now elm-lang/html
  • evancz/elm-svg is now elm-lang/svg
  • evancz/virtual-dom is now elm-lang/virtual-dom
  • The functionality of evancz/start-app now lives in elm-lang/html in Html.App
  • The functionality of evancz/elm-effects now lives in elm-lang/core in Platform.*
  • The functionality of Graphics.* now lives in evancz/elm-graphics

So the first thing you want to do is update your elm-package.json file. Here is one that has been properly updated:

{
    "version": "1.0.0",
    "summary": "let people do a cool thing in a fun way",
    "repository": "https://github.com/user/project.git",
    "license": "BSD3",
    "source-directories": [
        "src"
    ],
    "exposed-modules": [],
    "dependencies": {
        "elm-lang/core": "4.0.0 <= v < 5.0.0",
        "elm-lang/html": "1.0.0 <= v < 2.0.0",
        "evancz/elm-http": "3.0.1 <= v < 4.0.0",
        "evancz/elm-markdown": "3.0.0 <= v < 4.0.0"
    },
    "elm-version": "0.17.0 <= v < 0.18.0"
}

The only changes should be in the dependencies and elm-version fields where you need to update constraints. The easiest way to get this all set up is to update elm-version by hand, and then remove everything from dependencies so you can install the dependencies you still need one at a time with elm package install.

Updating Syntax

The major syntax changes are:

feature 0.16 0.17
module declaration
module Queue (..) where
module Queue exposing (..)

This is a super easy change, so we will add a link to an auto-upgrade tool here when one exists.

Action is now Msg

The Elm Architecture tutorial uses the term Action for the data that gets fed into your update function. This is a silly name. So in 0.17 the standard name is message.

-- 0.16
type Action = Increment | Decrement

-- 0.17
type Msg = Increment | Decrement

The idea is that your app is receiving messages from the user, from servers, from the browser, etc. Your app then reacts to these messages in the update function.

No More Signal.Address

The most common thing in your code will probably be that Signal.Address no longer exists. Here is a before and after of upgrading some typical view code.

-- 0.16
view : Signal.Address Action -> Model -> Html
view address model =
  div []
    [ button [ onClick address Decrement ] [ text "-" ]
    , div [ countStyle ] [ text (toString model) ]
    , button [ onClick address Increment ] [ text "+" ]
    ]

-- 0.17
view : Model -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [ countStyle ] [ text (toString model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

This change is pretty simple. Any occurance of address just gets deleted. In the types, you see the addresses removed, and Html becomes Html Msg. You can read Html Msg as "an HTML node that can produce messages of type Msg". This change makes addresses unnecessary and makes it much clearer what kind of messages can be produced by a particular block of HTML.

The Signal.forwardTo function is replaced by Html.App.map. So you may need to make changes like this:

-- 0.16
view : Signal.Address Action -> Model -> Html
view address model =
  div []
    [ Counter.view (Signal.forwardTo address Top) model.topCounter
    , Counter.view (Signal.forwardTo address Bottom) model.bottomCounter
    , button [ onClick address Reset ] [ text "RESET" ]
    ]

-- 0.17
view : Model -> Html Msg
view model =
  div []
    [ map Top (Counter.view model.topCounter)
    , map Bottom (Counter.view model.bottomCounter)
    , button [ onClick Reset ] [ text "RESET" ]
    ]

These changes are nice for a couple really good reasons:

  • Addresses were consistently one of the things that new folks found most confusing.
  • It allows the elm-lang/virtual-dom implementation to be more efficient with lazy
  • It uses a normal map instead of some unfamiliar API.

You can see more examples of the new HTML API here.

Effects is now Cmd

If you are working with HTTP or anything, you are probably using evancz/elm-effects and have your update function returning Effects values. That library was a successful experiment, so it has been folded into elm-lang/core and given a name that works better in the context of Elm 0.17.

The changes are basically a simple rename:

-- 0.16
update : Action -> Model -> (Model, Effects Action)
update action model =
  case action of
    RequestMore ->
      (model, getRandomGif model.topic)

    NewGif maybeUrl ->
      ( Model model.topic (Maybe.withDefault model.gifUrl maybeUrl)
      , Effects.none
      )

-- 0.17
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    RequestMore ->
      ( model, getRandomGif model.topic )

    NewGif maybeUrl ->
      ( Model model.topic (Maybe.withDefault model.gifUrl maybeUrl)
      , Cmd.none
      )

The Cmd stuff lives in elm-lang/core in Platform.Cmd. It is imported by default with import Platform.Cmd as Cmd exposing (Cmd) to make it easier to use.

Again, very easy changes. The key goal of 0.17 was to manage effects in a nicer way, so in making these facilities more complete, the term Effects became very ambiguous. You should read more about this in the updated Elm Architecture Tutorial which has a section all about effects.

StartApp is now Html.App

The evancz/start-app package was an experiment to help people get productive with Elm more quickly. It meant that newcomers could get really far with Elm without knowing a ton about signals, and it has been very effective. With 0.17, it has been folded in to elm-lang/html in the Html.App module.

Upgrading looks like this:

-- 0.16 ---------------------------------------
import StartApp
import Task

app =
  StartApp.start
    { init = init, update = update, view = view, inputs = [] }

main =
  app.html

port tasks : Signal (Task.Task Never ())
port tasks =
  app.tasks

-- 0.17 ---------------------------------------
import Html.App as Html

main =
  Html.program
    { init = init, update = update, view = view, subscriptions = \_ -> Sub.none }

The type of main has changed from Signal Html to Program flags. The main value is a program that knows exactly how it needs to be set up. All that will be handled by Elm, so you no longer need to specially hook tasks up to a port or anything.

Upgrading Ports

Talking to JavaScript still uses ports. It is pretty similar, but adapted to fit nicely with commands and subscriptions.

Here is the change for outgoing ports:

-- 0.16
port focus : Signal String
port focus =
  ...

-- 0.17
port focus : String -> Cmd msg

Instead of hooking up a signal, you have a function that can create commands. So you just call focus : String -> Cmd msg from anywhere in your app and the command is processed like all the others.

And here is the change for incoming ports:

type User = { name : String, age : Int }

-- 0.16
port users : Signal User

-- 0.17
port users : (User -> msg) -> Sub msg

Instead of getting a signal to route to the right place, we now can create subscriptions to incoming ports. So wherever you need to know about users, you just subscribe to it.

You should definitely read more about this here.

JavaScript Interop

The style of initializing Elm programs in JS has also changed slightly.

Initialize 0.16 0.17
Embed
Elm.embed(Elm.Main, someNode);
Elm.Main.embed(someNode);
Fullscreen
Elm.fullscreen(Elm.Main);
Elm.Main.fullscreen();
Worker
Elm.worker(Elm.Main);
Elm.Main.worker();

Next Steps

From here, I would highly recommend looking through guide.elm-lang.org, particularly the sections on The Elm Architecture. This will help you get a feel for 0.17.