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

Inline DOM property configs #10805

Closed
wants to merge 13 commits into from
Closed

Conversation

gaearon
Copy link
Collaborator

@gaearon gaearon commented Sep 24, 2017

This replaces DOM injections with static switch cases.
I think this is a lot simpler to follow.

See individual commits. I took the strategy of getting rid of individual properties on the injected configs, then getting rid of getPropertyInfo() branching calls, and finally removing the configs.

@gaearon gaearon changed the title [WIP] Inline DOM property configs Inline DOM property configs Sep 25, 2017
@gaearon
Copy link
Collaborator Author

gaearon commented Sep 25, 2017

Okay, the initial version is ready.
Curiously, despite the switches, it still reduces the bundle size a bit (2%).

Still need to test it with attribute table, add Flow types, and maybe simplify the callers.
We might also be making more function calls so worth testing if the perf is the same.

But curious to hear what you all think about this direction.

@trueadm
Copy link
Contributor

trueadm commented Sep 25, 2017

I wonder how this will impact runtime performance as object lookups tend to be quite a bit faster than large switch statements – although things might be different as I've not looked into this for a while. Considering this is a hot path though, we should look into it more. I can help with this if needed :)

I know other virtual DOM libraries recently started using ES2015 Sets for attribute/property lookups as they were giving great performance results compared to object lookups and switches.

@gaearon
Copy link
Collaborator Author

gaearon commented Sep 25, 2017

The switches are not the point and I can change them to lookups (would be nice to confirm this with data). This is more about removing the injection and consolidating the lookups in one place.

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.

I like the approach of inlining over injection. Currently, there's an error with how this changes the behavior for unknown attributes, which I left a comment about.

Here's a small test case showing the issue that this introduces:

http://dom-config-inline-attribute-error.surge.sh/

return setDOMValueAttribute;
default:
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we use a switch case here if there's only two possible return values? We could shave a off few more bytes with a ternary or if/else

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't really care about this yet, as we'll likely change what's inside of these methods anyway. I'm mostly looking for feedback on the overall changes in how functions call each other.

