From 91d9289c49815cad5b8f7c840e4d8cf3a3a322e3 Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Tue, 3 Mar 2026 07:02:20 -0800 Subject: [PATCH] Babel preset: Add unstable_preserveClassPrivate to experiment with disabling private class transforms for SH (#55880) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/55880 Disable `babel/plugin-transform-private-methods` and `babel/plugin-transform-private-property-in-object` when `customTransformOptions.unstable_preserveClassPrivate` is truthy. This allows us to experiment with native private class field and method support in Static Hermes. Changelog: [Internal] Reviewed By: vzaidman, javache Differential Revision: D93010263 --- .../src/__tests__/transform-snapshot-test.js | 36 +++++++++++++++++++ .../src/configs/main.js | 23 +++++++++--- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/packages/react-native-babel-preset/src/__tests__/transform-snapshot-test.js b/packages/react-native-babel-preset/src/__tests__/transform-snapshot-test.js index 430d0a78e25a..8451562fe50d 100644 --- a/packages/react-native-babel-preset/src/__tests__/transform-snapshot-test.js +++ b/packages/react-native-babel-preset/src/__tests__/transform-snapshot-test.js @@ -352,5 +352,41 @@ describe('react-native-babel-preset transform snapshots', () => { expect(result).toContain('import'); expect(result).toContain('export'); }); + + it('preserves private class fields with unstable_preserveClassPrivate', () => { + const code = ` + class Counter { + #count = 0; + #privateMethod() { return this.#count; } + increment() { this.#count++; } + } + `; + const result = transformCode(code, { + dev: false, + unstable_transformProfile: 'hermes-stable', + customTransformOptions: { + unstable_preserveClassPrivate: true, + }, + }); + expect(result).toContain('#count'); + expect(result).toContain('#privateMethod'); + }); + + it('transforms private class fields without unstable_preserveClassPrivate', () => { + const code = ` + class Counter { + #count = 0; + #privateMethod() { return this.#count; } + increment() { this.#count++; } + } + `; + const result = transformCode(code, { + dev: false, + unstable_transformProfile: 'hermes-stable', + }); + expect(result).not.toContain('#count'); + expect(result).not.toContain('#privateMethod'); + expect(result).toContain('_classPrivateFieldLooseKey'); + }); }); }); diff --git a/packages/react-native-babel-preset/src/configs/main.js b/packages/react-native-babel-preset/src/configs/main.js index ad3dfe462803..5229c59ca9f7 100644 --- a/packages/react-native-babel-preset/src/configs/main.js +++ b/packages/react-native-babel-preset/src/configs/main.js @@ -21,6 +21,10 @@ const EXCLUDED_FIRST_PARTY_PATHS = [ /[/\\]private[/\\]react-native-fantom[/\\]/, ]; +// customTransformOptions may be strings from URL params, or booleans passed +// programatically. For strings, handle them as Metro does when parsing URLs. +const TRUE_VALS = new Set([true, 'true', '1']); + function isTypeScriptSource(fileName) { return !!fileName && fileName.endsWith('.ts'); } @@ -73,6 +77,11 @@ const getPreset = (src, options, babel) => { // Preserve class syntax and related if we're using Hermes V1. const preserveClasses = isHermesV1; + // Preserve private class fields and methods if the experiment is enabled. + const preserveClassPrivate = TRUE_VALS.has( + options?.customTransformOptions?.unstable_preserveClassPrivate, + ); + const isNull = src == null; const hasClass = isNull || src.indexOf('class') !== -1; @@ -217,11 +226,15 @@ const getPreset = (src, options, babel) => { ...(preserveClasses ? [] : [[require('@babel/plugin-transform-class-properties'), {loose}]]), - [require('@babel/plugin-transform-private-methods'), {loose}], - [ - require('@babel/plugin-transform-private-property-in-object'), - {loose}, - ], + ...(preserveClassPrivate + ? [] + : [ + [require('@babel/plugin-transform-private-methods'), {loose}], + [ + require('@babel/plugin-transform-private-property-in-object'), + {loose}, + ], + ]), [require('@babel/plugin-syntax-dynamic-import')], [require('@babel/plugin-syntax-export-default-from')], ...passthroughSyntaxPlugins,