Skip to content

Commit

Permalink
BREAKING CHANGE: UPGRADE REACT NATIVE TO BABEL 7!
Browse files Browse the repository at this point in the history
Summary:
BREAKING CHANGE
This change upgrades the React Native build pipeline from Babel 6 to Babel 7

If you use a `.babelrc` then you'll need to update it to Babel 7 (note that some plugins are no longer relevant, some plugins are automatically upgraded, and some will need some manual love).

Note that you may also need to upgrade your dev env, tests etc, to make sure they work with Babel 7.

Reviewed By: mjesun

Differential Revision: D7591303

fbshipit-source-id: 29cef21f6466633a9c366d1f3c0d3cf874c714db
  • Loading branch information
Peter van der Zee authored and facebook-github-bot committed Apr 11, 2018
1 parent 3277bed commit f8d6b97
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 112 deletions.
220 changes: 145 additions & 75 deletions Libraries/polyfills/babelHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,15 @@
/* eslint-disable quotes, curly, no-proto, no-undef-init, dot-notation */

// Created by running:
// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray taggedTemplateLiteral toArray toConsumableArray '.split(' '))
// then replacing the `global` reference in the last line to also use `this`.
// require('fs').writeFileSync('babelExternalHelpers.js', require('@babel/core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray taggedTemplateLiteral toArray toConsumableArray wrapNativeSuper assertThisInitialized taggedTemplateLiteralLoose'.split(' ')))// then replacing the `global` reference in the last line to also use `this`.
//
// actually, that's a lie, because babel6 omits _extends and createRawReactElement

This comment has been minimized.

Copy link
@loganfsmyth

loganfsmyth Apr 13, 2018

This comment seems misleading. These helpers are called extends and jsx. The names you've listed here are just internal implementation details.

This comment has been minimized.

Copy link
@gengjiawen

gengjiawen Apr 19, 2018

Contributor

@loganfsmyth I feel this way is very cumbersome. Is it possible @babel/helpers export helpers, so we can use it like

babelHelpers.objectSpread = require("@babel/helpers").helpers.objectSpread

This comment has been minimized.

Copy link
@loganfsmyth

loganfsmyth Apr 19, 2018

babel-runtime and transform-runtime is the standard approach there. In Babel 6 the helpers in there were hardcoded to also use core-js which was probably why it wasn't used. In Babel 7, it has a dependency on core-js, but whether or not you actually use it is up to you. We may expand that further to avoid the dependency entirely, but that's not currently implemented.

Alternatively, there's nothing wrong with require('@babel/core').buildExternalHelpers, but I don't understand why this is using the whitelist option in the first place. It'd be a lot easier to use require('@babel/core').buildExternalHelpers() directly to avoid missing any new helpers we add.

This comment has been minimized.

Copy link
@pvdz

pvdz Apr 19, 2018

I realize they were internal. The comment is mostly for the next person to upgrade this file. This file is just an artifact. We may be able to get rid of it in the future. I didn't really change how we use this file, just updated it.

// Actually, that's a lie, because babel omits _extends and
// createRawReactElement. the file is also cleaned up a bit.
// You may need to clear wrapNativeSuper while the bug hasn't been fixed yet.
// Do try to keep diffs to a minimum.

var babelHelpers = global.babelHelpers = {};

babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};

babelHelpers.createRawReactElement = (function () {
var REACT_ELEMENT_TYPE = typeof Symbol === "function" && Symbol.for && Symbol.for("react.element") || 0xeac7;
return function createRawReactElement(type, key, props) {
Expand All @@ -45,32 +41,20 @@ babelHelpers.classCallCheck = function (instance, Constructor) {
}
};

babelHelpers.createClass = (function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}

return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
})();

