Repository navigation
fix(core): don't reload iframes and media during hydration #71874
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
jsaguet
wants to merge
1
commit into
angular:main
Choose a base branch
from
jsaguet:fix/hydration-skip-unchanged-dom-writes
base: main
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.
+542
−11
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AGENT: Running
setupStaticAttributeswithskipIfUnchangedduring hydration (creation mode) compares the SSR DOM against the statictNodeattributes/classes/styles and overwrites them whenever they differ. Because creation mode runs before directivengOnInitand update-mode bindings ([class.*],[style.*],[attr.*]), any attribute, class, or style that was legitimately augmented on the server by a directive or binding will differ from the statictNodevalue and get clobbered during hydration:class/style: For<div class="static" [class.active]="true">, the SSR HTML hasclass="static active". In creation mode,element.getAttribute('class') !== classes('static active' !== 'static') evaluates totrue, sowriteDirectClassclobbers the attribute back to'static', only for the update pass to re-add'active'immediately after—resulting in two unnecessary DOM mutations during hydration.NgOptimizedImagein NgOptimizedImage makes the browser select each image source again during hydration #71873): For<img ngSrc="hero.jpg" width="400" height="300" sizes="50vw">, SSR renderssizes="auto, 50vw". Herenative.getAttribute('sizes') !== '50vw'istrue, sosetupStaticAttributesoverwritessizesback to'50vw', andNgOptimizedImage.ngOnInit()then writes'auto, 50vw'again.When hydrating an existing element (
!wasLastNodeCreated()), the server has already serialized its static attributes, classes, and styles. Have we considered skippingsetupStaticAttributesaltogether when!wasLastNodeCreated()(aside fromisAnimationPropproperties which aren't serialized as HTML attributes)? That would avoid all thegetAttributereads on static attributes and prevent clobbering directive/binding state from SSR.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those writes still happen, as they do today:
static activediffers from the template, so it's replaced, and[class.active]adds the class back.I'd keep the comparison rather than skip
setupStaticAttributesaltogether, because skipping is what #71200 does, and your review there lists what it breaks. With the comparison, a server-only class or a value from an older deployment is still corrected, animation properties are still set, and anngSkipHydrationhost is still written when it differs. Root hosts aren't touched.The cost is that the
sizescase stays as it is. #71873 tracks that one.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AGENT: Thanks for updating the PR and scoping down the dynamic property/attribute checks—narrowing
isFirstWriteOfRenderedResourcetovideo,audio,iframe,embed, andobject[data]makes the property-binding side much safer and avoids the pitfalls with generic DOM property reflection and custom elements.Regarding
setupStaticAttributes:Fair point about the subtle regressions identified in #71200 (such as root hosts, animation properties, and
ngSkipHydration).However, comparing
element.getAttribute('class') !== classesandelement.getAttribute('style') !== stylesin creation mode has a fundamental limitation: it only checks static classes/styles against the SSR DOM, but the SSR DOM legitimately contains classes and styles added by update bindings ([class.*],[style.*]) and directives. On any element combining static classes with dynamic bindings (e.g.<div class="card" [class.active]="isActive">),getAttribute('class')is'card active', which doesn't match'card', so it still overwrites the class back to'card'in creation mode, only for[class.active]to re-add'active'right after.Rather than trying to partially patch
classandstylereconciliation here (which is a much broader problem with styling pipelines and directives like in #71873):What do you think about narrowing this PR specifically to the media/resource loading bug (#71872)?
src/datacheck inshared.ts(isFirstWriteOfRenderedResource).setupStaticAttributes/setUpAttributes, guard only staticsrc/datafor these media elements (video,audio,iframe,embed,object), leavingclassandstyleuntouched.That gives a very clean separation of concerns:
<video>,<audio>,<iframe>) without touching the fragile styling pipeline.NgOptimizedImagespecifically, which is genuinely needed becauseNgOptimizedImagemanages its attributes imperatively viaRenderer2.setAttributeinngOnInit(bypassing core's template binding pipeline entirely).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I narrowed the PR as you suggested and squashed it into one commit:
srconiframe,embed,video, andaudio, anddataonobject, are compared.classandstyleare written as before.isResourceAttribute.I opened #71971 for the cost of the other writes, with the measurements and the
class/styleproblem you described.