Fixing the React Native Compiler for ClojureScript

Published 2018-10-16

If you've tried to bundle any medium size ClojureScript program with React Native, you've probably run into exponential build time problems, which can push a significant delay in your schedule if you aren't prepared for it.

Problem Statement

React native's compiler, Metro, is not designed to handle the large .js blobs that the ClojureScript compiler can put out. While it tends to handle .js file sizes fine under 1MB, including most libraries you'll come across, once your app grows beyond a certain size, your React Native bundler will start to time out as it tries to parse the entire AST for your already-compiled Javascript code into a form that Metro can re-manipulate.

Finding a Solution

Austin Birch described the problem originally here on his blog, and provided a hack to work-around it. However we found that this solution was incompatible with the latest Metro release, and had problems with dependency management and require() calls. After much hacking around, we had to take a look inside the Metro Javascript compiler for React Native in order to understand how it works, and design a more reliable solution for transpiling ClojureScript to React Native.

CLJS Metro Transformer

Here's the code you'll need to FIX your React Native timeouts with ClojureScript builds. Not only will it fix the timeout issues, but you'll appreciate the much faster build times you get from skipping the recompilation of the .cljs output.

  1. Copy this file into your project, and name it 'transformer.js'

2. Configure Metro to use this transformation in a file named 'metro.config.js' at the top of your React Native project:

module.exports = { transformerPath: require.resolve('./transformer.js') }

3. Finally, write a third file, 'cljs-deps.js'. In this file, put all the require() statements that your Clojurescript code will need for it's dependencies. We're planning to write a plugin (or build it directly into re-natal), but for now, you'll have to supply this file by hand.

var modules={ 'react-native': require('react-native'), 'react': require('react'), 'create-react-class': require('create-react-class') }; modules['react-dom'] = require('react-dom'); // require() everything your Clojurescript needs

The way this works, is that instead of reading  your (large) compiled Clojurescript output, now Metro will only read your tiny little cljs-deps.js file instead, and parse that for dependencies. Then the transformer will shim those dependencies into a format that your Clojurescript can load, and voilia! You're back in business writing ClojureScript apps.

This solution was tested using react-native 0.57.0, and metro 0.45.4. If you have any questions or issues, leave them in the comments below!

If you are interested in hiring help with your Clojurescript project, send us a message!

Discussion