Skip to content
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

add defer hydration support to fast-element #6441

Merged
merged 18 commits into from
Oct 21, 2022

Conversation

nicholasrice
Copy link
Contributor

@nicholasrice nicholasrice commented Oct 11, 2022

Pull Request

📖 Description

This PR illustrates a general approach to supporting defer-hydration in fast-element. The goal was to support hydration scenarios while minimally impacting scenarios that do not need SSR hydration.

To support hydration, client-side JS would set up support prior to defining custom elements:

// main-bundle.ts
import "@microsoft/fast-element/install-element-hydration";
// or
import { HydrateableElementController } from "@microsoft/fast-element/element-hydration";
HydrateableElementController.install();

// Define custom elements

This PR adds a new configuration concept, ElementControllerStrategy, which is leveraged to construct each element instances ElementController. The strategy is configured to return a HydrationElementController, an extension of ElementController which delays the connect() super call until the derfer-hydration attribute is removed. Over time, this class will evolve to perform hydration of declarative shadow DOM rather than re-creating template DOM.

🎫 Issues

👩‍💻 Reviewer Notes

📑 Test Plan

✅ Checklist

General

  • I have included a change request file using $ yarn change
  • I have added tests for my changes.
  • I have tested my changes.
  • I have updated the project documentation to reflect my changes.
  • I have read the CONTRIBUTING documentation and followed the standards for this project.

Component-specific

⏭ Next Steps

@github-actions
Copy link

github-actions bot commented Oct 11, 2022

📊 Tachometer Benchmark Results

Summary

clickTrigger10x

  • repeat-basic-splice-itemCount=1000&deleteCount=20&addCount=20: unsure 🔍 -4% - +18% (-4.10ms - +19.11ms)
    users/nirice/defer-hydration vs master Customize summary
  • repeat-nested-push-itemCount=100&addCount=20: unsure 🔍 -1% - +1% (-2.17ms - +2.25ms)
    users/nirice/defer-hydration vs master Customize summary
  • repeat-nested-reverse-itemCount=100: unsure 🔍 -2% - +1% (-7.34ms - +5.29ms)
    users/nirice/defer-hydration vs master Customize summary
  • repeat-nested-shift-itemCount=100: unsure 🔍 -1% - +0% (-2.08ms - +0.28ms)
    users/nirice/defer-hydration vs master Customize summary
  • repeat-nested-unshift-itemCount=100&addCount=20: unsure 🔍 -0% - +1% (-0.70ms - +1.65ms)
    users/nirice/defer-hydration vs master Customize summary

create10k

  • render-create10k: unsure 🔍 -2% - +1% (-5.29ms - +2.76ms)
    users/nirice/defer-hydration vs master Customize summary

createDelete5x

  • render-createDelete5x: unsure 🔍 -1% - +2% (-7.04ms - +8.16ms)
    users/nirice/defer-hydration vs master Customize summary

runFile1k

  • observable-runFile1k: unsure 🔍 -2% - +27% (-0.09ms - +2.36ms)
    users/nirice/defer-hydration vs master Customize summary

update10th

  • render-update10th: unsure 🔍 -1% - +1% (-1.29ms - +1.59ms)
    users/nirice/defer-hydration vs master Customize summary

usedJSHeapSize

  • observable-runFile1k: unsure 🔍 -1% - +0% (-0.43ms - +0.11ms)
    users/nirice/defer-hydration vs master Customize summary
  • render-create10k: unsure 🔍 -0% - +0% (-0.01ms - +0.01ms)
    users/nirice/defer-hydration vs master Customize summary
  • render-createDelete5x: unsure 🔍 -0% - +0% (-0.04ms - +0.09ms)
    users/nirice/defer-hydration vs master Customize summary
  • render-update10th: unsure 🔍 -0% - +0% (-0.01ms - +0.00ms)
    users/nirice/defer-hydration vs master Customize summary
  • repeat-basic-splice-itemCount=1000&deleteCount=20&addCount=20: unsure 🔍 -1% - +0% (-0.32ms - +0.09ms)
    users/nirice/defer-hydration vs master Customize summary
  • repeat-nested-push-itemCount=100&addCount=20: unsure 🔍 -0% - +0% (-0.03ms - +0.02ms)
    users/nirice/defer-hydration vs master Customize summary
  • repeat-nested-reverse-itemCount=100: unsure 🔍 -0% - +0% (-0.04ms - +0.05ms)
    users/nirice/defer-hydration vs master Customize summary
  • repeat-nested-shift-itemCount=100: unsure 🔍 -0% - +0% (-0.03ms - +0.03ms)
    users/nirice/defer-hydration vs master Customize summary
  • repeat-nested-unshift-itemCount=100&addCount=20: unsure 🔍 -0% - +0% (-0.02ms - +0.03ms)
    users/nirice/defer-hydration vs master Customize summary

Results

observable-runFile1k

