From 9d38b800bac90160c7b0e0fb14ceef89b14a5d0b Mon Sep 17 00:00:00 2001 From: Aravind M Nair Date: Fri, 31 Jul 2026 16:20:19 +0530 Subject: [PATCH 1/2] Remove unused Promise polyfill and promise dependency Every supported JS engine ships a native Promise, but the polyfill branch in polyfillPromise.js is statically reachable, so Metro bundles the promise package into every app even though it is never used. Remove the dead branch, Libraries/Promise.js and the promise dependency. DEV unhandled-rejection tracking is unchanged. Fixes #57702 --- flow-typed/npm/promise_v8.x.x.js | 30 ------------------- .../Libraries/Core/polyfillPromise.js | 30 ++++++------------- packages/react-native/Libraries/Promise.js | 23 -------------- .../promiseRejectionTrackingOptions.js | 11 +++++-- packages/react-native/package.json | 1 - 5 files changed, 17 insertions(+), 78 deletions(-) delete mode 100644 flow-typed/npm/promise_v8.x.x.js delete mode 100644 packages/react-native/Libraries/Promise.js diff --git a/flow-typed/npm/promise_v8.x.x.js b/flow-typed/npm/promise_v8.x.x.js deleted file mode 100644 index 7358622e3bea..000000000000 --- a/flow-typed/npm/promise_v8.x.x.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @flow strict - * @format - */ - -declare module 'promise/setimmediate/es6-extensions' { - declare module.exports: typeof Promise; -} - -declare module 'promise/setimmediate/done' { - declare module.exports: typeof Promise; -} - -declare module 'promise/setimmediate/finally' { - declare module.exports: typeof Promise; -} - -declare module 'promise/setimmediate/rejection-tracking' { - declare module.exports: { - enable: ( - options?: ?{ - whitelist?: ?Array, - allRejections?: ?boolean, - onUnhandled?: ?(number, unknown) => void, - onHandled?: ?(number, unknown) => void, - }, - ) => void, - disable: () => void, - }; -} diff --git a/packages/react-native/Libraries/Core/polyfillPromise.js b/packages/react-native/Libraries/Core/polyfillPromise.js index 5bfec271d1c5..a202b3b749e6 100644 --- a/packages/react-native/Libraries/Core/polyfillPromise.js +++ b/packages/react-native/Libraries/Core/polyfillPromise.js @@ -10,29 +10,17 @@ 'use strict'; -const {polyfillGlobal} = require('../Utilities/PolyfillFunctions'); - /** - * Set up Promise. The native Promise implementation throws the following error: - * ERROR: Event loop not supported. - * - * If you don't need these polyfills, don't use InitializeCore; just directly - * require the modules you need from InitializeCore for setup. + * Set up Promise. All supported JS engines provide a spec-compliant native + * Promise implementation, so it no longer needs to be polyfilled. In DEV, + * enable unhandled rejection tracking where the engine supports it. */ -// If global.Promise is provided by Hermes, we are confident that it can provide -// all the methods needed by React Native, so we can directly use it. -if (global?.HermesInternal?.hasPromise?.()) { - const HermesPromise = global.Promise; - - if (__DEV__) { - if (typeof HermesPromise !== 'function') { - console.error('HermesPromise does not exist'); - } - global.HermesInternal?.enablePromiseRejectionTracker?.( - require('../promiseRejectionTrackingOptions').default, - ); +if (__DEV__) { + if (typeof global.Promise !== 'function') { + console.error('Promise does not exist'); } -} else { - polyfillGlobal('Promise', () => require('../Promise').default); + global.HermesInternal?.enablePromiseRejectionTracker?.( + require('../promiseRejectionTrackingOptions').default, + ); } diff --git a/packages/react-native/Libraries/Promise.js b/packages/react-native/Libraries/Promise.js deleted file mode 100644 index 5184765a0c83..000000000000 --- a/packages/react-native/Libraries/Promise.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict - * @format - */ - -'use strict'; - -import Promise from 'promise/setimmediate/es6-extensions'; - -require('promise/setimmediate/finally'); - -if (__DEV__) { - require('promise/setimmediate/rejection-tracking').enable( - require('./promiseRejectionTrackingOptions').default, - ); -} - -export default Promise; diff --git a/packages/react-native/Libraries/promiseRejectionTrackingOptions.js b/packages/react-native/Libraries/promiseRejectionTrackingOptions.js index 95fcf1c093d0..4a7b5ec3b515 100644 --- a/packages/react-native/Libraries/promiseRejectionTrackingOptions.js +++ b/packages/react-native/Libraries/promiseRejectionTrackingOptions.js @@ -8,11 +8,16 @@ * @format */ -import typeof {enable} from 'promise/setimmediate/rejection-tracking'; - import ExceptionsManager from './Core/ExceptionsManager'; -const rejectionTrackingOptions: NonNullable[0]> = { +type RejectionTrackingOptions = { + whitelist?: ?Array, + allRejections?: ?boolean, + onUnhandled?: ?(number, unknown) => void, + onHandled?: ?(number, unknown) => void, +}; + +const rejectionTrackingOptions: RejectionTrackingOptions = { allRejections: true, onHandled: id => { const warning = diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 9ba6a5668473..95ccddaf327e 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -169,7 +169,6 @@ "metro-source-map": "^0.87.0", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", - "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", From 57a8c2fedc1a736c1fbe1a97c00ec89bf5b667e5 Mon Sep 17 00:00:00 2001 From: Aravind M Nair Date: Sun, 13 Sep 2026 15:57:48 +0530 Subject: [PATCH 2/2] Narrow to removing only the polyfill fallback Keep Libraries/Promise.js, the promise dependency and its flow-typed def, so anything that imports them directly still builds. Deleting the else branch on its own is enough to stop Metro bundling the promise package, which is what #57702 is about. --- flow-typed/npm/promise_v8.x.x.js | 30 +++++++++++++++++++ .../Libraries/Core/polyfillPromise.js | 26 ++++++++++------ packages/react-native/Libraries/Promise.js | 23 ++++++++++++++ .../promiseRejectionTrackingOptions.js | 11 ++----- packages/react-native/package.json | 1 + 5 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 flow-typed/npm/promise_v8.x.x.js create mode 100644 packages/react-native/Libraries/Promise.js diff --git a/flow-typed/npm/promise_v8.x.x.js b/flow-typed/npm/promise_v8.x.x.js new file mode 100644 index 000000000000..7358622e3bea --- /dev/null +++ b/flow-typed/npm/promise_v8.x.x.js @@ -0,0 +1,30 @@ +/** + * @flow strict + * @format + */ + +declare module 'promise/setimmediate/es6-extensions' { + declare module.exports: typeof Promise; +} + +declare module 'promise/setimmediate/done' { + declare module.exports: typeof Promise; +} + +declare module 'promise/setimmediate/finally' { + declare module.exports: typeof Promise; +} + +declare module 'promise/setimmediate/rejection-tracking' { + declare module.exports: { + enable: ( + options?: ?{ + whitelist?: ?Array, + allRejections?: ?boolean, + onUnhandled?: ?(number, unknown) => void, + onHandled?: ?(number, unknown) => void, + }, + ) => void, + disable: () => void, + }; +} diff --git a/packages/react-native/Libraries/Core/polyfillPromise.js b/packages/react-native/Libraries/Core/polyfillPromise.js index a202b3b749e6..069c12012c19 100644 --- a/packages/react-native/Libraries/Core/polyfillPromise.js +++ b/packages/react-native/Libraries/Core/polyfillPromise.js @@ -11,16 +11,24 @@ 'use strict'; /** - * Set up Promise. All supported JS engines provide a spec-compliant native - * Promise implementation, so it no longer needs to be polyfilled. In DEV, - * enable unhandled rejection tracking where the engine supports it. + * Set up Promise. The engines React Native supports ship a native Promise, so + * it is no longer polyfilled here. + * + * If you don't need these polyfills, don't use InitializeCore; just directly + * require the modules you need from InitializeCore for setup. */ -if (__DEV__) { - if (typeof global.Promise !== 'function') { - console.error('Promise does not exist'); +// If global.Promise is provided by Hermes, we are confident that it can provide +// all the methods needed by React Native, so we can directly use it. +if (global?.HermesInternal?.hasPromise?.()) { + const HermesPromise = global.Promise; + + if (__DEV__) { + if (typeof HermesPromise !== 'function') { + console.error('HermesPromise does not exist'); + } + global.HermesInternal?.enablePromiseRejectionTracker?.( + require('../promiseRejectionTrackingOptions').default, + ); } - global.HermesInternal?.enablePromiseRejectionTracker?.( - require('../promiseRejectionTrackingOptions').default, - ); } diff --git a/packages/react-native/Libraries/Promise.js b/packages/react-native/Libraries/Promise.js new file mode 100644 index 000000000000..5184765a0c83 --- /dev/null +++ b/packages/react-native/Libraries/Promise.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +'use strict'; + +import Promise from 'promise/setimmediate/es6-extensions'; + +require('promise/setimmediate/finally'); + +if (__DEV__) { + require('promise/setimmediate/rejection-tracking').enable( + require('./promiseRejectionTrackingOptions').default, + ); +} + +export default Promise; diff --git a/packages/react-native/Libraries/promiseRejectionTrackingOptions.js b/packages/react-native/Libraries/promiseRejectionTrackingOptions.js index 4a7b5ec3b515..95fcf1c093d0 100644 --- a/packages/react-native/Libraries/promiseRejectionTrackingOptions.js +++ b/packages/react-native/Libraries/promiseRejectionTrackingOptions.js @@ -8,16 +8,11 @@ * @format */ -import ExceptionsManager from './Core/ExceptionsManager'; +import typeof {enable} from 'promise/setimmediate/rejection-tracking'; -type RejectionTrackingOptions = { - whitelist?: ?Array, - allRejections?: ?boolean, - onUnhandled?: ?(number, unknown) => void, - onHandled?: ?(number, unknown) => void, -}; +import ExceptionsManager from './Core/ExceptionsManager'; -const rejectionTrackingOptions: RejectionTrackingOptions = { +const rejectionTrackingOptions: NonNullable[0]> = { allRejections: true, onHandled: id => { const warning = diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 95ccddaf327e..9ba6a5668473 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -169,6 +169,7 @@ "metro-source-map": "^0.87.0", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", + "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2",