[SOLVED] Polyfills don’t “stick” in Meteor on older iOS & Xiaomi browser

Hi all,
I’m stuck with a cross-browser issue where several ES features are missing or half-implemented on older iPhones (older Safari/iOS) and the Xiaomi/MIUI browser. I can’t make them take effect for third-party libs.

Symptoms/errors

From real devices (screens attached, text below):

Uncaught TypeError: Invalid attempt to iterate non-iterable instance.
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.

and

Uncaught TypeError: this.state.customRenderingMap.values is not a function
or its return value is not iterable

So this looks like a missing/buggy Symbol.iterator / Map iterators and also missing Object.values.

Object.values reports present, yet code inside libraries (FullCalendar, Slate) still crashes with the iterator error above.

Affected libraries/places

  • useSearchParams from react-router v6 (I replaced it with my own code, and that part started working).
  • FullCalendar (errors around .values() / iteration inside its state map).
  • Slate also misbehaves in older iOS.

What I’ve tried in Meteor (none of these fixed it)

  1. Importing core-js first in the app entry (client/main.tsx)
// client/main.tsx (first lines)
import 'regenerator-runtime/runtime';
import 'core-js/features/object/values';
import 'core-js/features/array/from';
import 'core-js/features/symbol';
import 'core-js/features/symbol/iterator';
import 'core-js/features/map';
import 'core-js/features/set';

// then React/Meteor app imports...
  1. Adding @babel/preset-env with built-ins

.babelrc (or package.json"babel"):

{
  "presets": [
    ["@babel/preset-env", {
      "useBuiltIns": "entry",
      "corejs": 3,
      "targets": ">0.5%, not dead"
    }],
    "@babel/preset-react",
    "@babel/preset-typescript"
  ]
}

and ensured the core-js “entry” imports are included as above.

  1. Local Meteor package to force earliest load
packages/app-polyfills/package.js
Package.describe({ name: 'app:polyfills', version: '0.0.1' });

Package.onUse(api => {
  api.versionsFrom('3.0');      // matches my Meteor version
  api.use('ecmascript');
  // load as bare to run before module system
  api.addFiles('client.js', 'client', { bare: true });
  api.addFiles('server.js', 'server', { bare: true });
});
packages/app-polyfills/client.js
import 'react-app-polyfill/ie11';
import 'core-js/features/array/find';
import 'core-js/features/array/flat';
import 'core-js/features/array/flat-map';
import 'core-js/features/array/includes';
import 'core-js/features/object/from-entries';
import 'core-js/features/object/assign';
import 'core-js/features/object/set-prototype-of';
import 'core-js/features/string/replace-all';
import 'core-js/features/promise';
import 'core-js/features/map';
import 'es7-object-polyfill';
import 'es6-set/polyfill';

Object.values = Object.values
  ? Object.values
  : o => Object.keys(o).map(k => o[k]);

Number.isInteger =
  Number.isInteger ||
  (value => {
    return (
      typeof value === 'number' &&
      isFinite(value) &&
      Math.floor(value) === value
    );
  });

(then I tried both referencing UMD bundles and also switching to <script> in head, see #4)

Added app:polyfills at the top of .meteor/packages.

  1. Hard-loading polyfills from a CDN before Meteor’s bundle (in <head>)

client/main.html:

<head>
  <script>
    window.__EARLY__ = true;
  </script>
  <script src="https://polyfill.io/v3/polyfill.min.js?features=es.array.flat,es.array.from,es.object.assign,es.promise,globalThis,URL,URLSearchParams,fetch,TextEncoder,TextDecoder,AbortController,ReadableStream&flags=gated,always"></script>
</head>

What’s confusing

  • Feature checks often return true after the above (e.g. typeof Object.values === 'function', Symbol.iterator exists), but libraries still throw “Invalid attempt to iterate non-iterable … must have a [Symbol.iterator]” during runtime.
  • This makes me suspect module/bundle evaluation order, or iterators being non-iterable (Safari-style) unless properly polyfilled/overridden, or Babel’s for..of helpers conflicting with half-native iterators.

Questions

  • Is there a known working recipe for making iterator/Map/Object.values polyfills stick for third-party deps in Meteor (React app) on older iOS & MIUI browser?
  • Any Meteor-specific gotchas around load order (Atmosphere packages with bare files?) that could still run before my head scripts/local package?
  • Could this be a Babel config issue (e.g. for..of transform needing @babel/runtime or a specific helper) inside Meteor’s build?
  • Am I barking up the wrong tree and this is not a polyfill problem?

Any guidance or a minimal working config snippet would be hugely appreciated. I can provide more device/browser versions and isolate a repro if that helps. Thanks!


You might not have to use polyfills.
Your MIUI browser is based on Chromium which has been modern for a long time now.
Safari IOS has also been modern for a long time. ("Ecmascript" | Can I use... Support tables for HTML5, CSS3, etc)

What I’d suggest. Just write a basic website and test Ecmascript versions with the oldest versions of browsers that you want to support. If you have a production app that already generated analytics, you might already have an idea of your 90-98% browser coverage. For the rest you can ask the user (like everyone else) to install a modern web browser. Today, you would only support legacy browsers on legacy devices such as POS machines, terminals of all kinds. Present cryptography complexity and the weight it puts on the processor is enough reason to not support old devices.
I personally own a 5 yo Android phone which works great, mid-tier, paid peanuts for it. I would not support anything less than it. The cost of maintaining technology so far back is not reasonable.

Back to the actual issue. On those browsers you have to determine 2 things:

  • are they actually legacy and don’t support ES6 or ES2023+.
  • does Meteor correctly identify these browsers as legacy or modern and provide the correct bundle to them.

A modern browser should receive the modern bundle and the same for the legacy.
You can use the modern-browsers package to correctly identify a browser as modern (if wrongly identified as legacy)


import { setMinimumBrowserVersions } from 'meteor/modern-browsers'
setMinimumBrowserVersions({
  facebook: 325
}, 'minimum versions for ECMAScript 2015 classes')

1 Like

Thank you very much. You had the right. Here is my working config:

export const initModernBrowserVersions = () => {
  ModernBrowsers.setMinimumBrowserVersions(
    {
      chrome: 70,
      safari: 12,
      mobileSafari: 12,
      firefox: 68,
      edge: 79,
    },
    'Needs ES2015+ iterables, Map/Set, Symbol.iterator, Object.values',
  );
};
1 Like

Just a couple of lines of config (can’t even call it code) to save you from tens of imports and the core-js nightmare.
I discovered these problems a couple of years ago when I was sharing content from my platform on Facebook. In Facebook, on mobile, my webpage would open in Facebook in-app browser, which is … just another browser to deal with. You might want to check some other userAgents for in-app browsers and see if they affect you. WhatsApp has an in-app browser (not sure about the agent), Facebook, LinkedIn, probably more.

I realised that with Xiaomi browser, the config below in settings.json has fixed my issue:

  "packages": {
    "modern-browsers": {
      "unknownBrowsersAssumedModern": true
    }
  }

The browsers have been treated as unknown, so get a legacy build.

1 Like