babelHelpers.defineEnumerableProperties = function(obj, descs) {
for (var key in descs) {
var desc = descs[key];
desc.configurable = (desc.enumerable = true);
if ('value' in desc) desc.writable = true;
Object.defineProperty(obj, key, desc);
}
return obj;
babelHelpers.createClass = function(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
};

babelHelpers.defineProperty = function (obj, key, value) {
Expand Down Expand Up @@ -129,9 +113,8 @@ babelHelpers.get = function get(object, property, receiver) {

babelHelpers.inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
throw new TypeError("Super expression must either be null or a function");
}

subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
Expand All @@ -143,6 +126,54 @@ babelHelpers.inherits = function (subClass, superClass) {
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};

var _gPO = Object.getPrototypeOf || function _gPO(o) { return o.__proto__ };
var _sPO = Object.setPrototypeOf || function _sPO(o, p) { o.__proto__ = p; return o };
var _construct =
// TODO: prepack does not like this line (and we can use the fallback just fine)
// (typeof Reflect === "object" && Reflect.construct) ||
function _construct(Parent, args, Class) {
var Constructor, a = [null];
a.push.apply(a, args);
Constructor = Parent.bind.apply(Parent, a);
return _sPO(new Constructor, Class.prototype);
};
var _cache = typeof Map === "function" && new Map();
babelHelpers.wrapNativeSuper = function(Class) {
// FB:
// Note: while extending native classes is pretty meh we do have cases, for
// example; Error. There is also a false positive, for example; Blob.

if (typeof Class !== "function") {
throw new TypeError("Super expression must either be null or a function");
}
if (typeof _cache !== "undefined") {
if (_cache.has(Class)) return _cache.get(Class);
_cache.set(Class, Wrapper);
}
function Wrapper() {
// this is a temporary fix for a babel bug (it's invoking the wrong func
// when you do `super()`)
return _construct(Class, arguments, _gPO(this).constructor);
}
Wrapper.prototype = Object.create(Class.prototype, {
constructor: {
value: Wrapper,
enumerable: false,
writeable: true,
configurable: true,
}
});
return _sPO(
Wrapper,
_sPO(
function Super() {
return _construct(Class, arguments, _gPO(this).constructor);
},
Class
)
);
};

babelHelpers.interopRequireDefault = function (obj) {
return obj && obj.__esModule ? obj : {
default: obj
Expand All @@ -157,7 +188,15 @@ babelHelpers.interopRequireWildcard = function (obj) {

if (obj != null) {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {};

if (desc.get || desc.set) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
}

Expand All @@ -166,63 +205,79 @@ babelHelpers.interopRequireWildcard = function (obj) {
}
};

babelHelpers.objectWithoutProperties = function (obj, keys) {
babelHelpers.objectWithoutProperties = function(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;

for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}

if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);

for (var i in obj) {
if (keys.indexOf(i) >= 0) continue;
if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
target[i] = obj[i];
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}

return target;
};

babelHelpers.possibleConstructorReturn = function (self, call) {
if (!self) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
}

if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}

return call && (typeof call === "object" || typeof call === "function") ? call : self;
return self;
};

babelHelpers.slicedToArray = (function () {
function sliceIterator(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
function _sliceIterator(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;

try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);

if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
try {
if (!_n && _i["return"]) _i["return"]();
} finally {
if (_d) throw _e;
}
if (_d) throw _e;
}

return _arr;
}

return function (arr, i) {
if (Array.isArray(arr)) {
return arr;
} else if (Symbol.iterator in Object(arr)) {
return sliceIterator(arr, i);
} else {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
};
})();
return _arr;
}