runFile1k

VersionAvg timevs mastervs users/nirice/defer-hydration
master8.66ms - 10.51ms-unsure 🔍
-8% - +20%
-0.67ms - +1.79ms
users/nirice/defer-hydration8.22ms - 9.84msunsure 🔍
-18% - +7%
-1.79ms - +0.67ms
-

usedJSHeapSize

VersionAvg timevs mastervs users/nirice/defer-hydration
master48.57ms - 48.95ms-unsure 🔍
-1% - +0%
-0.39ms - +0.14ms
users/nirice/defer-hydration48.70ms - 49.07msunsure 🔍
-0% - +1%
-0.14ms - +0.39ms
-
render-create10k

create10k

VersionAvg timevs mastervs users/nirice/defer-hydration
master284.02ms - 290.23ms-unsure 🔍
-1% - +2%
-3.99ms - +4.76ms
users/nirice/defer-hydration283.66ms - 289.82msunsure 🔍
-2% - +1%
-4.76ms - +3.99ms
-

usedJSHeapSize

VersionAvg timevs mastervs users/nirice/defer-hydration
master47.27ms - 47.28ms-unsure 🔍
-0% - +0%
-0.02ms - +0.01ms
users/nirice/defer-hydration47.27ms - 47.29msunsure 🔍
-0% - +0%
-0.01ms - +0.02ms
-
render-createDelete5x

createDelete5x

VersionAvg timevs mastervs users/nirice/defer-hydration
master302.31ms - 310.96ms-unsure 🔍
-4% - +1%
-11.60ms - +1.62ms
users/nirice/defer-hydration306.62ms - 316.63msunsure 🔍
-1% - +4%
-1.62ms - +11.60ms
-

usedJSHeapSize

VersionAvg timevs mastervs users/nirice/defer-hydration
master54.14ms - 54.24ms-unsure 🔍
-0% - +0%
-0.08ms - +0.06ms
users/nirice/defer-hydration54.15ms - 54.25msunsure 🔍
-0% - +0%
-0.06ms - +0.08ms
-
render-update10th

update10th

VersionAvg timevs mastervs users/nirice/defer-hydration
master155.21ms - 157.59ms-unsure 🔍
-1% - +1%
-1.30ms - +2.02ms
users/nirice/defer-hydration154.89ms - 157.19msunsure 🔍
-1% - +1%
-2.02ms - +1.30ms
-

usedJSHeapSize

VersionAvg timevs mastervs users/nirice/defer-hydration
master47.27ms - 47.28ms-unsure 🔍
-0% - +0%
-0.01ms - +0.01ms
users/nirice/defer-hydration47.27ms - 47.29msunsure 🔍
-0% - +0%
-0.01ms - +0.01ms
-
repeat-basic-splice-itemCount=1000&deleteCount=20&addCount=20

clickTrigger10x

VersionAvg timevs mastervs users/nirice/defer-hydration
master104.31ms - 122.83ms-unsure 🔍
-14% - +8%
-16.15ms - +9.45ms
users/nirice/defer-hydration108.08ms - 125.76msunsure 🔍
-8% - +14%
-9.45ms - +16.15ms
-

usedJSHeapSize

VersionAvg timevs mastervs users/nirice/defer-hydration
master46.87ms - 47.17ms-unsure 🔍
-0% - +1%
-0.15ms - +0.28ms
users/nirice/defer-hydration46.81ms - 47.11msunsure 🔍
-1% - +0%
-0.28ms - +0.15ms
-
repeat-nested-push-itemCount=100&addCount=20

clickTrigger10x

VersionAvg timevs mastervs users/nirice/defer-hydration
master215.39ms - 216.78ms-faster ✔
0% - 1%
0.08ms - 2.97ms
users/nirice/defer-hydration216.34ms - 218.88msslower ❌
0% - 1%
0.08ms - 2.97ms
-

usedJSHeapSize

VersionAvg timevs mastervs users/nirice/defer-hydration
master51.64ms - 51.67ms-unsure 🔍
-0% - +0%
-0.02ms - +0.04ms
users/nirice/defer-hydration51.63ms - 51.67msunsure 🔍
-0% - +0%
-0.04ms - +0.02ms
-
repeat-nested-reverse-itemCount=100

clickTrigger10x

VersionAvg timevs mastervs users/nirice/defer-hydration
master637.15ms - 648.13ms-unsure 🔍
-1% - +2%
-4.55ms - +11.32ms
users/nirice/defer-hydration633.52ms - 644.98msunsure 🔍
-2% - +1%
-11.32ms - +4.55ms
-

usedJSHeapSize

VersionAvg timevs mastervs users/nirice/defer-hydration
master58.59ms - 58.64ms-unsure 🔍
-0% - +0%
-0.04ms - +0.03ms
users/nirice/defer-hydration58.59ms - 58.65msunsure 🔍
-0% - +0%
-0.03ms - +0.04ms
-
repeat-nested-shift-itemCount=100

