From 05f6ab2cc80dc5343653d150b34ff8efdd2504fa Mon Sep 17 00:00:00 2001 From: Daniel Friesen Date: Mon, 22 Jun 2015 00:35:23 -0700 Subject: [PATCH] Remove React's dependency on es5-sham.js. Shams are potentially dangerous and add 5kb of code, of which React requires almost nothing from (see #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. --- docs/docs/08-working-with-the-browser.md | 5 -- .../classic/element/ReactElement.js | 5 +- src/renderers/dom/ReactDOMClient.js | 6 +- .../client/syntheticEvents/SyntheticEvent.js | 3 +- src/shared/stubs/Object.create.js | 45 +++++++++++ src/shared/stubs/Object.freeze.js | 37 +++++++++ .../stubs/__tests__/Object.create-test.js | 57 ++++++++++++++ .../stubs/__tests__/Object.freeze-test.js | 76 +++++++++++++++++++ src/shared/vendor/core/emptyObject.js | 4 +- 9 files changed, 224 insertions(+), 14 deletions(-) create mode 100644 src/shared/stubs/Object.create.js create mode 100644 src/shared/stubs/Object.freeze.js create mode 100644 src/shared/stubs/__tests__/Object.create-test.js create mode 100644 src/shared/stubs/__tests__/Object.freeze-test.js diff --git a/docs/docs/08-working-with-the-browser.md b/docs/docs/08-working-with-the-browser.md index be4500b3d94..aebc8714bef 100644 --- a/docs/docs/08-working-with-the-browser.md +++ b/docs/docs/08-working-with-the-browser.md @@ -123,11 +123,6 @@ In addition to that philosophy, we've also taken the stance that we, as authors * `String.prototype.split` * `String.prototype.trim` -`es5-sham.js`, also from [kriskowal's es5-shim](https://github.com/es-shims/es5-shim), provides the following that React needs: - -* `Object.create` -* `Object.freeze` - The unminified build of React needs the following from [paulmillr's console-polyfill](https://github.com/paulmillr/console-polyfill). * `console.*` diff --git a/src/isomorphic/classic/element/ReactElement.js b/src/isomorphic/classic/element/ReactElement.js index 9e333270e2f..ab0269673ca 100644 --- a/src/isomorphic/classic/element/ReactElement.js +++ b/src/isomorphic/classic/element/ReactElement.js @@ -14,6 +14,7 @@ var ReactCurrentOwner = require('ReactCurrentOwner'); var assign = require('Object.assign'); +var freeze = require('Object.freeze'); var RESERVED_PROPS = { key: true, @@ -62,8 +63,8 @@ var ReactElement = function(type, key, ref, owner, props) { this._store.validated = false; } this.props = props; - Object.freeze(this.props); - Object.freeze(this); + freeze(this.props); + freeze(this); } }; diff --git a/src/renderers/dom/ReactDOMClient.js b/src/renderers/dom/ReactDOMClient.js index 03609f1e5e6..2d4796a1476 100644 --- a/src/renderers/dom/ReactDOMClient.js +++ b/src/renderers/dom/ReactDOMClient.js @@ -89,16 +89,12 @@ if (__DEV__) { Object.keys, String.prototype.split, String.prototype.trim, - - // shams - Object.create, - Object.freeze, ]; for (var i = 0; i < expectedFeatures.length; i++) { if (!expectedFeatures[i]) { console.error( - 'One or more ES5 shim/shams expected by React are not available: ' + + 'One or more ES5 shims expected by React are not available: ' + 'https://fb.me/react-warning-polyfills' ); break; diff --git a/src/renderers/dom/client/syntheticEvents/SyntheticEvent.js b/src/renderers/dom/client/syntheticEvents/SyntheticEvent.js index 088326ccf8e..c3132764e7e 100644 --- a/src/renderers/dom/client/syntheticEvents/SyntheticEvent.js +++ b/src/renderers/dom/client/syntheticEvents/SyntheticEvent.js @@ -15,6 +15,7 @@ var PooledClass = require('PooledClass'); var assign = require('Object.assign'); +var create = require('Object.create'); var emptyFunction = require('emptyFunction'); var getEventTarget = require('getEventTarget'); @@ -148,7 +149,7 @@ SyntheticEvent.Interface = EventInterface; SyntheticEvent.augmentClass = function(Class, Interface) { var Super = this; - var prototype = Object.create(Super.prototype); + var prototype = create(Super.prototype); assign(prototype, Class.prototype); Class.prototype = prototype; Class.prototype.constructor = Class; diff --git a/src/shared/stubs/Object.create.js b/src/shared/stubs/Object.create.js new file mode 100644 index 00000000000..0c070ab142a --- /dev/null +++ b/src/shared/stubs/Object.create.js @@ -0,0 +1,45 @@ +/** + * Copyright 2014-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Object.create + */ + +// https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.create + +'use strict'; + +var nativeCreate = typeof Object.create === 'function' && Object.create; + +var Type = function() {}; + +function create(prototype, properties) { + if (prototype == null) { + throw new TypeError('This create() implementation cannot create empty objects with create(null)'); + } + + if (typeof prototype !== 'object' && typeof prototype !== 'function') { + throw new TypeError('Object prototype may only be an Object'); + } + + if (properties) { + throw new TypeError('This create() implementation does not support assigning properties'); + } + + var object; + if ( nativeCreate ) { + object = nativeCreate(prototype); + } else { + Type.prototype = prototype; + object = new Type(); + Type.prototype = null; + } + + return object; +} + +module.exports = create; diff --git a/src/shared/stubs/Object.freeze.js b/src/shared/stubs/Object.freeze.js new file mode 100644 index 00000000000..56ac3b3bb84 --- /dev/null +++ b/src/shared/stubs/Object.freeze.js @@ -0,0 +1,37 @@ +/** + * Copyright 2014-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Object.freeze + */ + +// http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.3.9 +// https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.freeze + +'use strict'; + +var nativeFreeze = typeof Object.freeze === 'function' && Object.freeze; + +function freeze(object) { + // ES5 and ES6 have different behaviors when object is not an Object. + // - ES5 Throw a TypeError + // - ES6 Return object + // Within React, using freeze() on a non-object is most likely to be an error + // so this method throws. + if (Object(object) !== object) { + throw new TypeError('freeze can only be called an Object'); + } + + // Freeze if possible, but don't error if it is not implemented. + if (nativeFreeze) { + nativeFreeze(object); + } + + return object; +} + +module.exports = freeze; diff --git a/src/shared/stubs/__tests__/Object.create-test.js b/src/shared/stubs/__tests__/Object.create-test.js new file mode 100644 index 00000000000..b8caf46681b --- /dev/null +++ b/src/shared/stubs/__tests__/Object.create-test.js @@ -0,0 +1,57 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +require('mock-modules') + .dontMock('Object.create'); + +var create; + +describe('Object.create', function() { + + beforeEach(function() { + create = require('Object.create'); + }); + + it('should throw when the prototype is null', function() { + expect(function() { + create(null); + }).toThrow( + 'This create() implementation cannot create empty objects with create(null)' + ); + }); + + it('should throw when the prototype is not an object', function() { + expect(function() { + create(1); + }).toThrow( + 'Object prototype may only be an Object' + ); + }); + + it('should throw when properties are given', function() { + expect(function() { + create({}, {}); + }).toThrow( + 'This create() implementation does not support assigning properties' + ); + }); + + it('should create an object that inherits from prototype', function() { + var proto = {}; + var object = create(proto); + + proto.foo = 'bar'; + expect(object.foo).toBe('bar'); + }); + +}); diff --git a/src/shared/stubs/__tests__/Object.freeze-test.js b/src/shared/stubs/__tests__/Object.freeze-test.js new file mode 100644 index 00000000000..4ea8e7a003b --- /dev/null +++ b/src/shared/stubs/__tests__/Object.freeze-test.js @@ -0,0 +1,76 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +require('mock-modules') + .dontMock('Object.freeze'); + +var freeze; + +var runnerCanFreeze = false; +try { + var obj = {foo: 'bar'}; + Object.freeze(obj); + try { + obj.foo = 'baz'; + runnerCanFreeze = obj.foo === 'bar'; + } catch (e) { + if (e instanceof TypeError) { + runnerCanFreeze = true; + } + } +} catch (x) {} + +describe('Object.freeze', function() { + + beforeEach(function() { + freeze = require('Object.freeze'); + }); + + it('should not throw when the argument is an object', function() { + expect(function() { + freeze({}); + }).not.toThrow(); + }); + + it('throws if the argument is not an object', function() { + expect(function() { + freeze(1); + }).toThrow( + 'freeze can only be called an Object' + ); + }); + + it('should return the same object it is given', function() { + var obj = {}; + + var returnValue = freeze(obj); + + expect(returnValue).toBe(obj); + }); + + it('should freeze the object if the native Object.freeze can', function() { + if ( !runnerCanFreeze ) { + pending(); + return; + } + + var obj = {foo: 'bar'}; + freeze(obj); + try { + obj.foo = 'baz'; + } catch (x) {} + + expect(obj.foo).toBe('bar'); + }) + +}); diff --git a/src/shared/vendor/core/emptyObject.js b/src/shared/vendor/core/emptyObject.js index c33476b05d4..a52b3124e3f 100644 --- a/src/shared/vendor/core/emptyObject.js +++ b/src/shared/vendor/core/emptyObject.js @@ -11,10 +11,12 @@ "use strict"; +var freeze = require('Object.freeze'); + var emptyObject = {}; if (__DEV__) { - Object.freeze(emptyObject); + freeze(emptyObject); } module.exports = emptyObject;