Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Audit][Implementation] - Remove Intl Polyfills which are supported by Hermes by separating iOS and android implementation #38565

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
"@expensify/react-native-live-markdown": "0.1.5",
"@expo/metro-runtime": "~3.1.1",
"@formatjs/intl-datetimeformat": "^6.10.0",
"@formatjs/intl-getcanonicallocales": "^2.2.0",
"@formatjs/intl-listformat": "^7.2.2",
"@formatjs/intl-locale": "^3.3.0",
"@formatjs/intl-numberformat": "^8.5.0",
Expand Down Expand Up @@ -154,8 +153,8 @@
"react-native-plaid-link-sdk": "10.8.0",
"react-native-qrcode-svg": "^6.2.0",
"react-native-quick-sqlite": "^8.0.0-beta.2",
"react-native-release-profiler": "^0.1.6",
"react-native-reanimated": "^3.7.2",
"react-native-release-profiler": "^0.1.6",
"react-native-render-html": "6.3.1",
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "3.29.0",
Expand Down
2 changes: 1 addition & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2922,7 +2922,7 @@ const CONST = {
CURRENCY: 'XAF',
FORMAT: 'symbol',
SAMPLE_INPUT: '123456.789',
EXPECTED_OUTPUT: 'FCFA 123,457',
EXPECTED_OUTPUT: 'FCFA 123,457',
},

PATHS_TO_TREAT_AS_EXTERNAL: ['NewExpensify.dmg', 'docs/index.html'],
Expand Down
17 changes: 17 additions & 0 deletions src/libs/IntlPolyfill/index.android.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import polyfillListFormat from './polyfillListFormat';
import type IntlPolyfill from './types';

/**
* Polyfill the Intl API, always performed for native devices.
*/
const intlPolyfill: IntlPolyfill = () => {
// Native devices require extra polyfills which are
// not yet implemented in hermes.
// see support: https://hermesengine.dev/docs/intl/

require('@formatjs/intl-locale/polyfill');

polyfillListFormat();
};

