As of version 0.70.0, Meta’s Hermes became the default JavaScript engine for React Native. While JSC is still supported, the React ecosystem moves fast, and what is the default now can quickly become the only supported option.

The Problem

ClojureScript depends on Google Closure as both a standard library and a compiler. Hermes does not yet support ES6 classes, which are used in several places in the Closure library. The Closure compiler does support transpiling to ES5, but without advanced optimizations, there is a small bug.

When Closure converts an ES6 class to a simple var, it uses the file location to build a JS name (e.g. my$directory$src$index). For files in JARs (which is how ClojureScript bundles up google-closure-library, for example), that filename will have a ! in it (e.g. bundle.jar!src/index.js). Since Closure does not specifically replace the !, it results in a var name that illegally contains the ! character, and therefore unparseable JS.

The Immediate Solution

The easiest thing to do is to just find/replace these illegal var names before they make it into a JS interpreter. When you start up a ClojureScript REPL with Krell, it will build all of the Closure JS into target/ first, so I have script that scours that directory for broken JS and fixes it:

#!/bin/sh

find ./target/goog -type f -name "*.js" -exec sed -i '' 's/\!\$/_$/g' {} +

Then I just run that script when I yarn start to start the Metro server that provides JS in the dev environment:

{
  "start": "sh ./scripts/fixClosure.sh && react-native start",
}

The Permanent Solution

Not generating illegal JS from Closure in the first place would of course be preferable. I submitted a small PR to the Closure project that fixes the behavior. That fix was accepted, so on the next release of Closure and then the next release of ClojureScript, ES5 output should work without hacks.