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

Unperformant Stringify Warning - WIP #12263

Closed

Conversation

Swieckowski
Copy link

Closes #12209.
performance.now() was not supported while I was writing this, so I'm using the less precise Date.now().

I have written a test to see if the warning shows up if something takes too long to stringify, but don't have one for if it does not warn, since I didn't see anyone else test for anything like that.

@xilaraux
Copy link

@Swieckowski
What did you mean by

performance.now() was not supported

@Swieckowski
Copy link
Author

In the simplest terms, if I run yarn test I'll fail a bunch of tests with ReferenceError: performance is not defined.

@xilaraux
Copy link

I see, thx for explanation.

@pshrmn
Copy link

pshrmn commented Feb 21, 2018

Node 8.5 is required for performance.now, but devEngines specifies that all 8.X versions are supported. I don't know what the process is for deciding which Node versions to support, so I'm not sure if 8.5 can just be made the minimum version (it looks like 8.9 is LTS).

} else {
node.setAttribute(attributeName, '' + (value: any));
node.setAttribute(attributeName, (value: any).toString());

Choose a reason for hiding this comment

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

isn't this will throw on undefined value? we only strictly check for null above it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, @Swieckowski please revert this. We were intentionally not using toString

@n3tr
Copy link
Contributor

n3tr commented Mar 1, 2018

Can we do check if performance.now is available and fallback to Date.now() if it's not, like in ReactNativeFrameScheduling.js

const hasNativePerformanceNow =
typeof performance === 'object' && typeof performance.now === 'function';
const now = hasNativePerformanceNow
? () => performance.now()
: () => Date.now();

it('should warn if custom attributes take too long to stringify', () => {
const container = document.createElement('div');
const attributeValue = {foo: 'bar'};
attributeValue.toString = function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO, We don't need actual implementation (which will slow down the test suite). I think better do polyfill global.performance in the test setup and implement our own version of it (like @aweary mentioned in #12209 (comment)).

For an idea, we can take a look at

global.performance = {
now() {
return now;
},
};

or jest.mockImplementation might be helpful.

Hope this help :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, let's go ahead and go with mocking performance instead of actually taking too much time.

// Only used in DEV, stringifies a value and Warns if it took too long
const stringifyWithPerformanceWarning = value => {
const stringifyStart = Date.now();
let attributeValue = (value: any).toString();
Copy link
Contributor

Choose a reason for hiding this comment

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

Use const over let.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please use '' + value instead of toString here as well.


warning(
stringifyEnd - stringifyStart <= 2,
'Stringifying your attribute is causing perfomance issues',
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: pefomance => performance.

Copy link
Contributor

Choose a reason for hiding this comment

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

The messaging here isn't clear enough. Let's also pass the attribute name to stringifyWithPerformanceWarning, improve the wording, and also include a component stack. You can get a component stack by importing and using getCurrentFiberStackAddendum from ReactDebugCurrentFiber.

Lets make it look like:

warning(
  stringifyEnd - stringifyStart <= 2,
  'The attribute `%s` took more than 2 milliseconds to stringify. This usually means you provided a large object ' +
  'as the value for a DOM attribute, which can lead to performance issues.%s',
  attributeName,
  getCurrentFiberStackAddendum(),
)

} else {
// `setAttribute` with objects becomes only `[object]` in IE8/9,
// ('' + value) makes it output the correct toString()-value.
attributeValue = '' + (value: any);
attributeValue = (value: any).toString();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was this changed? The comment above no longer reflects the actual code. Aren't we concerned about IE8/9 anymore? If not, then the comment should also be updated, I guess.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

@Swieckowski please revert his back as well.

Copy link
Contributor

@aweary aweary left a comment

Choose a reason for hiding this comment

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

Sorry for the late review, and thanks again for doing this @Swieckowski. Please see my comments for what we need to change.

it('should warn if custom attributes take too long to stringify', () => {
const container = document.createElement('div');
const attributeValue = {foo: 'bar'};
attributeValue.toString = function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, let's go ahead and go with mocking performance instead of actually taking too much time.

} else {
node.setAttribute(attributeName, '' + (value: any));
node.setAttribute(attributeName, (value: any).toString());
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, @Swieckowski please revert this. We were intentionally not using toString

} else {
// `setAttribute` with objects becomes only `[object]` in IE8/9,
// ('' + value) makes it output the correct toString()-value.
attributeValue = '' + (value: any);
attributeValue = (value: any).toString();
Copy link
Contributor

Choose a reason for hiding this comment

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

@Swieckowski please revert his back as well.

@@ -172,3 +180,17 @@ export function setValueForProperty(
}
}
}

// Only used in DEV, stringifies a value and Warns if it took too long
const stringifyWithPerformanceWarning = value => {
Copy link
Contributor

Choose a reason for hiding this comment

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

For consistency let's move this to the top of the file, and then only define it in DEV

let stringifyWithPerformanceWarning;

if (__DEV__) {
  stringifyWithPerformanceWarning = function(value) {
   // ...
  }
}
``

// Only used in DEV, stringifies a value and Warns if it took too long
const stringifyWithPerformanceWarning = value => {
const stringifyStart = Date.now();
let attributeValue = (value: any).toString();
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use '' + value instead of toString here as well.


// Only used in DEV, stringifies a value and Warns if it took too long
const stringifyWithPerformanceWarning = value => {
const stringifyStart = Date.now();
Copy link
Contributor

Choose a reason for hiding this comment

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

We want to use performance.now if it exists, and fallback to Date.now if it doesn't. Lets do the same thing we do here: https://github.com/facebook/react/blob/master/packages/react-native-renderer/src/ReactNativeFrameScheduling.js#L12-L17

Copy link
Author

Choose a reason for hiding this comment

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

Thanks a lot for the review, I'll get on it sometime soon!


warning(
stringifyEnd - stringifyStart <= 2,
'Stringifying your attribute is causing perfomance issues',
Copy link
Contributor

Choose a reason for hiding this comment

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

The messaging here isn't clear enough. Let's also pass the attribute name to stringifyWithPerformanceWarning, improve the wording, and also include a component stack. You can get a component stack by importing and using getCurrentFiberStackAddendum from ReactDebugCurrentFiber.

Lets make it look like:

warning(
  stringifyEnd - stringifyStart <= 2,
  'The attribute `%s` took more than 2 milliseconds to stringify. This usually means you provided a large object ' +
  'as the value for a DOM attribute, which can lead to performance issues.%s',
  attributeName,
  getCurrentFiberStackAddendum(),
)

@gaearon
Copy link
Collaborator

gaearon commented Aug 9, 2018

One more concern is that performance.now() itself might add overhead. We'd want to measure the impact of this on a large tree.

@hassaans
Copy link

We'll only be showing the warning in development mode. Performance drops in development should be insignificant.

@stale
Copy link

stale bot commented Jan 10, 2020

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Jan 10, 2020
@stale
Copy link

stale bot commented Jan 17, 2020

Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you!

@stale stale bot closed this Jan 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Resolution: Stale Automatically closed due to inactivity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Warn if stringifying an attribute takes too long