babelHelpers.slicedToArray = function(arr, i) {
if (Array.isArray(arr)) {
return arr;
} else if (Symbol.iterator in Object(arr)) {
return _sliceIterator(arr, i);
} else {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
};

babelHelpers.taggedTemplateLiteral = function (strings, raw) {
return Object.freeze(Object.defineProperties(strings, {
Expand All @@ -238,10 +293,25 @@ babelHelpers.toArray = function (arr) {

babelHelpers.toConsumableArray = function (arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];

return arr2;
} else {
return Array.from(arr);
}
};

babelHelpers.assertThisInitialized = function(self) {
if (self === void 0) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called",
);
}

return self;
};

babelHelpers.taggedTemplateLiteralLoose = function(strings, raw) {
strings.raw = raw;
return strings;
};
78 changes: 45 additions & 33 deletions babel-preset/configs/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,55 @@
'use strict';

const defaultPlugins = [
[require('babel-plugin-syntax-class-properties')],
[require('babel-plugin-syntax-trailing-function-commas')],
[require('babel-plugin-transform-class-properties')],
[require('babel-plugin-transform-es2015-block-scoping')],
[require('babel-plugin-transform-es2015-computed-properties')],
[require('babel-plugin-transform-es2015-destructuring')],
[require('babel-plugin-transform-es2015-function-name')],
[require('babel-plugin-transform-es2015-literals')],
[require('babel-plugin-transform-es2015-parameters')],
[require('babel-plugin-transform-es2015-shorthand-properties')],
[require('babel-plugin-transform-flow-strip-types')],
[require('babel-plugin-transform-react-jsx')],
[require('babel-plugin-transform-regenerator')],
[require('@babel/plugin-transform-block-scoping')],
// the flow strip types plugin must go BEFORE class properties!
// there'll be a test case that fails if you don't.
[require('@babel/plugin-transform-flow-strip-types')],
[
require('babel-plugin-transform-es2015-modules-commonjs'),
{strict: false, allowTopLevelThis: true},
require('@babel/plugin-proposal-class-properties'),
// use `this.foo = bar` instead of `this.defineProperty('foo', ...)`
// (Makes the properties enumerable)

This comment has been minimized.

Copy link
@loganfsmyth

loganfsmyth Apr 13, 2018

The defineProperty call sets enumerable: true so this should not affect that.

This comment has been minimized.

Copy link
@pvdz

pvdz Apr 19, 2018

Aren't class properties supposed to be non-enumerable? The comment was more to clarify the loose option.
Either way, I've removed the option. It's not that important.

{loose: true},
],
[require('@babel/plugin-transform-computed-properties')],
[require('@babel/plugin-transform-destructuring')],
[require('@babel/plugin-transform-function-name')],
[require('@babel/plugin-transform-literals')],
[require('@babel/plugin-transform-parameters')],
[require('@babel/plugin-transform-shorthand-properties')],
[require('@babel/plugin-transform-react-jsx')],
[require('@babel/plugin-transform-regenerator')],
[require('@babel/plugin-transform-sticky-regex')],
[require('@babel/plugin-transform-unicode-regex')],
[
require('@babel/plugin-transform-modules-commonjs'),
{
strict: false,
strictMode : false, // prevent "use strict" injections
allowTopLevelThis: true, // dont rewrite global `this` -> `undefined`

This comment has been minimized.

Copy link
@loganfsmyth

loganfsmyth Apr 13, 2018

Out of curiosity, anyone know why this is done? Depending on the reason, there might be a better way in Babel 7.

This comment has been minimized.

Copy link
@janicduplessis

janicduplessis Apr 13, 2018

Contributor

RN packager runs transforms on node_modules so it is possible that some third party dependency is not strict mode compatible or uses top level this.

This comment has been minimized.

Copy link
@loganfsmyth

loganfsmyth Apr 13, 2018

Alright, that is why I was curious.

With Babel 7, I'd say your better option would be to remove these options and set sourceType: "unambiguous". Babel already skips use strict and this -> undefined if the file isn't considered a module, and unambiguous would only consider it a module if the file explicitly uses import or export.

strict: false also seems like a mistake to me. It was probably set with the intention of being strictMode, but it isn't really something I'd expect people to want to use now.

This comment has been minimized.

Copy link
@janicduplessis

janicduplessis Apr 13, 2018

Contributor

Makes sense.

I remember at some point the option to disable "use strict" insertion was called just strict, maybe it was renamed at some point.

This comment has been minimized.

Copy link
@loganfsmyth

loganfsmyth Apr 13, 2018

Yeah it was kind of a mess. It was called strict, but in Babel 6 both the module transform itself, and the use strict-injection did too, and they did two separate things. To disambiguate we renamed one to strictMode. I can't imagine why you'd want to set strict but if you do that's fine.

This comment has been minimized.

Copy link
@gengjiawen

gengjiawen Apr 19, 2018

Contributor

@loganfsmyth @janicduplessis Looks like current config is not correct from the test result.

 deepFreezeAndThrowOnMutationInDev › should not throw on insertion in dev without strict

    expect(function).not.toThrow()
    
    Expected the function not to throw an error.
    Instead, it threw:
      TypeError: Cannot add property newKey, object is not extensible
          88 |     var o = {oldKey: 'value'};
          89 |     deepFreezeAndThrowOnMutationInDev(o);
        > 90 |     expect(() => { o.newKey = 'value'; }).not.toThrow();
          91 |     expect(o.newKey).toBe(undefined);
          92 |   });
          93 | 
          
          at Libraries/Utilities/__tests__/deepFreezeAndThrowOnMutationInDev-test.js:90:34
          at Object.<anonymous> (node_modules/expect/build/to_throw_matchers.js:51:9)
          at Object.throwingMatcher [as toThrow] (node_modules/expect/build/index.js:239:24)
          at Object.<anonymous> (Libraries/Utilities/__tests__/deepFreezeAndThrowOnMutationInDev-test.js:90:51)

      88 |     var o = {oldKey: 'value'};
      89 |     deepFreezeAndThrowOnMutationInDev(o);
    > 90 |     expect(() => { o.newKey = 'value'; }).not.toThrow();
      91 |     expect(o.newKey).toBe(undefined);
      92 |   });
      93 | 
      
      at Object.<anonymous> (Libraries/Utilities/__tests__/deepFreezeAndThrowOnMutationInDev-test.js:90:51)

This comment has been minimized.

Copy link
@gengjiawen

gengjiawen Apr 19, 2018

Contributor

I add the option sourceType: "unambiguous", everything works.

Test Suites: 2 skipped, 100 passed, 100 of 102 total
Tests:       4 skipped, 553 passed, 557 total
Snapshots:   37 passed, 37 total
Time:        78.455s
Ran all test suites.
Done in 79.46s.

This comment has been minimized.

Copy link
@gengjiawen

gengjiawen Apr 19, 2018

Contributor

@janicduplessis I have created a pr for this #18941.

This comment has been minimized.

Copy link
@pvdz

pvdz Apr 19, 2018

Out of curiosity, anyone know why this is done? Depending on the reason, there might be a better way in Babel 7.

I tried to check it out but could not find the reason. However, it's sensitive enough that I didn't want to change the status quo here. Same for strict.

We need strictMode because "use strict"s were injected leading to function statements which are outlawed in es5 (they are okay again in es6+ but we compile to es5 or worse).

As for the ast and sourceType options; These were new and I didn't lock down the beta version which led to this problem. Apologies for that. I've fixed that today and things should stabilize in the next few days.

This comment has been minimized.

Copy link
@gengjiawen

gengjiawen Apr 19, 2018

Contributor

@qfox Add ast to preprocessor is not working. You need fix that in metro. I have created an pr facebook/metro#162.

This comment has been minimized.

Copy link
@pvdz

pvdz Apr 20, 2018

As commented there; the workaround is to lockdown babel to beta.40. This "fix" (for whatever meaning of that word) will also land here today. I hope.

},
],
];