export default intlPolyfill;
hurali97 marked this conversation as resolved.
Show resolved Hide resolved
hurali97 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,21 @@ import type IntlPolyfill from './types';
* Polyfill the Intl API, always performed for native devices.
*/
const intlPolyfill: IntlPolyfill = () => {
// Native devices require extra polyfills
require('@formatjs/intl-getcanonicallocales/polyfill');
hurali97 marked this conversation as resolved.
Show resolved Hide resolved
// Native devices require extra polyfills which are
// not yet implemented in hermes.
// see support: https://hermesengine.dev/docs/intl/

require('@formatjs/intl-locale/polyfill');

// Required to polyfill NumberFormat on iOS
// see: https://github.com/facebook/hermes/issues/1172#issuecomment-1776156538
require('@formatjs/intl-pluralrules/polyfill');
hurali97 marked this conversation as resolved.
Show resolved Hide resolved
polyfillNumberFormat();
Copy link
Contributor

Choose a reason for hiding this comment

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

There is an additional check inside that function: hasOldCurrencyData. Seems like even if the API is supported it may not work as expected e.g. old API. Seeing that we have an early return in enabling that polyfill it may be safer to keep it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@s77rt I have some reservations around hasOldCurrencyData, if you could explain a bit more about it. So the thing is the output of this function differs only by a space character, otherwise both of the strings are the same on all platforms.

I printed both values of the comparison in the console and it appears that the output by NumberFormat contains a special space character which is different from normal space character.

Screenshot 2024-03-21 at 1 33 02 PM

If I update this const value, only then we do early return otherwise we are always polyfilling NumberFormat. Android and Web also works fine BUT iOS doesn't. Which means that the below code is not working as expected. Ideally, iOS should not fall in the early-return. We probably want to update this condition to include Intl.NumberFormat(locale, options).formatToParts(number) because this is what's missing in iOS and it causes the crash.

/**
 * Check if the locale data is as expected on the device.
 * Ensures that the currency data is consistent across devices.
 */
function hasOldCurrencyData(): boolean {
    return (
        new Intl.NumberFormat(CONST.LOCALES.DEFAULT, {
            style: CONST.POLYFILL_TEST.STYLE,
            currency: CONST.POLYFILL_TEST.CURRENCY,
            currencyDisplay: CONST.POLYFILL_TEST.FORMAT,
        }).format(Number(CONST.POLYFILL_TEST.SAMPLE_INPUT)) !== CONST.POLYFILL_TEST.EXPECTED_OUTPUT
    );
}

export default function () {
    if (Intl && 'NumberFormat' in Intl && !hasOldCurrencyData()) {
        return;
    }

    require('@formatjs/intl-numberformat/polyfill-force');

    // Load en & es Locale data
    require('@formatjs/intl-numberformat/locale-data/en');
    require('@formatjs/intl-numberformat/locale-data/es');
}

Copy link
Contributor

Choose a reason for hiding this comment

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

iOS do not fall into the early-return, perhaps you tested after the polyfill was applied already.
Screenshot 2024-03-22 at 12 10 22 AM

Got same results on Android

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@s77rt Did you test with the applied diff? I also get the same results as you but without the diff which is what we have in main.

With the diff, each platform falls into the early return and only iOS fails because polyfill isn't applied and we get error on Intl.NumberFormat(locale, options).formatToParts(number). So I am wondering whether we should include formatToParts in the condition for early return?

Otherwise, let's keep the things as is 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

@hurali97 Let's do the following:

  1. Fix the POLYFILL_TEST.EXPECTED_OUTPUT value (use correct whitespace) so the polyfill is applied only when needed
  2. Add formatToParts to the condition

Summary:

  • Intl.NumberFormat polyfill is not needed on Android.
  • Intl.NumberFormat polyfill is needed on iOS due to missing formatToParts funcition
  • Intl.NumberFormat polyfill is needed on Web due to outdated currency data on some browsers (issue)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@s77rt Perfect. I have addressed these changes, you can re-review 👍


// Required to polyfill DateTimeFormat on iOS
// see: https://github.com/facebook/hermes/issues/1172#issuecomment-1776156538
polyfillDateTimeFormat();

polyfillListFormat();
};

Expand Down
24 changes: 16 additions & 8 deletions src/libs/IntlPolyfill/polyfillNumberFormat.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import CONST from '@src/CONST';

const numberFormat = new Intl.NumberFormat(CONST.LOCALES.DEFAULT, {
style: CONST.POLYFILL_TEST.STYLE,
currency: CONST.POLYFILL_TEST.CURRENCY,
currencyDisplay: CONST.POLYFILL_TEST.FORMAT,
});

/**
* Check if the locale data is as expected on the device.
* Ensures that the currency data is consistent across devices.
*/
function hasOldCurrencyData(): boolean {
return (
new Intl.NumberFormat(CONST.LOCALES.DEFAULT, {
style: CONST.POLYFILL_TEST.STYLE,
currency: CONST.POLYFILL_TEST.CURRENCY,
currencyDisplay: CONST.POLYFILL_TEST.FORMAT,
}).format(Number(CONST.POLYFILL_TEST.SAMPLE_INPUT)) !== CONST.POLYFILL_TEST.EXPECTED_OUTPUT
);
return numberFormat.format(Number(CONST.POLYFILL_TEST.SAMPLE_INPUT)) !== CONST.POLYFILL_TEST.EXPECTED_OUTPUT;
}

/**
* Checks if the formatToParts function is available on the
* Intl.NumberFormat object.
*/
function hasFormatToParts(): boolean {
return typeof numberFormat.formatToParts === 'function';
}

export default function () {
if (Intl && 'NumberFormat' in Intl && !hasOldCurrencyData()) {
if (Intl && 'NumberFormat' in Intl && !hasOldCurrencyData() && hasFormatToParts()) {
return;
}

Expand Down
Loading