Skip to content

Commit

Permalink
Remove React's dependency on es5-sham.js.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dantman committed Jun 22, 2015
1 parent df05c6e commit 05f6ab2
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 14 deletions.
5 changes: 0 additions & 5 deletions docs/docs/08-working-with-the-browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.*`
Expand Down
5 changes: 3 additions & 2 deletions src/isomorphic/classic/element/ReactElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
var ReactCurrentOwner = require('ReactCurrentOwner');

var assign = require('Object.assign');
var freeze = require('Object.freeze');

var RESERVED_PROPS = {
key: true,
Expand Down Expand Up @@ -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);
}
};

Expand Down
6 changes: 1 addition & 5 deletions src/renderers/dom/ReactDOMClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion src/renderers/dom/client/syntheticEvents/SyntheticEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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;
Expand Down
45 changes: 45 additions & 0 deletions src/shared/stubs/Object.create.js
Original file line number Diff line number Diff line change
@@ -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;
37 changes: 37 additions & 0 deletions src/shared/stubs/Object.freeze.js
Original file line number Diff line number Diff line change
@@ -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;
57 changes: 57 additions & 0 deletions src/shared/stubs/__tests__/Object.create-test.js
Original file line number Diff line number Diff line change
@@ -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');
});

});
76 changes: 76 additions & 0 deletions src/shared/stubs/__tests__/Object.freeze-test.js
Original file line number Diff line number Diff line change
@@ -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');
})

});
4 changes: 3 additions & 1 deletion src/shared/vendor/core/emptyObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@

"use strict";

var freeze = require('Object.freeze');

var emptyObject = {};

if (__DEV__) {
Object.freeze(emptyObject);
freeze(emptyObject);
}

module.exports = emptyObject;

0 comments on commit 05f6ab2

Please sign in to comment.