Elm currently has no concept of "unmount". The topic has been talked about a few times:
- https://discourse.elm-lang.org/t/unmountable-elm-programs/1515
- https://discourse.elm-lang.org/t/subscriptions-bug-when-unmounting-elm-apps/1224
- https://discourse.elm-lang.org/t/how-to-unbind-elm-app/2202/2
However none of the suggested workarounds allow an Elm app's vars to be fully released.
Lamdera's Evergreen system means backend app lifetimes are not finite as in a browser window.
New code deployments result in new Elm app source being loaded, a new .init
run, and data transferring from the old app to the new app.
We need to somehow unmount and clear out the old app after a deploy is done.
Here's what we can do without JS:
-
Use a
type alias Model = Maybe (...)
, and set it toNothing
when we need to unmount our app, this lets us GC theModel
-
Unsubscribe from all subscriptions inside the app:
subscriptions model = if model == Nothing then Sub.none else ...
-
Keep track of all the port subscription handlers we've ever set, and remove them:
var handler = function() { ... } app.ports.somePort.subscribe(handler) // ... later when unmounting, must have retained the handler val app.ports.somePort.unsubscribe(handler)
However, the following memory leaks are left which it seems we cannot do anything about:
- The
managers
,stepper
, andports
vars in the old Elm app - The loaded source code of the old Elm app (won't unload if source ref vals are still active AFAICT)
The only way I've found to actually unmount the app entirely and allow GC to cleanup everything, is to add a die()
method to the app that references some of the private vars in the Elm runtime.
This needs to be inside the scope of the _Platform_initialize
function. In Lamdera we modify the return here like so:
const die = function() {
console.log('App dying')
managers = null
model = null
stepper = null
ports = null
}
return ports ? { ports: ports, die: die } : { die: die };
Now app.die()
can be called on an Elm app to force the release of remaining vars.
The final piece is unloading the Elm code, the possibility of which may depend on the method of loading.
If using require
on Node.js, it can be done like so:
// at load time:
var someref = require('path/to/App.elm')
// at unload time:
const oldAppModule = require.resolve('path/to/App.elm')
delete require.cache[oldAppModule]