-
Notifications
You must be signed in to change notification settings - Fork 46.9k
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
inlineElements
optimization breaks on older browsers; discussion about Symbols and React.elementFromObject() API
#5138
Comments
inlineElements
optimization breaks on older browsers; discussion about Symbols and new APIinlineElements
optimization breaks on older browsers; discussion about Symbols and React.elementFromObject() API
The purpose of inlining the elements is to get rid of function calls so I don't think adding a |
I thought so too at first. But the really expensive part of I think our discussion re: using |
I feel that adding a new “blessed” top-level API to work around a bundling / polyfill issue is a bit of an overkill. (By “blessed” I mean something like |
Perhaps, and it might lead to some confusion in the future - but it would be nice for other (non-babel) tooling in the future, like cljs, that wants to benefit from using raw objects. |
@gaearon I'd argue that it doesn't seem like a bundling issue in the long run. The core issue is that React should not accept random objects as components. What better way to do that than to expose an API that allows that? The current functionality is an API, but it relys on the global symbol registry and it's not clear (to me at least) why that approach was taken, or what makes that better than exposing a function or constant from |
cc @sebmarkbage |
The If React can coordinate with multiple versions of itself, then Babel can as well. One possible solution would be that Babel depends on a module that exposes this or somehow opts out of inlining the polyfill so that it gets the same fallback as React itself. |
I'm totally pro-symbol, to be clear, I was mostly surprised to see that the symbol registry was the actual using facing UI, rather than having React internally pull a symbol from the the registry and expose it as a constant. |
I left a comment on the original PR re: the magicNum - why not just use |
This problem is still present and will cause unexpected breakage in older browsers in production builds only. Since profiling has shown that the function call is actually not the bottleneck, I think it makes sense to put this back on the table as a blessed API (if even somewhat undocumented) as not to break apps in production. It would be great for interop with other optimizing runtimes as a fast path alternative to Alternatively, could the Babel helpers have this interesting desire to remain "pure", but I don't consider relying on a Symbol polyfill (and failing interop between a polyfill and native) to be pure. This seems to be the only long-term solution that will fix this for good. |
How does this work with |
In that case, Symbol would be polyfilled in the library code (it would have to be if you used Symbol.iterator), and both polyfills would fall back to global.Symbol if present. The crux of the problem here is that React has a fallback for when Symbol is not present, and it's not to polyfill it - it's to use a magicnum. So it's possible that user code uses a polyfill (say via Babel), but the library does not and thus uses the magicnum, and the magicnum !== the polyfill. I'm thinking the way to fix this is not to fix babel-plugin-transform-react-inline-elements, but to make its existence unnecessary. Originally, it was thought that replacing the function call with an inline object would be faster, but it turns out that's not true and the transform now uses the If there's a function that can create React elements faster in production than |
The inlining is only part of the story. The point is to decouple this from the React package itself and not needing that dependency. I think you're missing the point though. The point is to explore a generic solution around nominal branding. E.g. for Polyfilling pattern matching. Not just for React's particular value type but any number of similar data structures. That's why we wouldn't use There is also a larger issue about how polyfills should work across these boundaries. I suspect that this partial polyfilling solution is the thing that is broken and will break down in more ways than this particular case. Others will have similar problems. It is not obvious to me why React's fallback is different from other feature tests that is standard practice. E.g. we also use What would be the best way to polyfill A similar problem would occur with anything that requires global state. The spec has intentionally tried to avoid global state but for example the Zones proposal would have a similar requirement. I suppose that when you have global state, you really need to have a shared object reference and a global polyfill is the only solution. So, maybe global polyfills should be best practice? |
This is the purpose of @@toStringTag, isn't it? Rather than React.Element defining a '$$typeof', it should define In non-ES6 environments, we can't prevent XSS in a way that matters and is robust. There is no solution (aside from using types that are not JSON-serializable, but of course this is one-and-done if a library uses it) that actually works. We could set I think the magicnum should just be ditched entirely, which would fix the bug. Re: Object.assign, it's not the same thing. Object.assign polyfills and "the real thing" have the same result. By definition, a Symbol polyfill and an actual Symbol cannot have reference equality, and reference equality is the primary use of a Symbol. The same bug also affects Reproduction: Say I pass an iterable into React, in an environment without native Symbol, where userland code has the polyfill but libraries do not:
I've run this to verify and it indeed fails in the case where the runtime is missing support, such as in Node 0.10, but succeeds in newer runtimes like 5.5. In the specific case of React, there are a few actions that should be taken:
|
Note inline-elements breakage on non-ES6 environments without the global polyfill. Discussion https://phabricator.babeljs.io/T2517, facebook/react#5138
I'm hoping that we will be able to find a way to share objects across the If the I admit that this is a long shot and many pieces need to fit together but something to consider. In the meantime, the way I read this is there are three variables:
If you use local polyfills, then both 2 and 3 breaks and your code is broken. So we have to fix something. a) If we fix only 2, then 3 still breaks and your code is broken. Option A doesn't fix anything so it is not very helpful. Option B won't work unless we can change how all libraries do this feature detection of Option C seems the most plausible to me. If we manage to fix Option C there is no point in changing how React works. Are there any other options? |
You mentioned before that it was intentional that these objects would not work across |
The idea is that they would not work across |
Any progress of this? Is there a workaround for this till the issue is fixed that doesn't involve adding |
@shubhamsizzles For now I put this in my browser entrypoint so all libs are using the same global.Symbol = require('core-js/es6/symbol'); // Fix react.inlineElements |
I’ll close this as stale. |
Unfortunately this is still an issue on old browsers unless you globally polyfill Symbol. Have there been any strategies discussed internally that didn't make it here? |
No, there haven't. My impression is we just don't support this use case. You could either use a global polyfill or not enable the transform. |
I think many tool authors would be very happy with a |
Meh. I don’t see us fixing this. We rely on Our suggestion is: if you use a Symbol polyfill, it is on you to ensure it’s the same one used globally throughout your code. We’re happy to accept PRs emphasizing this in the documentation. |
This action would still be relevant: Simply return true from ReactElement.isValidElement() if there is no global Symbol and the input is an object with a The magicnum can be trivially bypassed via useragent detection from a determined adversary targeting XSS into React. It causes more problems than it solves. |
That would technically not be right, would it? We use other |
Perhaps another way we can duck-type it? The point is, XSS detection without |
My point is that it's wrong to include |
Only because of ES5 browsers, and only because of the |
We use
If you mean the Babel plugin in particular, IMO it is expected that people read the README of the plugin they’re adding. If they’re not trusted to do that then how can they be trusted to only enable it in production. (Or similarly, enable |
See this babel/babel#2517 and the associated discussion around the PR babel/babel#2518, which I don't expect to be merged due to loader issues.
To recap:
The
inlineElements
optimization requires brittle knowledge of internal React values, namely,$$typeof
. This breaks on older browsers unless the developer globally polyfillsSymbol
, becauseSymbol
will be polyfilled automatically by Babel in the user's code, but will not be polyfilled in the React library. This causesReactElement.isValidElement
to fail asSymbol.for('react.element') !== 0xeac7
.Worse, this bug only occurs in older browsers that don't implement Symbol, meaning that many devs won't catch it right away as it will work fine in FF, Chrome, and (latest) Safari.
This is a hard issue to fix without globally polyfilling Symbol or giving up on the use of
Symbol
for$$typeof
. Babel could automatically this as part of enabling the optimisation, but @loganfsmyth had a better idea - how about aReact.elementFromObject()
API?This function would be nothing more than:
This ensures that the
REACT_ELEMENT_TYPE
we are using is equal to the one used inReactElement.isValidElement
. It shouldn't be necessary to do any validation inelementFromObject
because it will be caught byisValidElement
later on.Thoughts?
The text was updated successfully, but these errors were encountered: