-
Notifications
You must be signed in to change notification settings - Fork 80
Batch: Support browser extensions, Google Translate, fix Html.map, and more #187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
lydell
wants to merge
110
commits into
elm:master
Choose a base branch
from
lydell:safe
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
`__oldDomNodes` could have gotten the name `i` when compiled and broken things. It only worked before because of luck.
According to this benchmark, setting .data is faster than .replaceData(): https://jsbench.me/80m6mbx5l1/1
This reverts commit 70989cf.
Instead of at `Number.MIN_SAFE_INTEGER`. While `Number.MIN_SAFE_INTEGER` doubles the amount of representable integers, there are reasons not to use it: - Debugging numbers near zero is easier, than numbers with a lot of digits. - In elm/browser I added another ever-increasing counter for animation frames, and there it was not possible to use negative numbers. So we’d run out of animation frame counting before we run out of render counting anyway. - It would still take like 25 000 years until we overflow. - It wouldn’t surprise me if JS engines can optimize numbers that are near zero in some way.
This was referenced Jun 1, 2025
One might think that if `oldNode === newNode` no changes are needed, but users can mutate properties, for example by typing into text inputs, so we still need to apply properties. This happens when using constants or `lazy`.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Intro
This PR:
For those who would like to use this PR in their own app – see https://github.com/lydell/elm-safe-virtual-dom.
The above link also explains exactly what is changed, in a way that should be somewhat understandable even if you’re not super into the real and virtual DOM.
Below is a more technical, condensed version of the above link.
Summary of changes and fixed issues
Html.map
bug where messages of the wrong type can appear inupdate
functions is fixed, by completely changing howHtml.map
works. The old code was very clever and could theoretically skip more work in some cases, but was also a bit difficult to understand. The new code is instead very simple, leaving little room for errors. Closes elm/virtual-dom#105, closes elm/virtual-dom#162, closes elm/virtual-dom#171, closes elm/virtual-dom#166 (PR), closes elm/html#160, closes elm/compiler#2069Html.Keyed
. The new algorithm is slightly smarter without losing performance, and uses the new Element.prototype.moveBefore API, if available, which allows moving an element on the page without “resetting” it (scroll position, animations, loaded state for iframes and video, etc.). Element.prototype.moveBefore was added to the specification recently. Closes elm/virtual-dom#175, closes elm/virtual-dom#178, closes elm/virtual-dom#183, closes Use Object.create(null) instead of {} to compute keyed diffs #180_VirtualDom_virtualize
is now completed, making it usable in practice, for example for elm-pages. This means that server-side rendered pages no longer have to redraw the whole page when Elm initializes (which seems to have been the intention for_VirtualDom_virtualize
all along, but hasn’t worked in practice).--primary-color
, can now be set withHtml.Attributes.style "--primary-color" "salmon"
. CSS custom properties are incredibly useful for implementing theming (such as a dark mode). Closes elm/html#177, closes Allow CSS custom properties by using style.setProperty() #127.Svg.Attributes.xlinkHref
no longer mutates the DOM on every single render, which caused flickering in Safari sometimes. This was due to an oversight in the diffing of namespaced attributes. Closes elm/virtual-dom#62, closes Fix diffing for namespaced attributes (Fixes #62) #159lazy
no longer changes behavior for<input>
. Closes Lazy is too lazy for inputs #189The key changes
Making Elm work well with browser extensions, third-party scripts and page translators required three key changes.
1. Attaching DOM nodes on the virtual nodes
A render in Elm currently works like this:
view
._VirtualDom_addDomNodes
).It’s step 3 that can crash if a browser extension has changed the DOM.
With this PR, there is no more step 3. Instead, we store the DOM node on the virtual node. This means that we don’t even need patches. While diffing in step 2, we simply make the changes immediately to the correct DOM node. Since there’s no walking of the DOM tree, it doesn’t matter what changes a browser extension makes.
(However, since the same virtual DOM node can appear more than once in the tree, it’s a bit more complicated than that – see New algorithm for pairing virtual DOM nodes.)
2. Cooperating with page translators
There’s some new code for cooperating with page translators (Google Translate, as well as the translators built into Firefox and Safari). If the diff tells us to update a text node, but we detect that the text node in question has been translated, we tell the parent element of that text node to delete all text node children and re-render them. The page translator then kicks in and re-translates that element (taking the entire text of the element into account for a more accurate translation).
(Evan, if you remember us talking about this at Elm Camp 2024 – don’t worry, there is no “bug” introduced on purpose regarding
<font>
elements – they can still be created by Elm just fine. Google Translate oddly uses<font>
tags for translated text, but it turned out to easy to tell the difference between<font>
tags created by Elm and by others.)3. Virtualizing more conservatively
When using
Browser.document
andBrowser.application
, Elm takes charge of<body>
. Third-party scripts and browser extensions put things in<body>
too, and crucially they might do so before Elm initializes. Currently, Elm virtualizes all elements in the mount node (<body>
) in this case, which most likely results in the extra things in<body>
added by third-party scripts and browser extensions being removed.This PR changes the virtualization behavior to only virtualize text nodes, and elements that have the
data-elm
attribute, leaving everything else alone. I adddata-elm
automatically to all elements created by Elm, so if you server-side render your page by running your Elm code on the server, you get that for free without having to do anything. You can read more about how this can be used in practice at elm-pages PR 519.Note: This is the reason the PR is only 99.99 % backwards compatible. Read more in the “Breaking” changes section below.
The other changes
So, I’ve talked about the key changes. But the “Summary of changes and fixed issues” section mentions a few more things. Why are they in this PR?
Html.map
: I needed to work onHtml.map
anyway to make it work with the new DOM node pairing algorithm.Html.Keyed
: Same thing.lazy
fix for inputs: It was incredibly easy to fix thanks to the other changes in this PR.Detailed descriptions of changes
“Breaking” changes
I haven’t changed the Elm interface at all (no added functions, no changed functions, no removed functions, or types). All behavior except one detail should be equivalent, except less buggy.
The goal was to be 100 % backwards compatible. For some people, it is. For others, there’s one change that is in “breaking change territory” which can be summarized as: Elm no longer empties the mount element. It’s easily fixed by adding the
data-elm
attribute to select elements in the HTML.That’s how users will perceive it. In reality, the actual change is which elements are virtualized (to support third-party scripts better, as mentioned in the “Virtualizing more conservatively” section).
Read all about these “Breaking” changes in the safe-virtual-dom documentation.
Performance
O(n)
complexity.Related PR:s
<a>
click listeners: Allow virtualization to set up link click listeners browser#137requestAnimationFrame
error loop on virtual DOM crashes: Fix animation frames browser#138Code style
I’ve done my best to: