tightenco/laravel-elm
Laravel Elm brings Elm components into your Laravel app, letting you write type-safe, interactive front-end code with a Livewire-like developer experience. It handles compiling Elm assets and wiring them into your Blade views.
Architecture fit. The package occupies a narrow, well-defined seam: Laravel owns routing, page composition, auth, and data; Elm owns bounded interactive islands mounted into Blade-rendered markup. Mechanically it is four pieces — a service provider, an Artisan scaffolder that emits a starter Elm module under resources/assets/elm/, a webpack.mix.js extension that adds elm-webpack-loader rules, and a Blade mount helper that serializes server-side data into Elm flags. A model→type-alias/decoder generator rounds it out. That shape fits "server-rendered shell, typed client island" and fits "Laravel as API, Elm as SPA client." It does not fit an app standardized on Livewire or Inertia, and mixing all three paradigms on one surface adds a compiler and a build step without removing Laravel-side complexity.
Integration feasibility. Install is cheap: composer require --dev, vendor:publish, npm install --save-dev elm, one mix.elm(...) line, one Blade directive. The real dependency is the companion runtime library (loganhenson/elm-laravel) — every generated module imports it, so its API is the Elm-side contract and must be evaluated alongside the package. Two caveats drive feasibility: the source of truth must be read, not the docs (26 stars, single maintainer, conflicting descriptions of whether the package is a mount helper, a codegen tool, or both), and the Mix-era asset step is the first thing to verify against a modern stack.
Technical risk. Ranked:
laravel-mix version compatibility, and Elm version in the generated elm.json. On Vite, assume the asset step does not work — the extension is a handful of lines around elm make, so this is a small scoped fork, not a rewrite.<div>. No error surfaces unless you render a fallback inside the mount node and log in non-production.toArray(), $casts, $hidden, $appends, and API Resources. Dates, decimals, and booleans are the recurring disagreements. Type safety here is only as good as the presenter that shapes flags explicitly.elm make --output defines Elm on window; two independently compiled bundles clobber each other. Single entry point, or deliberate namespacing.app.ports.x undefined usually means the mount script ran before the bundle or the port name doesn't match the .elm file.php artisan config:clear after touching config/elm.php.<meta> tag, not in flags.Key questions for engineering.
laravel-mix, and Elm versions, and does the package's constraint set admit them?$casts, or from raw column types? Has anyone diffed the two?Stack fit. Laravel + Blade shell + npm/Mix build + Elm islands, with mix.version() fingerprinting and assets referenced via mix() in Blade. Static compiled output is CDN-friendly and horizontal-scaling-friendly with no server-side runtime for the client. If the team's direction is Livewire or Inertia, this is not the integration layer — it is a parallel track nobody will maintain. If the direction is a decoupled SPA, use it as the build/mount seam only.
Migration path.
elm make. Scope it as a day, not a project.Compatibility checklist before code.
php artisan list | grep elm — confirm the real command surface for the pinned version.config/elm.php paths, output dirs, package/dependency settings verified against where scaffolds actually landed.elm.json and the runtime library's expected API aligned with what the generator emits.mix.elm() compiles the starter module against the actual laravel-mix version.Sequencing for a real adoption.
path-repo fork). Read the Artisan command sources.PostFlags::make($post)), never a raw model dump.elm-test feeding decoders the exact JSON the presenter produces, plus a Laravel feature test asserting flags shape via assertViewHas.~/.elm and elm-stuff/, and run a cold-cache elm make as its own job so a broken Elm build fails fast and separately from the asset pipeline.Blast-radius constraints. One Elm Program per interactive island — multiple mounts, distinct node selectors, distinct flag objects. Ports for all Laravel communication; wire them in the mount script rather than scattering window. lookups. Don't commit public/js output while also deploying it — pick one.
Maintenance ownership. The honest read of the adoption signals — 26 stars, zero dependents, single-maintainer lineage, no community — is that you will own this code. Budget for it explicitly: a named owner, a fork or path repository ready before production, and the Laravel↔Elm contract (JSON shapes, CSRF, asset fingerprinting) covered by your own tests so a glue replacement is a swap, not a migration. The two seams genuinely worth owning are the mount helper (flag serialization, escaping, mount-node markup) and the generator's output template (house style, runtime-library import shape). Both are small.
Support. No upstream support channel to lean on. Internal escalation is the only path. Mitigate by keeping the package surface small, the fork shallow, and the diff from upstream documented so a future maintainer can trace what was changed and why.
Scaling. Compiled Elm is static — CDN caching and horizontal Laravel scaling are unaffected. Costs are on the build side: Elm/Node build minutes added to CI, cold-cache compile times, and cache complexity. No SSR unless separately built, so first-paint interactivity comes from flags injected at boot rather than a fetch waterfall — that is the actual performance win and worth measuring in the spike. Multiple compiled entry points are a known sharp edge (global Elm clobbering); plan one entry point that imports everything unless you deliberately namespace.
Failure modes to instrument.
elm-test decoder tests catch this; without them the benefit of type safety evaporates and you've swapped hand-written drift for unreviewed drift.toArray()/$casts/Resources while the decoder was generated from the model. Classic first failure: a timestamp string where Elm expects an Int.mix-manifest.json, config cache pointing scaffolds at the wrong folder.Ramp-up. Elm competency is a gate, not a detail. It determines hiring, onboarding, and pairing costs and caps how much roadmap can touch the frontend. Model it as a capacity constraint before committing sprints. Practical mitigations: a short internal style guide covering presenters, ports, and generated-module rules; pair the first two widgets; keep the Elm surface bounded so a single engineer can be the expert without becoming a single point of failure for the whole frontend. Treat the whole engagement as buying an option on Elm with a stated expiry date — the package is the cheap test of the decision, not the decision.
How can I help you explore Laravel packages today?