const checkES2015Constants = [require('babel-plugin-check-es2015-constants')];
const es2015ArrowFunctions = [require('babel-plugin-transform-es2015-arrow-functions')];
const es2015Classes = [require('babel-plugin-transform-es2015-classes')];
const es2015ForOf = [require('babel-plugin-transform-es2015-for-of'), {loose: true}];
const es2015Spread = [require('babel-plugin-transform-es2015-spread')];
const es2015TemplateLiterals = [require('babel-plugin-transform-es2015-template-literals')];
const asyncFunctions = [require('babel-plugin-syntax-async-functions')];
const exponentiationOperator = [require('babel-plugin-transform-exponentiation-operator')];
const objectAssign = [require('babel-plugin-transform-object-assign')];
const objectRestSpread = [require('babel-plugin-transform-object-rest-spread')];
const reactDisplayName = [require('babel-plugin-transform-react-display-name')];
const reactJsxSource = [require('babel-plugin-transform-react-jsx-source')];
const es2015ArrowFunctions = [
require('@babel/plugin-transform-arrow-functions'),
];
const es2015Classes = [require('@babel/plugin-transform-classes')];
const es2015ForOf = [require('@babel/plugin-transform-for-of'), {loose: true}];
const es2015Spread = [require('@babel/plugin-transform-spread')];
const es2015TemplateLiterals = [
require('@babel/plugin-transform-template-literals'),
{loose: true}, // dont 'a'.concat('b'), just use 'a'+'b'
];
const exponentiationOperator = [
require('@babel/plugin-transform-exponentiation-operator'),
];
const objectAssign = [require('@babel/plugin-transform-object-assign')];
const objectRestSpread = [require('@babel/plugin-proposal-object-rest-spread')];
const reactDisplayName = [
require('@babel/plugin-transform-react-display-name'),
];
const reactJsxSource = [require('@babel/plugin-transform-react-jsx-source')];
const symbolMember = [require('../transforms/transform-symbol-member')];

const getPreset = (src, options) => {
Expand All @@ -49,18 +67,12 @@ const getPreset = (src, options) => {

const extraPlugins = [];

if (isNull || src.indexOf('async') !== -1 || src.indexOf('await') !== -1) {
extraPlugins.push(asyncFunctions);
}
if (hasClass) {
extraPlugins.push(es2015Classes);
}
if (isNull || src.indexOf('=>') !== -1) {
extraPlugins.push(es2015ArrowFunctions);
}
if (isNull || src.indexOf('const') !== -1) {
extraPlugins.push(checkES2015Constants);
}
if (isNull || hasClass || src.indexOf('...') !== -1) {
extraPlugins.push(es2015Spread);
extraPlugins.push(objectRestSpread);
Expand Down
Loading

2 comments on commit f8d6b97

@gastonmorixe
Copy link

Choose a reason for hiding this comment

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

Thank you <3

@n1ru4l
Copy link
Contributor

@n1ru4l n1ru4l commented on f8d6b97 Apr 13, 2018

Choose a reason for hiding this comment

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

This is awesome 🔥🎉

Please sign in to comment.