Upgrading should be pretty easy. Everything is quite mechanical, so I would not be very afraid of this process.
Some core packages have been renamed:
evancz/elm-html
is nowelm-lang/html
evancz/elm-svg
is nowelm-lang/svg
evancz/virtual-dom
is nowelm-lang/virtual-dom
- The functionality of
evancz/start-app
now lives inelm-lang/html
inHtml.App
- The functionality of
evancz/elm-effects
now lives inelm-lang/core
inPlatform.*
- The functionality of
Graphics.*
now lives inevancz/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
.
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.
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.
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 withlazy
- It uses a normal
map
instead of some unfamiliar API.
You can see more examples of the new HTML API here.
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.
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.
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.
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(); |
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.