From 055652948093c496e509dc0f5d050f83144cb5d8 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 2 Aug 2025 20:23:03 +0200 Subject: [PATCH 1/3] Add hot reloading This adds hot reloading support for all the programs in elm/browser: - `Browser.sandbox` - `Browser.element` - `Browser.document` - `Browser.application` - `Debugger.element` - `Debugger.document` It needs support in `Platform` in elm/core to work. --- src/Elm/Kernel/Browser.js | 155 +++++++++++++--------- src/Elm/Kernel/Debugger.js | 254 ++++++++++++++++++++++--------------- 2 files changed, 247 insertions(+), 162 deletions(-) diff --git a/src/Elm/Kernel/Browser.js b/src/Elm/Kernel/Browser.js index 3d293c4..6e3fb7f 100644 --- a/src/Elm/Kernel/Browser.js +++ b/src/Elm/Kernel/Browser.js @@ -7,8 +7,8 @@ import Elm.Kernel.Debug exposing (crash) import Elm.Kernel.Debugger exposing (element, document) import Elm.Kernel.Json exposing (runHelp) import Elm.Kernel.List exposing (Nil) -import Elm.Kernel.Platform exposing (initialize) -import Elm.Kernel.Scheduler exposing (binding, fail, rawSpawn, succeed, spawn) +import Elm.Kernel.Platform exposing (effectManagers, initialize) +import Elm.Kernel.Scheduler exposing (binding, enqueue, fail, rawSpawn, succeed, spawn) import Elm.Kernel.Utils exposing (Tuple0, Tuple2) import Elm.Kernel.VirtualDom exposing (appendChild, applyPatches, diff, doc, node, passiveSupported, render, divertHrefToApp) import Json.Decode as Json exposing (map) @@ -26,33 +26,49 @@ import Url exposing (fromString) var __Debugger_element; -var _Browser_element = __Debugger_element || F4(function(impl, flagDecoder, debugMetadata, args) +var _Browser_element = __Debugger_element || F3(function(impl, flagDecoder, debugMetadata) { - return __Platform_initialize( - flagDecoder, - args, - impl.__$init, - impl.__$update, - impl.__$subscriptions, - function(sendToApp, initialModel) { - var view = impl.__$view; - /**__PROD/ - var domNode = args['node']; - //*/ - /**__DEBUG/ - var domNode = args && args['node'] ? args['node'] : __Debug_crash(0); - //*/ - var currNode = _VirtualDom_virtualize(domNode); - - return _Browser_makeAnimator(initialModel, function(model) - { - var nextNode = view(model); - var patches = __VirtualDom_diff(currNode, nextNode); - domNode = __VirtualDom_applyPatches(domNode, currNode, patches, sendToApp); - currNode = nextNode; - }); - } - ); + var init = function(args) + { + return __Platform_initialize( + flagDecoder, + args, + // These three arguments are used by old versions of __Platform_initialize. + // Newer versions ignore them. + impl.__$init, + impl.__$update, + impl.__$subscriptions, + function(sendToApp, initialModel) { + /**__PROD/ + var domNode = args['node']; + //*/ + /**__DEBUG/ + var domNode = args && args['node'] ? args['node'] : __Debug_crash(0); + //*/ + var currNode = _VirtualDom_virtualize(domNode); + + return _Browser_makeAnimator(initialModel, function(model) + { + var nextNode = impl.__$view(model); + var patches = __VirtualDom_diff(currNode, nextNode); + domNode = __VirtualDom_applyPatches(domNode, currNode, patches, sendToApp); + currNode = nextNode; + }); + }, + // Only used by newer versions of __Platform_initialize. + impl + ); + }; + + /**__DEBUG/ + init.hotReloadData = { + __$impl: impl, + __$platform_effectManagers: __Platform_effectManagers, + __$scheduler_enqueue: __Scheduler_enqueue + }; + //*/ + + return init; }); @@ -62,35 +78,51 @@ var _Browser_element = __Debugger_element || F4(function(impl, flagDecoder, debu var __Debugger_document; -var _Browser_document = __Debugger_document || F4(function(impl, flagDecoder, debugMetadata, args) +var _Browser_document = __Debugger_document || F3(function(impl, flagDecoder, debugMetadata) { - return __Platform_initialize( - flagDecoder, - args, - impl.__$init, - impl.__$update, - impl.__$subscriptions, - function(sendToApp, initialModel) { - var divertHrefToApp = impl.__$setup && impl.__$setup(sendToApp) - var view = impl.__$view; - var title = __VirtualDom_doc.title; - var bodyNode = __VirtualDom_doc.body; - __VirtualDom_divertHrefToApp = divertHrefToApp; - var currNode = _VirtualDom_virtualize(bodyNode); - __VirtualDom_divertHrefToApp = 0; - return _Browser_makeAnimator(initialModel, function(model) - { + var init = function(args) + { + return __Platform_initialize( + flagDecoder, + args, + // These three arguments are used by old versions of __Platform_initialize. + // Newer versions ignore them. + impl.__$init, + impl.__$update, + impl.__$subscriptions, + function(sendToApp, initialModel) { + var divertHrefToApp = impl.__$setup && impl.__$setup(sendToApp) + var title = __VirtualDom_doc.title; + var bodyNode = __VirtualDom_doc.body; __VirtualDom_divertHrefToApp = divertHrefToApp; - var doc = view(model); - var nextNode = __VirtualDom_node('body')(__List_Nil)(doc.__$body); - var patches = __VirtualDom_diff(currNode, nextNode); - bodyNode = __VirtualDom_applyPatches(bodyNode, currNode, patches, sendToApp); - currNode = nextNode; + var currNode = _VirtualDom_virtualize(bodyNode); __VirtualDom_divertHrefToApp = 0; - (title !== doc.__$title) && (__VirtualDom_doc.title = title = doc.__$title); - }); - } - ); + return _Browser_makeAnimator(initialModel, function(model) + { + __VirtualDom_divertHrefToApp = divertHrefToApp; + var doc = impl.__$view(model); + var nextNode = __VirtualDom_node('body')(__List_Nil)(doc.__$body); + var patches = __VirtualDom_diff(currNode, nextNode); + bodyNode = __VirtualDom_applyPatches(bodyNode, currNode, patches, sendToApp); + currNode = nextNode; + __VirtualDom_divertHrefToApp = 0; + (title !== doc.__$title) && (__VirtualDom_doc.title = title = doc.__$title); + }); + }, + // Only used by newer versions of __Platform_initialize. + impl + ); + } + + /**__DEBUG/ + init.hotReloadData = { + __$impl: impl, + __$platform_effectManagers: __Platform_effectManagers, + __$scheduler_enqueue: __Scheduler_enqueue + }; + //*/ + + return init; }); @@ -214,9 +246,7 @@ function _Browser_makeAnimator(model, draw) function _Browser_application(impl) { - var onUrlChange = impl.__$onUrlChange; - var onUrlRequest = impl.__$onUrlRequest; - var key = function() { key.__sendToApp(onUrlChange(_Browser_getUrl())); }; + var key = function() { key.__sendToApp(impl.__$onUrlChange(_Browser_getUrl())); }; return _Browser_document({ __$setup: function(sendToApp) @@ -233,7 +263,7 @@ function _Browser_application(impl) var href = domNode.href; var curr = _Browser_getUrl(); var next = __Url_fromString(href).a; - sendToApp(onUrlRequest( + sendToApp(impl.__$onUrlRequest( (next && curr.__$protocol === next.__$protocol && curr.__$host === next.__$host @@ -249,9 +279,18 @@ function _Browser_application(impl) { return A3(impl.__$init, flags, _Browser_getUrl(), key); }, + // Unnecessary-looking wrapper functions are needed during development + // for hot reloading. In production, we optimize slightly by omitting them. + /**__PROD/ __$view: impl.__$view, __$update: impl.__$update, __$subscriptions: impl.__$subscriptions + //*/ + /**__DEBUG/ + __$view: function(model) { return impl.__$view(model); }, + __$update: F2(function(msg, model) { return A2(impl.__$update, msg, model); }), + __$subscriptions: function(model) { return impl.__$subscriptions(model); } + //*/ }); } diff --git a/src/Elm/Kernel/Debugger.js b/src/Elm/Kernel/Debugger.js index 9c89041..16a9cbc 100755 --- a/src/Elm/Kernel/Debugger.js +++ b/src/Elm/Kernel/Debugger.js @@ -7,8 +7,8 @@ import Elm.Kernel.Browser exposing (makeAnimator) import Elm.Kernel.Debug exposing (crash) import Elm.Kernel.Json exposing (wrap) import Elm.Kernel.List exposing (Cons, Nil) -import Elm.Kernel.Platform exposing (initialize) -import Elm.Kernel.Scheduler exposing (binding, succeed) +import Elm.Kernel.Platform exposing (effectManagers, initialize) +import Elm.Kernel.Scheduler exposing (binding, enqueue, succeed) import Elm.Kernel.Utils exposing (Tuple0, Tuple2, ap) import Elm.Kernel.VirtualDom exposing (node, applyPatches, diff, doc, makeStepper, map, render, virtualize, divertHrefToApp) import Json.Decode as Json exposing (map) @@ -35,127 +35,173 @@ function _Debugger_unsafeCoerce(value) // PROGRAMS -var _Debugger_element = F4(function(impl, flagDecoder, debugMetadata, args) +var _Debugger_element = F3(function(impl, flagDecoder, debugMetadata) { - return __Platform_initialize( - flagDecoder, - args, - A3(__Main_wrapInit, __Json_wrap(debugMetadata), _Debugger_popout(), impl.__$init), - __Main_wrapUpdate(impl.__$update), - __Main_wrapSubs(impl.__$subscriptions), - function(sendToApp, initialModel) - { - var view = impl.__$view; - var domNode = args && args['node'] ? args['node'] : __Debug_crash(0); - var currNode = __VirtualDom_virtualize(domNode); - var currBlocker = __Main_toBlockerType(initialModel); - var currPopout; - - var cornerNode = __VirtualDom_doc.createElement('div'); - domNode.parentNode.insertBefore(cornerNode, domNode.nextSibling); - var cornerCurr = __VirtualDom_virtualize(cornerNode); - - initialModel.__$popout.__sendToApp = sendToApp; + var wrappedImpl = { + __$init: A3(__Main_wrapInit, __Json_wrap(debugMetadata), _Debugger_popout(), impl.__$init), + __$view: impl.__$view, + __$update: __Main_wrapUpdate(impl.__$update), + __$subscriptions: __Main_wrapSubs(impl.__$subscriptions) + }; - return _Browser_makeAnimator(initialModel, function(model) + var init = function(args) + { + return __Platform_initialize( + flagDecoder, + args, + // These three arguments are used by old versions of __Platform_initialize. + // Newer versions ignore them. + wrappedImpl.__$init, + wrappedImpl.__$update, + wrappedImpl.__$subscriptions, + function(sendToApp, initialModel) { - var nextNode = A2(__VirtualDom_map, __Main_UserMsg, view(__Main_getUserModel(model))); - var patches = __VirtualDom_diff(currNode, nextNode); - domNode = __VirtualDom_applyPatches(domNode, currNode, patches, sendToApp); - currNode = nextNode; + var domNode = args && args['node'] ? args['node'] : __Debug_crash(0); + var currNode = __VirtualDom_virtualize(domNode); + var currBlocker = __Main_toBlockerType(initialModel); + var currPopout; - // update blocker + var cornerNode = __VirtualDom_doc.createElement('div'); + domNode.parentNode.insertBefore(cornerNode, domNode.nextSibling); + var cornerCurr = __VirtualDom_virtualize(cornerNode); - var nextBlocker = __Main_toBlockerType(model); - _Debugger_updateBlocker(currBlocker, nextBlocker); - currBlocker = nextBlocker; + initialModel.__$popout.__sendToApp = sendToApp; - // view corner + return _Browser_makeAnimator(initialModel, function(model) + { + var nextNode = A2(__VirtualDom_map, __Main_UserMsg, wrappedImpl.__$view(__Main_getUserModel(model))); + var patches = __VirtualDom_diff(currNode, nextNode); + domNode = __VirtualDom_applyPatches(domNode, currNode, patches, sendToApp); + currNode = nextNode; + + // update blocker + + var nextBlocker = __Main_toBlockerType(model); + _Debugger_updateBlocker(currBlocker, nextBlocker); + currBlocker = nextBlocker; + + // view corner + + var cornerNext = __Main_cornerView(model); + var cornerPatches = __VirtualDom_diff(cornerCurr, cornerNext); + cornerNode = __VirtualDom_applyPatches(cornerNode, cornerCurr, cornerPatches, sendToApp); + cornerCurr = cornerNext; + + if (!model.__$popout.__doc) + { + currPopout = undefined; + return; + } + + // view popout + + __VirtualDom_doc = model.__$popout.__doc; // SWITCH TO POPOUT DOC + currPopout || (currPopout = __VirtualDom_virtualize(model.__$popout.__doc.body)); + var nextPopout = __Main_popoutView(model); + var popoutPatches = __VirtualDom_diff(currPopout, nextPopout); + __VirtualDom_applyPatches(model.__$popout.__doc.body, currPopout, popoutPatches, sendToApp); + currPopout = nextPopout; + __VirtualDom_doc = document; // SWITCH BACK TO NORMAL DOC + }); + }, + // Only used by newer versions of __Platform_initialize. + wrappedImpl + ); + }; - var cornerNext = __Main_cornerView(model); - var cornerPatches = __VirtualDom_diff(cornerCurr, cornerNext); - cornerNode = __VirtualDom_applyPatches(cornerNode, cornerCurr, cornerPatches, sendToApp); - cornerCurr = cornerNext; + /**__DEBUG/ + init.hotReloadData = { + __$impl: wrappedImpl, + __$platform_effectManagers: __Platform_effectManagers, + __$scheduler_enqueue: __Scheduler_enqueue + }; + //*/ - if (!model.__$popout.__doc) - { - currPopout = undefined; - return; - } - - // view popout - - __VirtualDom_doc = model.__$popout.__doc; // SWITCH TO POPOUT DOC - currPopout || (currPopout = __VirtualDom_virtualize(model.__$popout.__doc.body)); - var nextPopout = __Main_popoutView(model); - var popoutPatches = __VirtualDom_diff(currPopout, nextPopout); - __VirtualDom_applyPatches(model.__$popout.__doc.body, currPopout, popoutPatches, sendToApp); - currPopout = nextPopout; - __VirtualDom_doc = document; // SWITCH BACK TO NORMAL DOC - }); - } - ); + return init; }); -var _Debugger_document = F4(function(impl, flagDecoder, debugMetadata, args) +var _Debugger_document = F3(function(impl, flagDecoder, debugMetadata) { - return __Platform_initialize( - flagDecoder, - args, - A3(__Main_wrapInit, __Json_wrap(debugMetadata), _Debugger_popout(), impl.__$init), - __Main_wrapUpdate(impl.__$update), - __Main_wrapSubs(impl.__$subscriptions), - function(sendToApp, initialModel) - { - var divertHrefToApp = impl.__$setup && impl.__$setup(function(x) { return sendToApp(__Main_UserMsg(x)); }); - var view = impl.__$view; - var title = __VirtualDom_doc.title; - var bodyNode = __VirtualDom_doc.body; - __VirtualDom_divertHrefToApp = divertHrefToApp; - var currNode = __VirtualDom_virtualize(bodyNode); - __VirtualDom_divertHrefToApp = 0; - var currBlocker = __Main_toBlockerType(initialModel); - var currPopout; - - initialModel.__$popout.__sendToApp = sendToApp; - - return _Browser_makeAnimator(initialModel, function(model) + var wrappedImpl = { + __$init: A3(__Main_wrapInit, __Json_wrap(debugMetadata), _Debugger_popout(), impl.__$init), + __$view: impl.__$view, + __$update: __Main_wrapUpdate(impl.__$update), + __$subscriptions: __Main_wrapSubs(impl.__$subscriptions) + }; + + var init = function(args) + { + return __Platform_initialize( + flagDecoder, + args, + // These three arguments are used by old versions of __Platform_initialize. + // Newer versions ignore them. + wrappedImpl.__$init, + wrappedImpl.__$update, + wrappedImpl.__$subscriptions, + function(sendToApp, initialModel) { + var divertHrefToApp = impl.__$setup && impl.__$setup(function(x) { return sendToApp(__Main_UserMsg(x)); }); + var title = __VirtualDom_doc.title; + var bodyNode = __VirtualDom_doc.body; __VirtualDom_divertHrefToApp = divertHrefToApp; - var doc = view(__Main_getUserModel(model)); - var nextNode = __VirtualDom_node('body')(__List_Nil)( - __Utils_ap( - A2(__List_map, __VirtualDom_map(__Main_UserMsg), doc.__$body), - __List_Cons(__Main_cornerView(model), __List_Nil) - ) - ); - var patches = __VirtualDom_diff(currNode, nextNode); - bodyNode = __VirtualDom_applyPatches(bodyNode, currNode, patches, sendToApp); - currNode = nextNode; + var currNode = __VirtualDom_virtualize(bodyNode); __VirtualDom_divertHrefToApp = 0; - (title !== doc.__$title) && (__VirtualDom_doc.title = title = doc.__$title); - - // update blocker + var currBlocker = __Main_toBlockerType(initialModel); + var currPopout; - var nextBlocker = __Main_toBlockerType(model); - _Debugger_updateBlocker(currBlocker, nextBlocker); - currBlocker = nextBlocker; + initialModel.__$popout.__sendToApp = sendToApp; - // view popout + return _Browser_makeAnimator(initialModel, function(model) + { + __VirtualDom_divertHrefToApp = divertHrefToApp; + var doc = wrappedImpl.__$view(__Main_getUserModel(model)); + var nextNode = __VirtualDom_node('body')(__List_Nil)( + __Utils_ap( + A2(__List_map, __VirtualDom_map(__Main_UserMsg), doc.__$body), + __List_Cons(__Main_cornerView(model), __List_Nil) + ) + ); + var patches = __VirtualDom_diff(currNode, nextNode); + bodyNode = __VirtualDom_applyPatches(bodyNode, currNode, patches, sendToApp); + currNode = nextNode; + __VirtualDom_divertHrefToApp = 0; + (title !== doc.__$title) && (__VirtualDom_doc.title = title = doc.__$title); + + // update blocker + + var nextBlocker = __Main_toBlockerType(model); + _Debugger_updateBlocker(currBlocker, nextBlocker); + currBlocker = nextBlocker; + + // view popout + + if (!model.__$popout.__doc) { currPopout = undefined; return; } + + __VirtualDom_doc = model.__$popout.__doc; // SWITCH TO POPOUT DOC + currPopout || (currPopout = __VirtualDom_virtualize(model.__$popout.__doc.body)); + var nextPopout = __Main_popoutView(model); + var popoutPatches = __VirtualDom_diff(currPopout, nextPopout); + __VirtualDom_applyPatches(model.__$popout.__doc.body, currPopout, popoutPatches, sendToApp); + currPopout = nextPopout; + __VirtualDom_doc = document; // SWITCH BACK TO NORMAL DOC + }); + }, + // Only used by newer versions of __Platform_initialize. + wrappedImpl + ); + }; - if (!model.__$popout.__doc) { currPopout = undefined; return; } + /**__DEBUG/ + init.hotReloadData = { + __$impl: wrappedImpl, + __$platform_effectManagers: __Platform_effectManagers, + __$scheduler_enqueue: __Scheduler_enqueue + }; + //*/ - __VirtualDom_doc = model.__$popout.__doc; // SWITCH TO POPOUT DOC - currPopout || (currPopout = __VirtualDom_virtualize(model.__$popout.__doc.body)); - var nextPopout = __Main_popoutView(model); - var popoutPatches = __VirtualDom_diff(currPopout, nextPopout); - __VirtualDom_applyPatches(model.__$popout.__doc.body, currPopout, popoutPatches, sendToApp); - currPopout = nextPopout; - __VirtualDom_doc = document; // SWITCH BACK TO NORMAL DOC - }); - } - ); + return init; }); From 2b7e9d412ee6bdc270dde60091a5747eb328e85f Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 3 Aug 2025 15:35:17 +0200 Subject: [PATCH 2/3] Make it possible to stop an app --- src/Elm/Kernel/Browser.js | 43 +++++++++++++++++++- src/Elm/Kernel/Debugger.js | 81 +++++++++++++++++++++++++++++++++++++- 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/src/Elm/Kernel/Browser.js b/src/Elm/Kernel/Browser.js index 6e3fb7f..ccffad9 100644 --- a/src/Elm/Kernel/Browser.js +++ b/src/Elm/Kernel/Browser.js @@ -47,13 +47,26 @@ var _Browser_element = __Debugger_element || F3(function(impl, flagDecoder, debu //*/ var currNode = _VirtualDom_virtualize(domNode); - return _Browser_makeAnimator(initialModel, function(model) + var stepper = _Browser_makeAnimator(initialModel, function(model) { var nextNode = impl.__$view(model); var patches = __VirtualDom_diff(currNode, nextNode); domNode = __VirtualDom_applyPatches(domNode, currNode, patches, sendToApp); currNode = nextNode; }); + + stepper.__$shutdown = function() + { + // Older versions of elm/virtual-dom does not provide this function. + if (typeof _VirtualDom_removeAllEventListeners === 'function') + { + _VirtualDom_removeAllEventListeners(domNode); + } + + return domNode; + }; + + return stepper; }, // Only used by newer versions of __Platform_initialize. impl @@ -97,7 +110,8 @@ var _Browser_document = __Debugger_document || F3(function(impl, flagDecoder, de __VirtualDom_divertHrefToApp = divertHrefToApp; var currNode = _VirtualDom_virtualize(bodyNode); __VirtualDom_divertHrefToApp = 0; - return _Browser_makeAnimator(initialModel, function(model) + + var stepper = _Browser_makeAnimator(initialModel, function(model) { __VirtualDom_divertHrefToApp = divertHrefToApp; var doc = impl.__$view(model); @@ -108,6 +122,24 @@ var _Browser_document = __Debugger_document || F3(function(impl, flagDecoder, de __VirtualDom_divertHrefToApp = 0; (title !== doc.__$title) && (__VirtualDom_doc.title = title = doc.__$title); }); + + stepper.__$shutdown = function() + { + // Older versions of elm/virtual-dom does not provide this function. + if (typeof _VirtualDom_removeAllEventListeners === 'function') + { + _VirtualDom_removeAllEventListeners(bodyNode); + } + + if (impl.__$shutdown) + { + impl.__$shutdown(); + } + + return bodyNode; + }; + + return stepper; }, // Only used by newer versions of __Platform_initialize. impl @@ -275,6 +307,13 @@ function _Browser_application(impl) } }); }, + __$shutdown: function() + { + _Browser_window.removeEventListener('popstate', key); + _Browser_window.removeEventListener('hashchange', key); + // Allow the app to be garbage collected. + key.__sendToApp = function() {}; + }, __$init: function(flags) { return A3(impl.__$init, flags, _Browser_getUrl(), key); diff --git a/src/Elm/Kernel/Debugger.js b/src/Elm/Kernel/Debugger.js index 16a9cbc..7777b67 100755 --- a/src/Elm/Kernel/Debugger.js +++ b/src/Elm/Kernel/Debugger.js @@ -67,7 +67,7 @@ var _Debugger_element = F3(function(impl, flagDecoder, debugMetadata) initialModel.__$popout.__sendToApp = sendToApp; - return _Browser_makeAnimator(initialModel, function(model) + var stepper = _Browser_makeAnimator(initialModel, function(model) { var nextNode = A2(__VirtualDom_map, __Main_UserMsg, wrappedImpl.__$view(__Main_getUserModel(model))); var patches = __VirtualDom_diff(currNode, nextNode); @@ -103,6 +103,40 @@ var _Debugger_element = F3(function(impl, flagDecoder, debugMetadata) currPopout = nextPopout; __VirtualDom_doc = document; // SWITCH BACK TO NORMAL DOC }); + + stepper.__$shutdown = function() + { + // Remove corner. + cornerNode.parentNode.removeChild(cornerNode); + + // Remove blockers. + _Debugger_updateBlocker(currBlocker, __Overlay_BlockNone); + + // Close popout if open. + var popout = initialModel.__$popout; + if (popout.__doc) + { + var debuggerWindow = popout.__doc.defaultView; + popout.__doc = undefined; + popout.__sendToApp(__Main_NoOp); + debuggerWindow.close(); + } + + // Allow the app to be garbage collected. `Function.prototype` is a no-op function. + // Note that we cannot use `function () {}` here, because it has `sendToApp` in scope, + // which prevents garbage collection. + popout.__sendToApp = Function.prototype; + + // Older versions of elm/virtual-dom does not provide this function. + if (typeof _VirtualDom_removeAllEventListeners === 'function') + { + _VirtualDom_removeAllEventListeners(domNode); + } + + return domNode; + }; + + return stepper; }, // Only used by newer versions of __Platform_initialize. wrappedImpl @@ -153,7 +187,7 @@ var _Debugger_document = F3(function(impl, flagDecoder, debugMetadata) initialModel.__$popout.__sendToApp = sendToApp; - return _Browser_makeAnimator(initialModel, function(model) + var stepper = _Browser_makeAnimator(initialModel, function(model) { __VirtualDom_divertHrefToApp = divertHrefToApp; var doc = wrappedImpl.__$view(__Main_getUserModel(model)); @@ -187,6 +221,49 @@ var _Debugger_document = F3(function(impl, flagDecoder, debugMetadata) currPopout = nextPopout; __VirtualDom_doc = document; // SWITCH BACK TO NORMAL DOC }); + + stepper.__$shutdown = function() + { + // Remove corner. + // Older versions of elm/virtual-dom does not provide this function. + if (typeof _VirtualDom_removeLastElmChild === 'function') + { + _VirtualDom_removeLastElmChild(bodyNode); + } + + // Remove blockers. + _Debugger_updateBlocker(currBlocker, __Overlay_BlockNone); + + // Close popout if open. + var popout = initialModel.__$popout; + if (popout.__doc) + { + var debuggerWindow = popout.__doc.defaultView; + popout.__doc = undefined; + popout.__sendToApp(__Main_NoOp); + debuggerWindow.close(); + } + + // Allow the app to be garbage collected. `Function.prototype` is a no-op function. + // Note that we cannot use `function () {}` here, because it has `sendToApp` in scope, + // which prevents garbage collection. + popout.__sendToApp = Function.prototype; + + // Older versions of elm/virtual-dom does not provide this function. + if (typeof _VirtualDom_removeAllEventListeners === 'function') + { + _VirtualDom_removeAllEventListeners(bodyNode); + } + + if (impl.__$shutdown) + { + impl.__$shutdown(); + } + + return bodyNode; + }; + + return stepper; }, // Only used by newer versions of __Platform_initialize. wrappedImpl From b45551e9ad0e858616b1693290f7bf40f8791982 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 3 Aug 2025 17:58:15 +0200 Subject: [PATCH 3/3] Fix timing of initial draw --- src/Elm/Kernel/Browser.js | 32 +++++++++++++++++++++++++------- src/Elm/Kernel/Debugger.js | 26 ++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/Elm/Kernel/Browser.js b/src/Elm/Kernel/Browser.js index ccffad9..4350a9e 100644 --- a/src/Elm/Kernel/Browser.js +++ b/src/Elm/Kernel/Browser.js @@ -38,7 +38,7 @@ var _Browser_element = __Debugger_element || F3(function(impl, flagDecoder, debu impl.__$init, impl.__$update, impl.__$subscriptions, - function(sendToApp, initialModel) { + function(sendToApp, initialModel, platformInitializeWillDoInitialDraw) { /**__PROD/ var domNode = args['node']; //*/ @@ -47,7 +47,7 @@ var _Browser_element = __Debugger_element || F3(function(impl, flagDecoder, debu //*/ var currNode = _VirtualDom_virtualize(domNode); - var stepper = _Browser_makeAnimator(initialModel, function(model) + var stepper = _Browser_makeAnimator(function(model) { var nextNode = impl.__$view(model); var patches = __VirtualDom_diff(currNode, nextNode); @@ -66,6 +66,15 @@ var _Browser_element = __Debugger_element || F3(function(impl, flagDecoder, debu return domNode; }; + // The initial draw used to be a side effect of `stepperBuilder`. + // Newer versions of `__Platform_initialize` do that instead. + // Older versions don’t send the `platformInitializeWillDoInitialDraw` + // parameter, which means that we need to do it here for compatibility. + if (!platformInitializeWillDoInitialDraw) + { + stepper(initialModel, true); + } + return stepper; }, // Only used by newer versions of __Platform_initialize. @@ -103,7 +112,7 @@ var _Browser_document = __Debugger_document || F3(function(impl, flagDecoder, de impl.__$init, impl.__$update, impl.__$subscriptions, - function(sendToApp, initialModel) { + function(sendToApp, initialModel, platformInitializeWillDoInitialDraw) { var divertHrefToApp = impl.__$setup && impl.__$setup(sendToApp) var title = __VirtualDom_doc.title; var bodyNode = __VirtualDom_doc.body; @@ -111,7 +120,7 @@ var _Browser_document = __Debugger_document || F3(function(impl, flagDecoder, de var currNode = _VirtualDom_virtualize(bodyNode); __VirtualDom_divertHrefToApp = 0; - var stepper = _Browser_makeAnimator(initialModel, function(model) + var stepper = _Browser_makeAnimator(function(model) { __VirtualDom_divertHrefToApp = divertHrefToApp; var doc = impl.__$view(model); @@ -139,6 +148,15 @@ var _Browser_document = __Debugger_document || F3(function(impl, flagDecoder, de return bodyNode; }; + // The initial draw used to be a side effect of `stepperBuilder`. + // Newer versions of `__Platform_initialize` do that instead. + // Older versions don’t send the `platformInitializeWillDoInitialDraw` + // parameter, which means that we need to do it here for compatibility. + if (!platformInitializeWillDoInitialDraw) + { + stepper(initialModel, true); + } + return stepper; }, // Only used by newer versions of __Platform_initialize. @@ -205,8 +223,10 @@ var _Browser_requestAnimationFrame_raw = ? requestAnimationFrame : function(callback) { return setTimeout(callback, 1000 / 60); }; -function _Browser_makeAnimator(model, draw) +function _Browser_makeAnimator(draw) { + var model; + // Whether `draw` is currently running. `draw` can cause side effects: // If the user renders a custom element, they can dispatch an event in // its `connectedCallback`, which happens synchronously. That causes @@ -250,8 +270,6 @@ function _Browser_makeAnimator(model, draw) } } - drawHelp(); - return function(nextModel, isSync) { model = nextModel; diff --git a/src/Elm/Kernel/Debugger.js b/src/Elm/Kernel/Debugger.js index 7777b67..be32f2f 100755 --- a/src/Elm/Kernel/Debugger.js +++ b/src/Elm/Kernel/Debugger.js @@ -54,7 +54,7 @@ var _Debugger_element = F3(function(impl, flagDecoder, debugMetadata) wrappedImpl.__$init, wrappedImpl.__$update, wrappedImpl.__$subscriptions, - function(sendToApp, initialModel) + function(sendToApp, initialModel, platformInitializeWillDoInitialDraw) { var domNode = args && args['node'] ? args['node'] : __Debug_crash(0); var currNode = __VirtualDom_virtualize(domNode); @@ -67,7 +67,7 @@ var _Debugger_element = F3(function(impl, flagDecoder, debugMetadata) initialModel.__$popout.__sendToApp = sendToApp; - var stepper = _Browser_makeAnimator(initialModel, function(model) + var stepper = _Browser_makeAnimator(function(model) { var nextNode = A2(__VirtualDom_map, __Main_UserMsg, wrappedImpl.__$view(__Main_getUserModel(model))); var patches = __VirtualDom_diff(currNode, nextNode); @@ -136,6 +136,15 @@ var _Debugger_element = F3(function(impl, flagDecoder, debugMetadata) return domNode; }; + // The initial draw used to be a side effect of `stepperBuilder`. + // Newer versions of `__Platform_initialize` do that instead. + // Older versions don’t send the `platformInitializeWillDoInitialDraw` + // parameter, which means that we need to do it here for compatibility. + if (!platformInitializeWillDoInitialDraw) + { + stepper(initialModel, true); + } + return stepper; }, // Only used by newer versions of __Platform_initialize. @@ -174,7 +183,7 @@ var _Debugger_document = F3(function(impl, flagDecoder, debugMetadata) wrappedImpl.__$init, wrappedImpl.__$update, wrappedImpl.__$subscriptions, - function(sendToApp, initialModel) + function(sendToApp, initialModel, platformInitializeWillDoInitialDraw) { var divertHrefToApp = impl.__$setup && impl.__$setup(function(x) { return sendToApp(__Main_UserMsg(x)); }); var title = __VirtualDom_doc.title; @@ -187,7 +196,7 @@ var _Debugger_document = F3(function(impl, flagDecoder, debugMetadata) initialModel.__$popout.__sendToApp = sendToApp; - var stepper = _Browser_makeAnimator(initialModel, function(model) + var stepper = _Browser_makeAnimator(function(model) { __VirtualDom_divertHrefToApp = divertHrefToApp; var doc = wrappedImpl.__$view(__Main_getUserModel(model)); @@ -263,6 +272,15 @@ var _Debugger_document = F3(function(impl, flagDecoder, debugMetadata) return bodyNode; }; + // The initial draw used to be a side effect of `stepperBuilder`. + // Newer versions of `__Platform_initialize` do that instead. + // Older versions don’t send the `platformInitializeWillDoInitialDraw` + // parameter, which means that we need to do it here for compatibility. + if (!platformInitializeWillDoInitialDraw) + { + stepper(initialModel, true); + } + return stepper; }, // Only used by newer versions of __Platform_initialize.