When using react-native 0.59.10, and setting the locale for momentJS, we caught a super brutal crash for our release-builds only. This crash wasn't reproducible for us with the debugger attached. This crash occurred for us both on iOS & Android. try-catch statements wrapping all moment usage did not catch the crash!
To Reproduce
["fr-CA", "en-US", "fr", "en"]
moment.locale(localeCandidate)
inside a try-catch block, the Application still crashes on this line⁇This was a crash-on-launch but only for Release builds!! This made it super tricky to extract useful error messages / logging.
We saw the following error messages via our Bugsnag integration & System Console Logging
Exception in HostFunction: Error loading module0from RAM Bundle: unspecified iostream_category error
Exception in HostFunction: Module not found: 0
Requiring unknown module "./locale/en-us".
-- but strangely, this error was not being processed in a timely manner. Possibly a react-native / bugsnag issue.Workaround: Commenting out those two lines halted the crash!
Expected behaviors
require()
in Release)Smartphone (please complete the following information):
Moment-specific environment
Please run the following code in your environment and include the output:
console.log([
new Date().toString(),
new Date().toLocaleString(),
new Date().getTimezoneOffset(),
navigator && navigator.userAgent, // react-native might not have a navigator
moment.version,
]);
Output:
[
"Wed Oct 09 2019 18:52:16 GMT-0700 (PDT)",
"09/10/2019 à 18:52:16", // This particular device is configured as fr-FR
420,
null,
"2.24.0"
]
Additional context
The referenced lines are "automatically" trying to require modules at runtime, but the loading locales docs indicate that if you're using a Package Manager like JSPM, you can load locales by import "moment/locale/fr
. Since we need the react-native package manager to "know" that the file must be imported, we tried that style of import so that the Packager can "see" all files that have to be bundled in.
Ultimately, our import lines looked like this:
import moment from "moment";
import "moment/min/locales"; // Import all moment-locales -- it's just 400kb
import "moment-timezone";
The exact implementation of require()
is injected by the runtime you're working with, and that's definitely something that behaves significantly differently between Debug & Release builds.
In react-native, there are also several different flavors of Release-mode JavaScript Bundling, including all-in-one-file, all-in-separate-files, and RAM Bundles. Each of these also change how require works. Debug require()
connects to the Metro Bundler running on a local http server. This is probably is very similar to the webpack/jspm/other debug servers, which is probably why aliasing require doesn't cause problems in that environment.
A. Delete the aliasedRequire
entirely if that's not how you're supposed to do things any more + tweak install instructions about it?
B. Detect react-native vs browser (navigator
isn't available in react-native, but there are other techniques here), and behave differently depending on which situation we find ourselves in? eg. if react-native && DEV then print a console.error if the locale is theoretically supported, but hasn't been required
yet (+ update docs).
C. Move the aliasedRequire
from a local variable in that function to a "semi-global". moment.aliasedRequire
, that way we could inject a no-op/do-nothing function so that aliasedRequire
can't cause react-native to crash hard anymore.
I'd be happy to implement any of these options if a maintainer can point me at which one they would like me to implement, and for Proposals B/C help me refine which exact implementation they would be inclined to accept!
@marwahaha -- not sure what the process is for Moment. Would you have an opinion on my fix proposals? I'd be happy to implement a PR once I get some advice on which route might be acceptable to contributors/maintainers?
Most helpful comment
The referenced lines are "automatically" trying to require modules at runtime, but the loading locales docs indicate that if you're using a Package Manager like JSPM, you can load locales by
import "moment/locale/fr
. Since we need the react-native package manager to "know" that the file must be imported, we tried that style of import so that the Packager can "see" all files that have to be bundled in.Ultimately, our import lines looked like this:
The exact implementation of
require()
is injected by the runtime you're working with, and that's definitely something that behaves significantly differently between Debug & Release builds.In react-native, there are also several different flavors of Release-mode JavaScript Bundling, including all-in-one-file, all-in-separate-files, and RAM Bundles. Each of these also change how require works. Debug
require()
connects to the Metro Bundler running on a local http server. This is probably is very similar to the webpack/jspm/other debug servers, which is probably why aliasing require doesn't cause problems in that environment.