clickTrigger10x

VersionAvg timevs mastervs users/nirice/defer-hydration
master219.48ms - 220.86ms-unsure 🔍
-1% - +0%
-1.52ms - +0.70ms
users/nirice/defer-hydration219.72ms - 221.44msunsure 🔍
-0% - +1%
-0.70ms - +1.52ms
-

usedJSHeapSize

VersionAvg timevs mastervs users/nirice/defer-hydration
master51.62ms - 51.66ms-unsure 🔍
-0% - +0%
-0.02ms - +0.04ms
users/nirice/defer-hydration51.61ms - 51.66msunsure 🔍
-0% - +0%
-0.04ms - +0.02ms
-
repeat-nested-unshift-itemCount=100&addCount=20

clickTrigger10x

VersionAvg timevs mastervs users/nirice/defer-hydration
master216.17ms - 217.62ms-unsure 🔍
-1% - +0%
-1.25ms - +0.83ms
users/nirice/defer-hydration216.37ms - 217.85msunsure 🔍
-0% - +1%
-0.83ms - +1.25ms
-

usedJSHeapSize

VersionAvg timevs mastervs users/nirice/defer-hydration
master51.64ms - 51.67ms-unsure 🔍
-0% - +0%
-0.04ms - +0.02ms
users/nirice/defer-hydration51.64ms - 51.68msunsure 🔍
-0% - +0%
-0.02ms - +0.04ms
-

tachometer-reporter-action v2 for Validate Benchmarks

@nicholasrice nicholasrice linked an issue Oct 11, 2022 that may be closed by this pull request
@nicholasrice nicholasrice force-pushed the users/nirice/defer-hydration branch from a115c0e to 829d530 Compare October 13, 2022 21:22
@nicholasrice nicholasrice marked this pull request as ready for review October 13, 2022 21:28
@nicholasrice nicholasrice force-pushed the users/nirice/defer-hydration branch from 1cbc1a3 to 436fdad Compare October 13, 2022 21:33
@EisenbergEffect EisenbergEffect self-requested a review October 18, 2022 19:38
Copy link
Contributor

@EisenbergEffect EisenbergEffect left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still think through the couple of issues I noted a bit more.

delete (element as any)[propertyName];
boundObservables[propertyName] = value;
delete element[propertyName];
element[propertyName] = value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It occurs to me that this will cause all the change handlers to fire during the constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops I missed this while reverting, this should be unchanged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh... I feel like a commit got lost somehow. I'll fix

ElementController,
ElementControllerStrategy,
} from "./components/element-controller.js";
export { addHydrationSupport } from "./components/hydration.js";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the hydration support should not be part of the core but rather provided as an optional through export path.

@nicholasrice
Copy link
Contributor Author

@EisenbergEffect The property re-assignment is reverted now; not sure what happened there, I thought I did that already. I also added two export paths for hydration. The first exports the controller and the second installs it as a side effect. I added both to support conditional installation, which I don't believe is possible with ESM (can't await at top level with import() and can't import { } from inside a conditional)

@EisenbergEffect EisenbergEffect self-requested a review October 19, 2022 21:28
@@ -461,12 +470,24 @@ export class ElementController<TElement extends HTMLElement = HTMLElement>
if (definition === void 0) {
throw FAST.error(Message.missingElementDefinition);
}
if (elementControllerStrategy === undefined) {
Copy link
Contributor

@EisenbergEffect EisenbergEffect Oct 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing this should we just set the strategy right after the close of the class definition? Then we don't have to do the undefined check every time. Similar to the way we are handling the style strategy I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, yeah that makes sense

*/
public static setStrategy(strategy: ElementControllerStrategy) {
elementControllerStrategy = strategy;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set strategy right here?

@nicholasrice nicholasrice force-pushed the users/nirice/defer-hydration branch from 35b712e to ff7b237 Compare October 19, 2022 23:00
@nicholasrice nicholasrice merged commit 32ca5de into master Oct 21, 2022
@nicholasrice nicholasrice deleted the users/nirice/defer-hydration branch October 21, 2022 05:58
janechu pushed a commit that referenced this pull request Jun 10, 2024
* adding mutation observer and starting to refactor fast-element

* break observable re-binding into it's own method

* re-apply observable values in constructor so that they don't need to be manually cached and re-applied during connection

* revert unnecessary binding observer changes

* refactor and reogranize draft implementation, adding tests

* added features to exports and updated api report

* Change files

* align StyleStrategy and ElementControllerStrategy implementations

* fix api report

* revert observable property re-assignment

* re-organize hydration support into export paths

* leverage strategy API internally

* make mutation observer internal for now

* revert removal of context export path

* fix unit test failure

* perform default strategy assignment as file side effect

Co-authored-by: nicholasrice <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat: add hydration deferal to FAST
3 participants