lamdera make --no-wire emits duplicate _Platform_initialize, breaking elm-watch/Elm Land dev builds
Nobody has claimed this yet.
- Dominant language
- Haskell
- Stars
- 95
- Forks
- 21
- PR merge metrics
- No merged PRs in 30d
Description
Summary
When using lamdera make --no-wire as a drop-in replacement for elm make in an Elm Land dev server, the generated JavaScript contains two definitions of _Platform_initialize.
This breaks downstream tooling that post-processes Elm compiler output, specifically Elm Land's dev server via vite-plugin-elm-watch / elm-watch.
The important detail is:
- Lamdera first generates JS with two
_Platform_initializedeclarations. - elm-watch then post-processes that generated JS.
- elm-watch successfully rewrites the first, standard Elm-shaped
_Platform_initialize. - elm-watch does not rewrite the second Lamdera-shaped
_Platform_initializenear the end of the file. - Because JavaScript function declarations with the same name share the same scope, the later unchanged Lamdera declaration overrides the elm-watch-patched declaration at runtime.
The visible browser failure is:
TypeError: Cannot read properties of undefined (reading '$')
at $elm$core$Result$isOk
at _Platform_initialize
Why elm-watch misses the second initializer
elm-watch is already running after Lamdera output is generated. The issue is not ordering. The issue is that the later Lamdera initializer has a different emitted shape than the normal Elm runtime initializer.
elm-watch's replacement regex expects a standard-looking function body, roughly:
function _Platform_initialize(...)
{
...
}
The first _Platform_initialize in Lamdera's output has that standard shape, so elm-watch rewrites it.
But the second _Platform_initialize near the end of the Lamdera output looks like this:
function _Platform_initialize(flagDecoder, args, init, update, subscriptions, stepperBuilder)
{
var result = A2(_Json_run, flagDecoder, _Json_wrap(args ? args['flags'] : undefined));
// @TODO need to figure out how to get this to automatically escape by mode?
//$elm$core$Result$isOk(result) || _Debug_crash(2 /**/, _Json_errorToString(result.a) /**/);
$elm$core$Result$isOk(result) || _Debug_crash(2 /**_UNUSED/, _Json_errorToString(result.a) /**/);
// ...
}}(this));
This differs from the normal shape in at least two ways:
- the opening
{is indented ({) rather than starting at the beginning of the next line - the function is fused into the closing IIFE tail as
}}(this));
So elm-watch leaves this second declaration unchanged.
Why that causes the runtime crash
After elm-watch injection, call sites are rewritten to call _Platform_initialize with elm-watch's expanded signature:
_Platform_initialize(
impl._impl ? "Browser.application" : "Browser.document",
false,
debugMetadata,
flagDecoder,
args,
impl.init,
impl,
stepperBuilder
)
That call is correct for elm-watch's patched initializer:
function _Platform_initialize(programType, isDebug, debugMetadata, flagDecoder, args, init, impl, stepperBuilder)
But the later unchanged Lamdera declaration overrides that patched function and still has the standard 6-argument shape:
function _Platform_initialize(flagDecoder, args, init, update, subscriptions, stepperBuilder)
As a result, the runtime calls the wrong function shape:
flagDecoderreceives elm-watch's first string argument, e.g."Browser.application"argsreceivesfalseinit,update, andsubscriptionsare shifted into the wrong positions
Then initialization crashes at the Result.isOk check.
Reproduction
In an Elm Land application using Lamdera as the elm executable:
-
Configure Elm Land's dev command so that
elmresolves to Lamdera, for example with a PATH wrapper where:elm make ...delegates to:
lamdera make --no-wire ... -
Start the Elm Land dev server:
elm-land server -
Open the app in a browser.
Expected:
The app starts normally, as it does with standard elm make output.
Actual:
The app crashes during initialization with:
TypeError: Cannot read properties of undefined (reading '$')
at $elm$core$Result$isOk
at _Platform_initialize
Output-level verification
Compile an Elm Land Browser.application entrypoint with Lamdera:
lamdera make --no-wire .elm-land/src/Main.elm --output=lamdera-output.js
Then inspect the generated JS:
rg "function _Platform_initialize" lamdera-output.js
Observed: the raw Lamdera output contains two definitions:
function _Platform_initialize(flagDecoder, args, init, update, subscriptions, stepperBuilder)
...
function _Platform_initialize(flagDecoder, args, init, update, subscriptions, stepperBuilder)
I also verified this through elm-watch's actual transform:
- raw Lamdera output had
_Platform_initializedefinitions at approximately lines 1876 and 146718 - after
elm-watch/vite-plugin-elm-watchinjection, the first initializer was rewritten to elm-watch's patched implementation - the second Lamdera initializer remained unchanged
- transformed call sites used elm-watch's expanded initializer signature
- the later unchanged Lamdera initializer therefore overrides the patched initializer at runtime
Environment
- Lamdera npm package:
lamdera@0.19.1-1.4.0 - Elm Land:
0.20.1 - Downstream dev tooling:
vite-plugin-elm-watch/elm-watch - Command:
lamdera make --no-wire .elm-land/src/Main.elm --output=...
Possible fixes
I realize this involves downstream tooling that patches Elm compiler internals, so this may not ultimately be Lamdera's responsibility. I opened this here because the duplicate same-named global runtime declaration is surprising compiler output, and it makes --no-wire output difficult to use as a frontend elm make replacement.
Possible directions:
- Avoid emitting the duplicate initializer in
--no-wireoutput when it is not needed. - Give Lamdera-specific helper runtime functions unique names so they cannot override Elm runtime functions.
- Otherwise make the emitted JS shape closer to standard
elm makeoutput when--no-wireis used as a frontend compiler replacement. - If this output shape is intentional, documenting that
--no-wireoutput is not expected to be compatible with tools like elm-watch would also be helpful.
Thanks!
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce with lamdera make --no-wire .elm-land/src/Main.elm --output=lamdera-output.js, then inspect the generated output with rg "function _Platform_initialize" lamdera-output.js and compare it with the elm-watch transform. Trace the compiler path that emits the duplicate initializer; done means the relevant --no-wire output no longer causes the elm-watch-transformed Elm Land app to fail during initialization.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- haskell, javascript
- Domain
- compilers, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100