PHPackages                             tightenco/laravel-elm - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. tightenco/laravel-elm

AbandonedLibrary[Utility &amp; Helpers](/categories/utility)

tightenco/laravel-elm
=====================

The Elm Laravel Platform

v5.0.2(9mo ago)262.4k7MITPHPPHP ^8.4 || ^8.5CI passing

Since Oct 7Pushed 9mo ago11 watchersCompare

[ Source](https://github.com/loganhenson/laravel-elm)[ Packagist](https://packagist.org/packages/tightenco/laravel-elm)[ RSS](/packages/tightenco-laravel-elm/feed)WikiDiscussions main Synced 2mo ago

READMEChangelogDependencies (2)Versions (14)Used By (0)

[![Laravel Elm logo](https://raw.githubusercontent.com/tightenco/laravel-elm/master/laravel-elm-banner.png)](https://raw.githubusercontent.com/tightenco/laravel-elm/master/laravel-elm-banner.png)

---

[![Latest Version on Packagist](https://camo.githubusercontent.com/627663456eff14506623f7f19b56bb40b0f9b97fb22bcf708ad7dfb9b0119023/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7469676874656e636f2f6c61726176656c2d656c6d2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/tightenco/laravel-elm)

A Platform for Elm on Laravel
=============================

[](#a-platform-for-elm-on-laravel)

Tired of the paradox of choice and constant churn on the frontend?

Want a stable and opinionated platform to build on?

This package makes it seamless.

Requirements
------------

[](#requirements)

- Laravel 12.x+
- PHP 8.4 / 8.5
- Vite

Docs
----

[](#docs)

- [Installation](#installation)
- [Creating a page](#Creating-a-page)

> Some Elm knowledge required from here on!
>
> [Elm learning resources](#Some-Elm-knowledge-required-from-here-on)

- [Pass values to your page](#Pass-values-to-your-page)
- [Share values with all your pages](#Share-values-with-all-your-pages)
- [Routing](#Routing)
- [Validation errors](#Validation-errors)
- [Interop with Javascript](#Interop-with-Javascript)
- [Persistent scroll](#Persistent-scroll)
- [Progress indicators](#Progress-indicators)
- [Debugging](#Debugging)
    - [Laravel errors](#Laravel-errors)
    - [Devtools](#Devtools) (Coming soon!)
- [Deploying](#Deploying)
    - [Updating Assets](#Updating-assets)
- [Configuration](#Configuration)
    - [Hot reloading](#Hot-reloading)
- [Testing](#Testing)
    - [Laravel tests](#Laravel-tests)
- [Example apps](#Example-apps)

Installation
------------

[](#installation)

- Ensure you have a Laravel 12+ app ready ()
- Add the laravel-elm package

```
composer require tightenco/laravel-elm

```

- Install the npm package

```
npm install --save-dev laravel-elm

```

- Configure Vite in `vite.config.js`:

```
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import laravelElm from 'laravel-elm';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
                'resources/js/elm.js',
            ],
            refresh: true,
        }),
        laravelElm({
            debug: true,
        }),
    ],
});
```

- Install the new npm dependencies

```
npm install

```

> Optional Auth Scaffolding (Tailwind)

- Run the elm:auth command, this will:
    - add all the routes &amp; Elm pages for basic login/registration
    - add `Elm::authRoutes()` to your `web.php`
    - setup `app.blade.php` with the js script includes

```
php artisan elm:auth

```

> Note: Don't forget to run `php artisan migrate`!

### Watch your elm files just like you would everything else

[](#watch-your-elm-files-just-like-you-would-everything-else)

> Note: Elm compilation will be drastically faster than you are used to 🔥

```
npm run dev

```

> And open your local site! (`valet link && valet open`) Try going to `/login` or `/register`!

> General assets note!
>
> You can add `public/build` to your `.gitignore` if you wish to avoid committing these built files!

Creating a page
---------------

[](#creating-a-page)

```
php artisan elm:create Welcome

```

> *this creates `resources/elm/Example/Welcome.elm`*

Now use the `Elm` facade to render your Elm Page!

> `routes/web.php`

```
Route::get('/', function () {
    return Elm::render('Welcome');
});
```

> Hello, Example!

Some Elm knowledge required from here on
========================================

[](#some-elm-knowledge-required-from-here-on)

> Learning resources

-

Pass values to your page
------------------------

[](#pass-values-to-your-page)

> Update your Laravel route:
>
> `routes/web.php`

```
Route::get('/', function () {
    return Elm::render('Welcome', ['name' => 'John']);
});
```

> Update your Elm page:
>
> `resources/elm/pages/Welcome.elm`

- add imports for decoding to the top
- add the `name` field to `Props` (`String`)
- update `decodeProps` with the `name` field
- use `props.name` in your `view` function

```
module Welcome exposing (..)

import Html exposing (Html, div, text)
import Html.Attributes exposing (class)
import Json.Decode exposing (Decoder, Value, decodeValue, string, succeed)
import Json.Decode.Pipeline exposing (required)
import LaravelElm exposing (Page, page)

type alias Props =
    { name : String }

type alias State =
    {}

type alias Model =
    { props : Props
    , state : State
    }

type Msg
    = NewProps Value
    | NoOp

decodeProps : Decoder Props
decodeProps =
    succeed Props
        |> required "name" string

stateFromProps : Props -> State
stateFromProps props =
    {}

main : Page Model Msg
main =
    page
        { decodeProps = decodeProps
        , stateFromProps = stateFromProps
        , update = update
        , view = view
        , newPropsMsg = NewProps
        }

update : Msg -> Model -> ( Model, Cmd Msg )
update msg { props, state } =
    case msg of
        NewProps newProps ->
            ( { props = Result.withDefault props (decodeValue decodeProps newProps)
              , state = state
              }
            , Cmd.none
            )

        NoOp ->
            ( { props = props
              , state = state
              }
            , Cmd.none
            )

view : Model -> Html Msg
view { props, state } =
    div [ class "container mx-auto m-4 p-4" ]
        [ text user() ? [
                'id' => auth()->user()->id,
                'name' => auth()->user()->name,
            ] : null;
        });
    }
...
```

Routing
-------

[](#routing)

Routing in Laravel Elm is handled completely by your Laravel routes!

However, we can *use* those routes in our Elm code in a built in way.

1. Add a route, for example, our Welcome page, with a `name`:

```
Route::get('/', function () {
    return Elm::render('Welcome');
})->name('welcome');
```

2. Run the `elm:routes` command to generate the Elm routes file > `resources/elm/laravel-elm-stuff/Routes.elm` (don't edit this manually)

```
php artisan elm:routes
```

3. Now we can send users to this page from Elm in our `update` handlers: > Send the user to `/`

```
Routes.get Routes.welcome
```

> Or even post some data to an endpoint:
>
> `POST /todos` with the `"description"` of `"add more docs"`

```
Routes.post  The `errors` value is automatically passed to your Elm views, all you need to do is add it to your props to use it!

```
import LaravelElm exposing (Errors)

type alias Props =
    { errors : Errors }

decodeProps : Decoder Props
decodeProps =
    succeed Props
        |> required "errors" (dict (list string))
```

Interop with Javascript
-----------------------

[](#interop-with-javascript)

> Talk back and forth from JS &amp; Elm `resources/elm/ExamplePage.elm`

```
port module ExamplePage exposing (..)

port saveEmail : String -> Cmd msg

port receiveEmail : (Value -> msg) -> Sub msg
...
```

```
LaravelElm.register("ExamplePage", (page) => {
    page.send("receiveEmail", localStorage.getItem("email"));

    page.subscribe("saveEmail", (email) => {
        localStorage.setItem("email", email);
    });
});
```

Debugging
---------

[](#debugging)

### Laravel errors

[](#laravel-errors)

> Laravel errors are displayed in a modal on the frontend during development, using the same ignition error page that you are used to!

### DevTools

[](#devtools)

> Coming soon!

Persistent scroll
-----------------

[](#persistent-scroll)

> Sometimes you want an "app like" preservation of scroll positions while navigating to and from different pages.

Laravel Elm has built in support for this, by saving the viewport values into the history.

To use it you need to:

- Import the components we need

```
import LaravelElm exposing (Scroll, Viewports, decodeViewports, preserveScroll, receiveNewProps, saveScroll, setViewports)
```

- Add a `SaveScroll` msg

```
type Msg
    = NewProps Value
    | NoOp
    | SaveScroll Scroll
```

- Add the `viewports` prop

```
type alias Props =
    { errors : Errors
    , loading : Bool
    , viewports : Viewports }
```

- Add the decoder for the `viewports` prop

```
decodeProps : Decoder Props
decodeProps =
    Json.Decode.succeed Props
        |> required "viewports" decodeViewports
```

- Make sure we are using the saved viewport positions on mount

```
main : Program Value (Result Error Model) Msg
main =
    LaravelElm.pageWithMountAndSubscriptions
        { decodeProps = decodeProps
        , stateFromProps = stateFromProps
        , view = view
        , update = update
        , subscriptions = \_ -> receiveNewProps NewProps
        , onMount = \props -> setViewports NoOp props.viewports
        }
```

- Make sure we are using the saved viewport positions on update
- As well as saving the viewport positions on scroll

```
update : Msg -> Model -> ( Model, Cmd Msg )
update msg { props, state } =
    case msg of
        NewProps newProps ->
            case decodeValue decodeProps newProps of
                Ok decodedProps ->
                    ( { props = decodedProps
                      , state = state
                      }
                    , setViewports NoOp decodedProps.viewports
                    )

                Err _ ->
                    ( { props = props, state = state }, Cmd.none )

        SaveScroll scroll ->
            ( { props = props, state = state }, saveScroll scroll )
```

- Finally, use `preserveScroll` on our html element ("key" should be unique for multiple scroll containers)

```
view : Model -> Html Msg
view { props, state } =
    div
        ([ class "h-full overflow-y-scroll" ]
            ++ preserveScroll SaveScroll "key"
        )
        [ text "long content" ]
```

Progress indicators
-------------------

[](#progress-indicators)

### In Elm

[](#in-elm)

> The `loading` prop is automatically passed to all your Elm views, you only need to add it to your props to use it!

```
type alias Props =
    { loading : Bool }

decodeProps : Decoder Props
decodeProps =
    succeed Props
        |> required "loading" bool
```

### In Javascript

[](#in-javascript)

> You can access the loading state in javascript via the `elm-loading` event
>
> Example using nprogress to show a top progress bar:
>
> (after 180ms, so it does not appear for fast connections)

```
import Nprogress from "nprogress";

let loadingTimeout = null;
Nprogress.configure({ showSpinner: false, minimum: 0.4 });
window.addEventListener("elm-loading", function ({ detail: loading }) {
    clearTimeout(loadingTimeout);

    if (loading) {
        loadingTimeout = setTimeout(Nprogress.start, 180);
    } else {
        Nprogress.done();
    }
});
```

Deploying
---------

[](#deploying)

### Updating assets

[](#updating-assets)

> Build your assets for production using Vite:

```
npm run build
```

Configuration
-------------

[](#configuration)

### Hot reloading

[](#hot-reloading)

> You may want to disable hot reloading &amp; debugging in development if your app is *extremely* large / complex

Configure the Laravel Elm plugin in your `vite.config.js`:

```
laravelElm({
    debug: false, // Disables debug mode and hot reloading
})
```

> This disables the generation of debug code &amp; does not start the hot reload server during `npm run dev`

Testing
-------

[](#testing)

### Laravel tests

[](#laravel-tests)

All your normal http tests function identically to how they do in a vanilla Laravel app.

But if we want to assert against the props that are sent to Elm, we can add the `X-Laravel-Elm` header to our `tests/TestCase.php` `setUp` method:

```