// Removed when strictly equal to false; present without a value when
// strictly equal to true; present with a value otherwise.
case 'download':
return 'overloadedBoolean';
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it would minify better if we used constants instead of string literals?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, flowifying and turning into something simper is in the plan. (Although it's already smaller than the original.)

Copy link
Contributor

Choose a reason for hiding this comment

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

AFAIK v8 used to/does deop switches without string literals as the case, I couldn't find anything recent that gave clarity on if that was still true, it may not matter here either. (I mention it because the return here is used in a switch elsewhere yeah?)

* Checks whether a property name is a writeable attribute.
* @method
*/
shouldSetAttribute: function(name, value) {
if (DOMProperty.isReservedProp(name)) {
if (name !== 'style' && DOMProperty.isReservedProp(name)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is style being special-cased here now?

Copy link
Collaborator Author

@gaearon gaearon Sep 25, 2017

Choose a reason for hiding this comment

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

It's the only reserved attribute that actually needs to appear in the DOM. Previously this was special cased by leaving it in the DOM config (even though the flag was set to 0 with style: 0). Now we don't have a config so it has to go somewhere else.

Copy link
Contributor

Choose a reason for hiding this comment

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

But in what case will shouldSetAttribute be called for the style prop? From what I can tell ReactDOMFiberComponent always checks for the style prop early and defers to CSSPropertyOperations. I don't think we're explicitly setting the style attribute anywhere.

Copy link
Contributor

@aweary aweary Sep 25, 2017

Choose a reason for hiding this comment

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

Is it the SSR code path? I didn't check that 😀

edit: I checked and, as expected, it isn't called for SSR either @gaearon

DOMPropertyOperations.setValueForAttribute(
node,
name,
DOMProperty.shouldSetAttribute(name, value) ? value : null,
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't this change the semantics of how unknown attributes are handled since the propertInfo check is removed? Now unknown attributes aren't being handled by setValueForAttribute so they aren't validated by isAttributeNameSafe, which means that invalid attributes will now cause a fatal error, e.g

Failed to execute 'setAttribute' on 'Element': 'foo$$bar' is not a valid attribute name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In general I found the distinction between *ForProperty and *ForAttribute very confusing because it's often lying ("properties" can use attributes).

This indeed seems like a bug, can you write a test case for it?

Copy link
Contributor

Choose a reason for hiding this comment

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

@gaearon I agree it's confusing, I wouldn't think that setValueForProperty would be setting attributes. I pushed a commit to this branch with a failing test.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd really like to get rid of this distinction if we can. It always felt a like to much indirection. At the very least, it would be nice if the property and attribute paths either had clearer separation, or we consolidated them and made sure the isAttributeNameSafe validation is applied.

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 as a rule of thumb, if you have an API like setValueForProperty it shouldn't ever hit a code path that sets it as an attribute, and vice-versa. It's confusing that there's code like node[name] = value inside shouldSetAttribute checks. From what I can tell, the naming scheme is confusing two different types of properties: what we just call "props" and properties that we need to be set as a property (what shouldUseProperty checks).

@gaearon
Copy link
Collaborator Author

gaearon commented Sep 25, 2017

I also noticed that unknown attributes on SVG started respecting capitalization. e.g. <svg><path unknownAttribute="lol" /></svg> is now unknownAttribute, not unknownattribute.

I’m not sure if it’s bad or which change caused it. If you have ideas please share :-)

Copy link
Contributor

@nhunzaker nhunzaker left a comment

Choose a reason for hiding this comment

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

Overall, I really like this approach, but I am genuinely curious about the performance differences.

Still, this feels like a big maintainability win.

domElement,
propKey,
nextProp,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

If anything, I already appreciate the reduction in branching.

DOMPropertyOperations.setValueForAttribute(
node,
name,
DOMProperty.shouldSetAttribute(name, value) ? value : null,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd really like to get rid of this distinction if we can. It always felt a like to much indirection. At the very least, it would be nice if the property and attribute paths either had clearer separation, or we consolidated them and made sure the isAttributeNameSafe validation is applied.

}
var mutationMethod = DOMProperty.getMutationMethod(name);
if (mutationMethod) {
mutationMethod(node, undefined);
Copy link
Contributor

Choose a reason for hiding this comment

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

Semi-unrelated, but method that this gets called for is value, which has this null guard:

if (value == null) {
  return node.removeAttribute('value');
}

I'm not sure if there is a big distinction between null and undefined in other parts of the code, but passing null here, like mutationMethod(node, null) feels better to me. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

Really, I think if we stop syncing the value attribute with the value property, we can just drop mutation methods all together.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could just not pass the second argument at all since it will default to undefined anyways, but I would prefer null here as well. It's also shorter 😄

Copy link
Contributor

@aweary aweary Sep 25, 2017

Choose a reason for hiding this comment

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

Really, I think if we stop syncing the value attribute with the value property, we can just drop mutation methods all together.

I wonder if we could just inline the getMutationMethod calls as well, since there's only one property that uses mutation methods. Like:

if (name === "value") {
  setDOMValueAttribute(node, null)
}

@nhunzaker
Copy link
Contributor

also noticed that unknown attributes on SVG started respecting capitalization. e.g. is now unknownAttribute, not unknownattribute.

Personally I think this is an enhancement, on the current 16 RC, if you do <svg unknownAttribute="lol"> does SSR hydration warn about bad casing?

@gaearon
Copy link
Collaborator Author

gaearon commented Sep 25, 2017

I haven't checked. Want to send a PR to get this check into attribute table?

@gaearon
Copy link
Collaborator Author

gaearon commented Sep 25, 2017

Regarding <svg unknownAttribute="lol"> I would expect to see this warning:

React does not recognize the `%s` prop on a DOM element.

Not sure why I didn't. Or maybe I missed it.

@aweary
Copy link
Contributor

aweary commented Sep 25, 2017

Not sure why I didn't. Or maybe I missed it.

@gaearon I tested<svg unknownAttribute="lol" /> locally and I did get the expected warning.

@gaearon
Copy link
Collaborator Author

gaearon commented Jan 5, 2018

Meh. I removed some of this indirection in other PRs and the rest isn't really helpful.

@gaearon gaearon closed this Jan 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants