-
Notifications
You must be signed in to change notification settings - Fork 47.3k
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
Remove React's dependency on es5-sham.js. #4192
Conversation
Shams are potentially dangerous and add 5kb of code, of which React requires almost nothing from (see facebook#4189). This commit. - Implements an internal Object.freeze stub that throws like ES5's object freeze and uses Object.freeze if it is implemented. - Implements an internal Object.create stub that only supports `create(prototype)`, the only Object.create behavior React requires. - Implements tests for both of these stubs. - Fixes React to use these stubs internally. - Removes the early Error thrown when either of these native or shamed methods are not available. - Removes reference of es5-sham.js from the docs. These stubs are implemented with consistency in mind so they will throw in the same conditions during development as they will when run in IE8. Tests which use Object.create or Object.freeze as part of the test have been left alone.
|
||
'use strict'; | ||
|
||
var nativeCreate = typeof Object.create === 'function' && Object.create; |
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.
This seems superfluous to me at least. Object.create
should be enough, a broken environment is not our problem I would think...
Just throwing this out there, since I'm not sure where everyone stands on this issue: Do we want to be maintaining Object.create and Object.freeze ourselves (increasing codebase size, introducing the possibility of errors); I assume that was the reason we didn't implement it ourselves initially (as we did for Object.assign). Also, I think we're probably getting close to the point of sunsetting support for any browser that doesn't support this functionality natively, right? So why add it this late in the lifecycle? Having said that, if the sham is causing problems for people and the fix is easy, might as well include it, right? I don't have a strong preference either way, just curious what @spicyj @zpao think. |
To confirm, |
In total reducing the possibility of errors IMHO. And they allow you to remove a 5kb (minified) es5-sham.js from your codebase. So personally, I think the net of this change actually makes errors less likely and reduces the total size of what is required to use React. EDIT: Here's an example. Currently a developer can carelessly write |
Freeze here seems reasonable-ish but if we want to avoid the create sham I'd just inline the minimal logic like in #139. Not my fight though. :) @sebmarkbage or @zpao can care. |
+1 for @spicyj's solution. Could lnline both Also, as mentioned before:
|
Hey I wanted to make Object.assign a required sham. :) If every library does this then we end up shipping an accumulation of unnecessary polyfills. Very few users of React even need these polyfills because they're not supporting old environments. Even if they do, they use polyfills anyway for other purposes. Sure, some libraries respond poorly to polyfills but that doesn't make polyfills inherently bad. If we go down this route we should use the transpiler model. That way it becomes an optional build process problem. Out of sight - out of mind. That can also be applied to the code base as a whole and integrated into build systems that target different browsers with different packages. Btw, it is likely that within a year or so, React might stop actively supporting some of these environments like IE8. Not sure but if we have significant need for newer features it might be necessary. That's not on the plans yet but it makes these changes less valuable. The question around ES6 features like Object.assign still stands though. |
There is a difference between shims and shams as pollyfills, you're mixing two things together. A shim implemented in JS like A sham will sort of behave like the native implementation in the simplest of use cases, and then either silently fail causing potential unexpected bugs or throwing errors when you do something beyond what you can implement without native code. Because a sham introduces an unexpected half-state where an ES5 method is present but isn't actually implemented the way it's supposed to be. Libraries which try and write good code, testing for the presence of ES5 methods before trying to use them, then have a possibility of being broken by the sham. es5-shim even explicitly says "In many cases, this means that these shams cause many ES5 methods to silently fail. Decide carefully whether this is what you want.". And the problem here is that React is basically taking away the option of deciding whether you want it or not.
Then would you prefer that these shams were individual npm modules other libraries could share, without modifying global scope in a way that can break other libraries? Alternatively, in place of lodash has per-method modules. So we could depend on the ((I do see the potential problem with browserify's static detection of require() and have a feeling the sollution would probably be a browserify transform that special cases loading of lodash per-method modules))
I do like the idea of detecting use of things like |
If you're supporting IE8, you can use a sham that overrides the native
This will run fine on their machine even with your proposed solution, since they will have access to the native
This is exactly the problem. If we never adopt better polyfilling strategies, we'll end up having to fallback to community library code like lodash. If we can't use Object.create today, six years after standardization, then we will never be able to have nice things. I explained why that's a problem in my JSConf.eu talk about this subject: The transpiler strategy is great because it can be used by you as a consumer instead of as part of React's build-process. It maximizes the configurability of optional localized polyfills. |
So, browserify transform that modifies Object.create and Object.freeze invocations? Or is babel the proper place... tricky to say for this instance. In react's repo or as a new npm module? What do we do when |
Wow, I never thought this would spark such a debate :/ I think everyone has made some great points.
#GreatestQuotes. I really need to start a collection of @sebmarkbage's best quotes :). I agree, btw, fwiw.
Probably Babel. It is my understanding that Browserify is more about interaction with NPM whereas Babel is more about language compatibility. This seems like a language compatibility feature.
New npm module.
Probably warn, but ultimately up to the implementor of the transform. But we digress. I think the question at hand is: What, if anything, do we want to do about removing the dependency on es5-sham? It sounds like @sebmarkbage is in favor of not re-implementing the polyfill, I find myself leaning in the same direction, so I'm going to mark this PR as needing revision. Implementing this as a transform seems ideal to me, but I'd be perfectly happy if we just manually inlined all the calls to |
I thought @sebmarkbage says you should use a transform in your app code, that it shouldn't be in the React repo at all. |
Ok so I'm mostly fighting a battle of principals here. However, I have a pragmatic side as well. If we can get a way from using the shams without compromising these principals too much that's a win-win. If we apply the same logic to ES6 classes, we should be writing using them and then have the transpiler runtime deal with it for us. So, we should just use ES6 classes instead of Object.create, no? As for Object.freeze, that's a DEV only feature that we use to enforce a set of warnings in one place, and that we're expecting to move/copy into the Babel runtime anyway. I wouldn't be opposed to just conditionally check it in that one callsite. |
Haha, I think we've reached a consensus here. :) |
We've been talking about best practices for transpiled code and npm. I think we're starting to land at that npm packages should include both the lowest common denominator and the original source. That way it can "just work" for all tools, and then you can get an enhanced experience when tools are aware of additional information. For example, we could leave Object.assign and ES6 classes as original source for build-systems that can handle them, and transpile them in the lowest common denominator bundle. I guess that means that our current bundle is considered lowest common denominator, and Object.assign should be transpiled/auto-polyfilled. |
Thinking about this there are a few messy details about transpiling:
Babel does have optional transforms and a loose mode. But in general I see it as still trying to avoid hacks and edge case bugs. And it also seems to focus on leaving non-syntax transforms to polyfills. So I think this kind of transform falls within browserify scope. Also, while unfortunate. Because of the quirks of sham dependencies I don't think this will work well as a grand "Use this transform and have all the shims/shams you need automatically transpiled/imported". But instead will be a narrow transform you can include for a simple common use case. ie:
And since transforming @sebmarkbage Object.assign shims? Though I'm not entirely happy about the bloat using either would imply. |
@dantman For Object.freeze I'm fine with just having it be conditional Likewise, we're already using Babel to transpile our source so we should just use the For example, Babel can track rudimentary type information so if it can easily prove that a value is non-null, it can avoid the check in the transpiled code. @sebmck is very responsive to that kind of challenges. |
Not really. It's a stretch to call any of the semantics you opt out of with loose mode as "edge cases" as sometimes they're cruicail parts of the specific feature.
You would be extremely strict and err on the side of caution with your transformations and then optimise for the common case where you can be 100% confident. At least this is the approach I take when optimising transforms and what I believe @sebmarkbage was alluding to.
Sort of. There currently aren't APIs for performing type checks like that. Right now there are only binary "is this a specific base type" methods. They'll definently need to be introduced in the future though, either when Babel gets better type inference (unlikely) or when there's tighter integration with Flow (hopefully!). |
I know the discussion is already past this, but isn't that exactly the problem? To illustrate, by that argument it would kind of make sense to implement So it's more that shams is a flawed concept, you deviate from the standard and polyfill the environment with non-standard/partial implementations. For you as a developer it works out really rather well, but as a consumer of various libraries that rely on standard behavior it's a nightmare. |
@syranide If you're calling into a sham that can't be polyfilled, you can wrap the invocation in a try-catch, so the exception will be non-fatal. You are effectively handling the case where the operation is not supported by that browser. It's a pretty reasonable case for developers to handle, since they know they are invoking features that are not well supported by their target browsers. |
@jimfb Not arguing that it can't be tested for by try/catch, the reality is that it often isn't (for whatever reason). |
The only way that will change is if people use shims more often :). But yeah, I think we're all in agreement that shims/shams suck and the best solution is to use a compiler/transpiler that generates code for your target browsers. |
@jimfb I don't mind shims. As far as I've been able to understand it "transpiling shims" doesn't really work super well. It works great for your own code, but you end up polyfilling the environment too because there are other libraries out there aren't transpiled along with the rest of your code. So to me shims seem just fine, but not all shams, people have different expectations when it comes to shams and that's where it breaks down. |
If your library relies on defineProperty behavior then it won't work in IE8 anyway. The issue is that libraries might have the ability to work around the sham if it is not available and therefore they want to feature detect if it does work, then conditionally fallback. That notion is flawed because now your code will still behave differently, just using one indirection. So effectively to get a safe environment you need to ensure that all your libraries conform to a subset of the language. In which case the sham model works fine. |
No, it will explicitly do what's written in the code, it will not perform some arbitrary fallback behavior that I may or may find acceptable and have to guard against too. To take a silly example, why is it that
"ensure that all your libraries conform", why it's so obviously fine for React to make non-standard demands on my environment? Why can't React conform to the standard instead? To me it's really simple; follow the standard then everyone gets the same experience and everyone gets to choose their preferred fallback, it's simple and it works. Don't follow the standard and you're making vague demands on everyone else to elaborately feature test for what is a minor benefit. It would have been fantastic if ECMA provided specs for acceptable partial implementations, but they didn't. |
I guess that we actually agree. Shams should throw if they can't follow the implemented behavior rather than silently failing. E.g. if the sham can't implement the second argument of Object.create, it should throw. Libraries that doesn't depend on that behavior should just avoid passing the second argument. I agree that silently failing is generally a bad strategy for a sham, however, for perf reasons it might be prohibitively expensive to throw. In those cases, you have a tough choice. You could take the extreme route and say that we should never use those APIs at all and implement it in user space, but you could be talking about an edge case that almost never occurs. We hold up progress in adoption because of it. My preference is to use a DEV time warning to ensure that this behavior isn't being relied on, but that does not allow the try/catch pattern to be used in production code. One example is ES6 classes. They require methods to be non-enumerable. Making them non-enumerable is prohibitively slow. Should we not use ES6 classes because of it? No, we definitely should be using them anyway with the loose mode because iterating over them is an edge case. It is up to the polyfilling environment to ensure that you have a good DEV mode experience that warns you if you try to iterate over the properties of a class prototype. It is not as simple as just follow the standard or don't use it at all. |
As for You shouldn't be relying on try/catch for code flow. In fact, the spec relies on throwing in places that are expected to NOT throw in the future. So you will break future specs by doing that. You also shouldn't be using non-strict mode. That's just breaks everything. So given those assumptions, your Besides, try/catch as a feature detection is a terrible user experience. Map polyfills rely on it to test the constructor, but we've had to disable those polyfills since they cause the "Break on caught exceptions" mode to trigger in Chrome. A few polyfills and that important debug tool is busted. |
@dantman @sebmarkbage To summarize: it sounds like the ideal solution is a transform but we would happily accept inlining the ifexists check for |
+1 for not requiring an environment where |
Closing out in favor of #4189 |
Shams are potentially dangerous and add 5kb of code, of which React requires almost nothing from (see #4189).
This commit.
create(prototype)
, the only Object.create behavior React requires.These stubs are implemented with consistency in mind so they will throw in the same conditions during development as they will when run in IE8.
Tests which use Object.create or Object.freeze as part of the test have been left alone.
I've dropped the reference of es5-sham.js from the docs in this commit. Would it instead be better for the docs to mention a specific range of React versions that require es5-sham.js and what version this commit will be a part of?