Skip to content

Commit e4cb767

Browse files
robhoganmeta-codesync[bot]
authored andcommitted
Babel preset: Add unstable_preserveBlockScoping to experiment with disabling block-scoping transform for SH (#56825)
Summary: Pull Request resolved: #56825 Disable `babel/plugin-transform-block-scoping` when `customTransformOptions.unstable_preserveBlockScoping` is truthy. This allows us to experiment with native let/const block scoping support in Static Hermes. Changelog: [Internal] Reviewed By: vzaidman Differential Revision: D93013927 fbshipit-source-id: 6e2c1274426943fcf1ce5a8c09d677e1d0afb35f
1 parent 67303df commit e4cb767

3 files changed

Lines changed: 343 additions & 1 deletion

File tree

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @generated
8+
* @noformat
9+
* @noflow
10+
* @nolint
11+
*
12+
* This is a snapshot of the transform output for testing purposes.
13+
* To update, run: js1 test transform-snapshot-test.js -u
14+
*
15+
* Transform configuration:
16+
* - Hermes stable transform profile in development mode with unstable_preserveBlockScoping enabled
17+
* - Options: {"dev":true,"unstable_transformProfile":"hermes-stable","customTransformOptions":{"unstable_preserveBlockScoping":true}}
18+
*/
19+
20+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
21+
Object.defineProperty(exports, "__esModule", {
22+
value: true
23+
});
24+
exports.LegacyComponent = exports.Dog = exports.Counter = exports.Animal = void 0;
25+
exports.ModernComponent = ModernComponent;
26+
exports.MyClass = void 0;
27+
exports.asyncNumberGenerator = asyncNumberGenerator;
28+
Object.defineProperty(exports, "default", {
29+
enumerable: true,
30+
get: function () {
31+
return _dataUtils.fetchData;
32+
}
33+
});
34+
exports.getNestedValue = getNestedValue;
35+
exports.loadModule = loadModule;
36+
exports.matchEmoji = matchEmoji;
37+
exports.mergeConfigs = mergeConfigs;
38+
exports.parseDate = parseDate;
39+
exports.processUser = processUser;
40+
exports.safeJsonParse = safeJsonParse;
41+
exports.sumPairs = sumPairs;
42+
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
43+
var _setPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/setPrototypeOf"));
44+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
45+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
46+
var _classPrivateFieldLooseBase2 = _interopRequireDefault(require("@babel/runtime/helpers/classPrivateFieldLooseBase"));
47+
var _classPrivateFieldLooseKey2 = _interopRequireDefault(require("@babel/runtime/helpers/classPrivateFieldLooseKey"));
48+
var _awaitAsyncGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/awaitAsyncGenerator"));
49+
var _wrapAsyncGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapAsyncGenerator"));
50+
var _react = _interopRequireWildcard(require("react"));
51+
var React = _react;
52+
var _jsxRuntime = require("react/jsx-runtime");
53+
var _dataUtils = require("./data-utils");
54+
var _jsxFileName = "/absolute/path/to/input.js";
55+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
56+
function _wrapRegExp() { _wrapRegExp = function (e, r) { return new BabelRegExp(e, void 0, r); }; var e = RegExp.prototype, r = new WeakMap(); function BabelRegExp(e, t, p) { var o = RegExp(e, t); return r.set(o, p || r.get(e)), (0, _setPrototypeOf2.default)(o, BabelRegExp.prototype); } function buildGroups(e, t) { var p = r.get(t); return Object.keys(p).reduce(function (r, t) { var o = p[t]; if ("number" == typeof o) r[t] = e[o];else { for (var i = 0; void 0 === e[o[i]] && i + 1 < o.length;) i++; r[t] = e[o[i]]; } return r; }, Object.create(null)); } return (0, _inherits2.default)(BabelRegExp, RegExp), BabelRegExp.prototype.exec = function (r) { var t = e.exec.call(this, r); if (t) { t.groups = buildGroups(t, this); var p = t.indices; p && (p.groups = buildGroups(p, this)); } return t; }, BabelRegExp.prototype[Symbol.replace] = function (t, p) { if ("string" == typeof p) { var o = r.get(this); return e[Symbol.replace].call(this, t, p.replace(/\$<([^>]+)(>|$)/g, function (e, r, t) { if ("" === t) return e; var p = o[r]; return Array.isArray(p) ? "$" + p.join("$") : "number" == typeof p ? "$" + p : ""; })); } if ("function" == typeof p) { var i = this; return e[Symbol.replace].call(this, t, function () { var e = arguments; return "object" != typeof e[e.length - 1] && (e = [].slice.call(e)).push(buildGroups(e, i)), p.apply(this, e); }); } return e[Symbol.replace].call(this, t, p); }, _wrapRegExp.apply(this, arguments); }
57+
function _createForOfIteratorHelperLoose(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (t) return (t = t.call(r)).next.bind(t); if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var o = 0; return function () { return o >= r.length ? { done: !0 } : { done: !1, value: r[o++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
58+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
59+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
60+
var _count = (0, _classPrivateFieldLooseKey2.default)("count");
61+
var _instances = (0, _classPrivateFieldLooseKey2.default)("instances");
62+
var _increment = (0, _classPrivateFieldLooseKey2.default)("increment");
63+
class Counter {
64+
constructor() {
65+
Object.defineProperty(this, _increment, {
66+
value: _increment2
67+
});
68+
Object.defineProperty(this, _count, {
69+
writable: true,
70+
value: 0
71+
});
72+
(0, _classPrivateFieldLooseBase2.default)(Counter, _instances)[_instances]++;
73+
}
74+
get value() {
75+
return (0, _classPrivateFieldLooseBase2.default)(this, _count)[_count];
76+
}
77+
increment() {
78+
(0, _classPrivateFieldLooseBase2.default)(this, _increment)[_increment]();
79+
}
80+
static get instanceCount() {
81+
return (0, _classPrivateFieldLooseBase2.default)(Counter, _instances)[_instances];
82+
}
83+
}
84+
exports.Counter = Counter;
85+
function _increment2() {
86+
(0, _classPrivateFieldLooseBase2.default)(this, _count)[_count]++;
87+
}
88+
Object.defineProperty(Counter, _instances, {
89+
writable: true,
90+
value: 0
91+
});
92+
function asyncNumberGenerator(_x) {
93+
return _asyncNumberGenerator.apply(this, arguments);
94+
}
95+
function _asyncNumberGenerator() {
96+
_asyncNumberGenerator = (0, _wrapAsyncGenerator2.default)(function* (max) {
97+
for (let i = 0; i < max; i++) {
98+
yield (0, _awaitAsyncGenerator2.default)(new Promise(resolve => setTimeout(resolve, 100)));
99+
yield i;
100+
}
101+
});
102+
return _asyncNumberGenerator.apply(this, arguments);
103+
}
104+
function fetchData(_x2) {
105+
return _fetchData.apply(this, arguments);
106+
}
107+
function _fetchData() {
108+
_fetchData = (0, _asyncToGenerator2.default)(function* (url) {
109+
const response = yield fetch(url);
110+
const data = yield response.json();
111+
return {
112+
data
113+
};
114+
});
115+
return _fetchData.apply(this, arguments);
116+
}
117+
function getNestedValue(obj) {
118+
var _obj$a$b$c, _obj$a;
119+
return (_obj$a$b$c = obj == null || (_obj$a = obj.a) == null || (_obj$a = _obj$a.b) == null ? void 0 : _obj$a.c) != null ? _obj$a$b$c : 42;
120+
}
121+
class Animal {
122+
#age;
123+
constructor(name, age) {
124+
this.name = name;
125+
this.#age = age;
126+
}
127+
speak() {
128+
return `${this.name} makes a sound`;
129+
}
130+
get age() {
131+
return this.#age;
132+
}
133+
}
134+
exports.Animal = Animal;
135+
class Dog extends Animal {
136+
constructor(name, age, breed) {
137+
super(name, age);
138+
this.breed = breed;
139+
}
140+
speak() {
141+
return `${this.name} barks!`;
142+
}
143+
fetchTreats() {
144+
return (0, _asyncToGenerator2.default)(function* () {
145+
yield new Promise(resolve => setTimeout(resolve, 100));
146+
return ['bone', 'biscuit', 'toy'];
147+
})();
148+
}
149+
}
150+
exports.Dog = Dog;
151+
function processUser({
152+
name,
153+
age = 18,
154+
...rest
155+
}) {
156+
const _rest$city = rest.city,
157+
city = _rest$city === void 0 ? 'Unknown' : _rest$city;
158+
return `${name} (${age}) from ${city}`;
159+
}
160+
function mergeConfigs(base, ...overrides) {
161+
return {
162+
...base,
163+
...overrides.reduce((acc, o) => ({
164+
...acc,
165+
...o
166+
}), {})
167+
};
168+
}
169+
function sumPairs(pairs) {
170+
let total = 0;
171+
for (var _iterator = _createForOfIteratorHelperLoose(pairs), _step; !(_step = _iterator()).done;) {
172+
const _ref = _step.value;
173+
var _ref2 = (0, _slicedToArray2.default)(_ref, 2);
174+
const a = _ref2[0];
175+
const b = _ref2[1];
176+
total += a + b;
177+
}
178+
return total;
179+
}
180+
function parseDate(dateString) {
181+
const regex = _wrapRegExp(/(\d{4})-(\d{2})-(\d{2})/, {
182+
year: 1,
183+
month: 2,
184+
day: 3
185+
});
186+
const match = dateString.match(regex);
187+
if (match != null && match.groups) {
188+
return {
189+
year: match.groups.year,
190+
month: match.groups.month,
191+
day: match.groups.day
192+
};
193+
}
194+
return null;
195+
}
196+
function safeJsonParse(input) {
197+
try {
198+
return JSON.parse(input);
199+
} catch (_unused) {
200+
return null;
201+
}
202+
}
203+
function matchEmoji(text) {
204+
const match = text.match(/(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEDC-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE70-\uDE7C\uDE80-\uDE89\uDE8F-\uDEC6\uDECE-\uDEDC\uDEDF-\uDEE9\uDEF0-\uDEF8])/);
205+
return match == null ? void 0 : match[0];
206+
}
207+
const MyClass = class {
208+
constructor(value) {
209+
this.value = value;
210+
}
211+
};
212+
exports.MyClass = MyClass;
213+
function loadModule() {
214+
return _loadModule.apply(this, arguments);
215+
}
216+
function _loadModule() {
217+
_loadModule = (0, _asyncToGenerator2.default)(function* () {
218+
const module = yield import('./some-module');
219+
return module.default;
220+
});
221+
return _loadModule.apply(this, arguments);
222+
}
223+
const LegacyComponent = exports.LegacyComponent = React.createClass({
224+
displayName: 'LegacyComponent',
225+
getInitialState() {
226+
return {
227+
count: 0
228+
};
229+
},
230+
render() {
231+
return (0, _jsxRuntime.jsx)("div", {
232+
children: this.state.count
233+
});
234+
}
235+
});
236+
function ModernComponent({
237+
initialCount = 0
238+
}) {
239+
const _useState = (0, _react.useState)(initialCount),
240+
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
241+
count = _useState2[0],
242+
setCount = _useState2[1];
243+
const _useState3 = (0, _react.useState)(Status.Active),
244+
_useState4 = (0, _slicedToArray2.default)(_useState3, 2),
245+
status = _useState4[0],
246+
setStatus = _useState4[1];
247+
(0, _react.useEffect)(() => {
248+
const timer = setInterval(() => {
249+
setCount(c => c + 1);
250+
}, 1000);
251+
return () => clearInterval(timer);
252+
}, []);
253+
function handleAsyncClick() {
254+
return _handleAsyncClick.apply(this, arguments);
255+
}
256+
function _handleAsyncClick() {
257+
_handleAsyncClick = (0, _asyncToGenerator2.default)(function* () {
258+
const data = yield fetchData('/api/data');
259+
console.log(data);
260+
});
261+
return _handleAsyncClick.apply(this, arguments);
262+
}
263+
const handleClick = function () {
264+
var _ref3 = (0, _asyncToGenerator2.default)(function* () {
265+
yield handleAsyncClick();
266+
setStatus(Status.Pending);
267+
});
268+
return function handleClick() {
269+
return _ref3.apply(this, arguments);
270+
};
271+
}();
272+
return (0, _jsxRuntime.jsxs)("div", {
273+
children: [(0, _jsxRuntime.jsx)("span", {
274+
"data-testid": "count",
275+
children: count
276+
}), (0, _jsxRuntime.jsx)("span", {
277+
"data-testid": "status",
278+
children: String(status)
279+
}), (0, _jsxRuntime.jsx)("button", {
280+
onClick: handleClick,
281+
children: "Increment"
282+
}), (0, _jsxRuntime.jsx)(LegacyComponent, {})]
283+
});
284+
}

packages/react-native-babel-preset/src/__tests__/transform-snapshot-test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,18 @@ const testConfigs = [
129129
description:
130130
'Hermes stable transform profile in development mode with unstable_preserveAsync enabled',
131131
},
132+
{
133+
name: 'hermes-stable-dev-preserve-block-scoping',
134+
options: {
135+
dev: true,
136+
unstable_transformProfile: 'hermes-stable',
137+
customTransformOptions: {
138+
unstable_preserveBlockScoping: true,
139+
},
140+
},
141+
description:
142+
'Hermes stable transform profile in development mode with unstable_preserveBlockScoping enabled',
143+
},
132144
];
133145

134146
function transformCode(
@@ -451,5 +463,44 @@ describe('react-native-babel-preset transform snapshots', () => {
451463
});
452464
expect(result).toContain('_asyncToGenerator');
453465
});
466+
467+
it('preserves block scoping with unstable_preserveBlockScoping', () => {
468+
const code = `
469+
let x = 1;
470+
const y = 2;
471+
{
472+
let x = 3;
473+
const z = 4;
474+
}
475+
`;
476+
const result = transformCode(code, {
477+
dev: false,
478+
unstable_transformProfile: 'hermes-stable',
479+
customTransformOptions: {
480+
unstable_preserveBlockScoping: true,
481+
},
482+
});
483+
expect(result).toContain('let x = 1');
484+
expect(result).toContain('const y = 2');
485+
expect(result).toContain('let x = 3');
486+
expect(result).toContain('const z = 4');
487+
});
488+
489+
it('transforms block scoping without unstable_preserveBlockScoping', () => {
490+
const code = `
491+
let x = 1;
492+
const y = 2;
493+
{
494+
let x = 3;
495+
}
496+
`;
497+
const result = transformCode(code, {
498+
dev: false,
499+
unstable_transformProfile: 'hermes-stable',
500+
});
501+
expect(result).toContain('var ');
502+
expect(result).not.toContain('let ');
503+
expect(result).not.toContain('const ');
504+
});
454505
});
455506
});

packages/react-native-babel-preset/src/configs/main.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ const getPreset = (src, options, babel) => {
8787
options?.customTransformOptions?.unstable_preserveAsync,
8888
);
8989

90+
// Preserve block scoping (let/const) if the experiment is enabled.
91+
const preserveBlockScoping = TRUE_VALS.has(
92+
options?.customTransformOptions?.unstable_preserveBlockScoping,
93+
);
94+
9095
const isNull = src == null;
9196
const hasClass = isNull || src.indexOf('class') !== -1;
9297

@@ -227,7 +232,9 @@ const getPreset = (src, options, babel) => {
227232
},
228233
],
229234
[require('babel-plugin-transform-flow-enums')],
230-
[require('@babel/plugin-transform-block-scoping')],
235+
...(preserveBlockScoping
236+
? []
237+
: [[require('@babel/plugin-transform-block-scoping')]]),
231238
...(preserveClasses
232239
? []
233240
: [[require('@babel/plugin-transform-class-properties'), {loose}]]),

0 commit comments

Comments
 (0)