Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📦 Release – Elm Land v0.19.0 #59

Merged
merged 56 commits into from
Jun 20, 2023
Merged

📦 Release – Elm Land v0.19.0 #59

merged 56 commits into from
Jun 20, 2023

Conversation

ryan-haskell
Copy link
Contributor

@ryan-haskell ryan-haskell commented Jan 6, 2023

Problem

When working with Elm Land, users benefit from automatically generated code. Users define Elm code in the src/ folder, while the generated stuff lives under an .elm-land/src folder.

Because the generated code references code in src, it's possible to create a valid Elm module that causes issues in generated Elm Land code.

This leads to compiler errors in the .elm-land folder, which is super confusing for folks. ( If they don't have control over those files, why are they seeing errors in them? )

There have also been limitations reported around certain features:

  • Layouts are unable to send messages to pages and embed HTML in more than one place
  • There's no way to respond to events like onUrlChanged from a page or a layout
    • This is a common need when using query parameters as filters!

Solution

1. Better error messages

The goal for the next release is to make sure all Elm compiler errors only occur within the user's src folder.

This means we'll need to catch issues before reporting Elm compiler messages, if the user's code doesn't match Elm Land's expectations for modules.

🕵️ If you're curious, here are the specific error messages that will be added:

Pages

  • Warn users about missing page exports in any page
  • Warn users about missing Model exports in any stateful page
  • Warn users about missing Msg exports in any stateful page
  • Warn users when a page function doesn't return the expected type ( Ex: page : String won't compile! )

Layouts

  • Rename Settings to Props, and ensure an Elm Land error message lets people know, to reduce confusion
  • Warn users about missing layout exports in any layout
  • Warn users about missing Settings exports in any layout
  • Warn users about missing Model exports in any layout
  • Warn users about missing Msg exports in any layout
  • Warn users when the layout function doesn't return the expected type

Other modules

  • Auth expects a valid:
    • User
    • onPageLoad
  • Effect expects a valid:
    • Effect
    • none
    • map
    • toCmd
  • Shared expects a valid:
    • Flags
    • decoder
    • Model
    • Msg
    • init
    • update
    • subscriptions
  • Shared.Model expects a valid:
    • Model
  • Shared.Msg expects a valid:
    • Msg
  • View expects a valid:
    • View
    • map
    • none
    • fromString
    • toBrowserDocument

elm-land.json

  • Warn users about invalid file, link to example
  • Warn users about missing fields
  • Warn users about fields with invalid types

"Problems" page

  • Define a "Problems" page with more in-depth explanations for why a user is seeing a given Elm Land error, like https://elm.land/problems#missing-page-export

2. Route event functions

Every page and layout will have access to the following new URL hook functions:

Page.withOnUrlChange :
    ({ from : Route (), to : Route () } -> msg)
    -> Page model msg
    -> Page model msg

Page.withOnQueryParameterChange :
    { name : String
    , onChange : { from : Maybe String, to : Maybe String } -> msg
    }
    -> Page model msg
    -> Page model msg

Page.withOnHashChange :
    ({ from : Maybe String, to : Maybe String } -> msg)
    -> Page model msg
    -> Page model msg

-- Still exploring: ability for each page to prompt user before
-- navigating away (think unsaved form changes!) 👇 
Page.withShouldPreventNavigation :
    (model -> Bool)
    -> Page model msg
    -> Page model msg

3. Upgraded Layout system

The high-level idea for "layouts" in Elm Land is great, but we want to make sure they are useful in more scenarios. This update allows a layout's Props type to include a single msg parameter.

This enables you to send page messages from a layout, or even embed multiple page views within any layout. Here's an example of a Props type:

type alias Props msg =
    { title : String
    , onButtonClick : msg
    }

This means if you have a "header" layout, you'll be able to embed interactive buttons the header bar, that have different behavior depending on the page.

Here's a visual example of when this might be useful:

image

4. Better guides, more resources

All the stuff mentioned above wasn't very time-consuming to implement. The hard part is making sure the next Elm Land release has more documentation, and it's even better than before!

To give you a sense of the scale, here's the current before/after for the official Elm Land guide (not shipped yet!):

Current (Elm Land v0.18.1) Upcoming (Elm Land v0.19.0)
image image

💥 Breaking changes

Because you're using Elm, making the upgrade will be easy! The compiler will walk you through all the changes you'll need to make, one step at a time. This section is here to provide additional context into why the changes were made, in case you're curious!

1. "Catch-all" parameters now use List String

Previously, creating a file at src/Blog/ALL_.elm created params named first_ and rest_. This has been replaced with a simple all_, to better match a user's intuition after learning about dynamic routes:

-- BEFORE
route.params == { first_ : String, rest_ : List String }

-- AFTER
route.params == { all_ : List String }

Note: If you have a use case that needs "non-empty lists", this is still possible by using a dynamic folder before the catch-all:

-- src/Pages/Blog/ALL_.elm
route.params == { all_ : List String }

-- src/Pages/Blog/First_/ALL_.elm
route.params == { first : String, all_ : List String }

2. Generated Layouts.Layout type now takes a msg variable

In order to support the ability to send page messages and embed more than one page view, we've extended the layout Props type so it can include an optional msg parameter.

Because the Props type is used in the generated .elm-land/src/Layouts.elm file, you'll need to make the following change to any page using Page.withLayout:

