From 86f8b8991a46120f63afef52c8927efb39f9caca Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sat, 2 Aug 2025 21:57:09 +0200 Subject: [PATCH 1/2] Add hot reloading This adds hot reloading support for `main : Html msg` programs. It needs support in `Platform` in elm/core to work. --- src/Elm/Kernel/VirtualDom.js | 45 +++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index 61e9003e..add36ff9 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -114,23 +114,46 @@ function _VirtualDom_createTNode(domNode) }; } -var _VirtualDom_init = F4(function(virtualNode, flagDecoder, debugMetadata, args) +var _VirtualDom_init = F3(function(virtualNode, flagDecoder, debugMetadata) { // NOTE: this function needs __Platform_export available to work - /**__PROD/ - var node = args['node']; - //*/ + var init = function(args) + { + /**__PROD/ + var node = args['node']; + //*/ + /**__DEBUG/ + var node = args && args['node'] ? args['node'] : __Debug_crash(0); + //*/ + + var sendToApp = function() {}; + var tNode = _VirtualDom_createTNode(undefined); + var nextNode = _VirtualDom_render(virtualNode, sendToApp, tNode); + nextNode.elmTree = tNode; + node.parentNode.replaceChild(nextNode, node); + node = nextNode; + + var app = {}; + + /**__DEBUG/ + app.hotReload = function(hotReloadData) + { + node = _VirtualDom_applyPatches(node, virtualNode, hotReloadData.__$virtualNode, sendToApp); + virtualNode = hotReloadData.__$virtualNode; + }; + //*/ + + return app; + }; + /**__DEBUG/ - var node = args && args['node'] ? args['node'] : __Debug_crash(0); + init.hotReloadData = { + __$virtualNode: virtualNode + }; //*/ - node.parentNode.replaceChild( - _VirtualDom_render(virtualNode, function() {}, _VirtualDom_createTNode(undefined)), - node - ); - - return {}; + return init; }); From 66fe2171254f4c284e6864ba2acaa972b7e16ff8 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 3 Aug 2025 15:35:00 +0200 Subject: [PATCH 2/2] Make it possible to stop an app This adds `app.stop()` and `app.detachView()` for `main : Html msg` programs. --- src/Elm/Kernel/VirtualDom.js | 90 ++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index add36ff9..c56d7faf 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -134,7 +134,23 @@ var _VirtualDom_init = F3(function(virtualNode, flagDecoder, debugMetadata) node.parentNode.replaceChild(nextNode, node); node = nextNode; - var app = {}; + var stopped = false; + var detachView = function() + { + // Make a second call to `app.detachView` do nothing. + if (stopped) + { + return node; + } + stopped = true; + _VirtualDom_removeAllEventListeners(node); + return node; + }; + + var app = { + detachView: detachView, + stop: detachView + }; /**__DEBUG/ app.hotReload = function(hotReloadData) @@ -613,7 +629,9 @@ function _VirtualDom_render(vNode, eventNode, tNode) if (_VirtualDom_divertHrefToApp && vNode.__tag == 'a') { - domNode.addEventListener('click', _VirtualDom_divertHrefToApp(domNode)); + var listener = _VirtualDom_divertHrefToApp(domNode); + domNode.elmAf = listener; + domNode.addEventListener('click', listener); } _VirtualDom_applyFacts(domNode, eventNode, {}, vNode.__facts); @@ -1994,7 +2012,9 @@ function _VirtualDom_virtualizeHelp(node, tNode) if (_VirtualDom_divertHrefToApp && node.localName === 'a') { - node.addEventListener('click', _VirtualDom_divertHrefToApp(node)); + var listener = _VirtualDom_divertHrefToApp(node); + node.elmAf = listener; + node.addEventListener('click', listener); } } @@ -2077,3 +2097,67 @@ function _VirtualDom_dekey(keyedNode, tNode) __descendantsCount: keyedNode.__descendantsCount }; } + +function _VirtualDom_removeAllEventListeners(rootDomNode) +{ + var tNode = rootDomNode.elmTree; + + // Allow mounting another Elm app on this DOM node. + delete rootDomNode.elmTree; + + _VirtualDom_removeAllEventListenersHelp(tNode); +} + +function _VirtualDom_removeAllEventListenersHelp(tNode) +{ + var domNode = tNode.__domNode; + var children = tNode.__children; + + // Allow the DOM nodes to be virtualized by another Elm app. + if (domNode.nodeType === 1) + { + domNode.setAttribute(_VirtualDom_markerProperty, ''); + } + + if (domNode.elmAf) + { + domNode.removeEventListener('click', domNode.elmAf); + delete domNode.elmAf; + } + + var allCallbacks = domNode.elmFs; + if (allCallbacks) + { + for (var key in allCallbacks) + { + domNode.removeEventListener(key, allCallbacks[key]); + } + delete domNode.elmFs; + } + + for (var key in children) + { + _VirtualDom_removeAllEventListenersHelp(children[key]); + } +} + +// Used by `_Debugger_document` to remove the corner when shutting down. +function _VirtualDom_removeLastElmChild(rootDomNode) +{ + var tNode = rootDomNode.elmTree; + var children = tNode.__children; + var childNodes = rootDomNode.childNodes; + for (var i = childNodes.length - 1; i >= 0; i--) + { + var childDomNode = childNodes[i]; + for (var key in children) + { + if (childDomNode === children[key].__domNode) + { + rootDomNode.removeChild(childDomNode); + delete children[key]; + return; + } + } + } +}