-- BEFORE
toLayout : Model -> Layouts.Layout

-- AFTER
toLayout : Model -> Layouts.Layout Msg

3. The Settings type has been renamed to Props

layouts can define specific information passed in from a page. previously, this data was called Settings

Elm Land now calls that data "props". If you are using any layouts today, you'll see an Elm Land error message expecting a Props type to be exposed.

The solution is to rename Settings to Props:

-- BEFORE
module Layouts.Sidebar exposing (Model, Msg, Settings, layout)

type alias Settings =
    { title : String
    , subtitle : String
    }

-- AFTER
module Layouts.Sidebar exposing (Model, Msg, Props, layout)

type alias Props =
    { title : String
    , subtitle : String
    }

3. New parentProps type variable on Layout

This release also comes with an update to how pages provide props to their layouts. We've removed the extra field nesting when using a layout.

For example, if you defined a Layouts.Header file with the following Props:

type alias Props =
    { title : String
    , subtitle : String
    }

Here's what you'll need to change for any page that is using this layout:

-- BEFORE
toLayout model =
    Layouts.Header
        { header =
            { title = "Dashboard"
            , subtitle = "Welcome back!"
            }
        }

-- AFTER
toLayout model =
    Layouts.Header
        { title = "Dashboard"
        , subtitle = "Welcome back!"
        }

Hopefully, you'll find that this change:

  • Reduces the extra nesting code when using a layout
  • Allows your nested layouts to have simpler interfaces
  • Matches your intuition when defining Props

To make this work, in each existing layout, you'll also need to add the parentProps type variable on the return value of your layout function.

For top-level layouts:

If this is not a nested layout, add the () type. There are no parent props to worry about for top-level layouts like Layouts.Sidebar or Layouts.Default:

module Layouts.Sidebar exposing (..)

-- BEFORE
layout : Props -> Shared.Model -> Route () -> Layout Model Msg mainMsg

-- AFTER
layout : Props -> Shared.Model -> Route () -> Layout () Model Msg mainMsg

For nested layouts:

If this is a nested layout, add the Props type for the parent of the layout. For example, a layout at Layouts.Sidebar.Header would use the props for Layouts.Sidebar.

To get the code to compile, use the new Layout.withParentProps function to provide the correct props:

module Layouts.Sidebar.Header exposing (..)

-- BEFORE
layout : Props -> Shared.Model -> Route () -> Layout Model Msg mainMsg
layout props shared route =
    Layout.new { ... }

-- AFTER
layout : Props -> Shared.Model -> Route () -> Layout Layouts.Sidebar.Props Model Msg mainMsg
layout props shared route =
    Layout.new { ... }
        |> Layout.withParentProps
            { user = props.user
            }

4. Auth.viewLoadingPage added, Auth.Action.showLoadingPage lost an arg

For compatibility with Lamdera, we've changed how Auth.Action.showLoadingPage works. Rather than passing your View Never into the showLoadingPage function, you'll define your loading page view within your customized Auth module.

If you have customized the Auth module, here's what you'll need to do:

module Auth exposing
    ( User
    , onPageLoad
    , viewLoadingPage  -- 1️⃣ Add this exposed value
    )

import View exposing (View) -- 2️⃣ Import the `View` module
-- ...



{-| Renders whenever `Auth.Action.showLoadingPage` is returned from `onPageLoad`.
-} -- 3️⃣ Add in this new `viewLoadingPage` function
viewLoadingPage : Shared.Model -> Route () -> View Never
viewLoadingPage shared route =
    View.fromString "Loading..."

If your customized Auth module is using Auth.Action.showLoadingPage, just move the existing View Never argument from it's current location into the body of the viewLoadingPage function.


5. The Effect.toCmd module replaced fromCmd with batch

If you have customized your Effect module, you'll get a compiler error complaining that the fromCmd field is no longer available. For compatibility with Lamdera, we've replaced this with a batch field that should enable any features.

-- BEFORE
toCmd :
    { ...
    , fromCmd : Cmd msg -> msg
    , ...
    }
    -> ...

-- AFTER
toCmd :
    { ...
    , batch : List msg -> msg
    , ...
    }
    -> ...

If you were using the fromCmd argument, check out examples/11-error-reporting to learn how you can use the batch function to send commands in a slightly different way.


Great documentation, approachable APIs, and clear error messages are a big part of what makes Elm so wonderful.

I'm very excited to get closer to sharing the next release, which will emphasize these core values in the Elm Land framework! 🌈


@netlify
Copy link

netlify bot commented Jan 6, 2023

Deploy Preview for elm-land ready!

Name Link
🔨 Latest commit 9596e1f
🔍 Latest deploy log https://app.netlify.com/sites/elm-land/deploys/649110420217c70008c537bc
😎 Deploy Preview https://deploy-preview-59--elm-land.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@ryan-haskell ryan-haskell changed the title 📦 Release – Elm Land v0.18.2 📦 Release – Elm Land v0.19.0 Apr 23, 2023
@ryan-haskell ryan-haskell marked this pull request as ready for review June 20, 2023 01:35
@ryan-haskell ryan-haskell merged commit 2ee1cd1 into main Jun 20, 2023
7 checks passed
@ryan-haskell ryan-haskell deleted the release/0.18.2 branch June 20, 2023 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants