Using Core Node JS Modules in React Native Apps

Written by aakashns | Published 2020/04/09
Tech Story Tags: javascript | react | react-native | mobile-app-development | nodejs | application | mobile-apps | mobile

TLDR React Native can’t package core Node JS modules into React Native, so you get a runtime error. This makes core modules like crypto modules like that of the Node JS binary unusable from React Native. Fortunately, there’s a solution to this problem, but it takes some work. There is a hacky but simple way to fix this problem with React Native: create a standalone Javascript file that contains a polyfill for themodule and use it within our app. The last few lines of the file contain our code from the polyfills.via the TL;DR App

Here’s a problem you might run into while using React Native: Let’s say you want to use the
crypto
module to create some hashes. It might seem natural to do something like this:
// Other imports..

const crypto = require('crypto');
const secret = 'open-sesame';
const hash = crypto.createHmac('sha256', secret)
                   .update('abcdefg')
                   .digest('hex');
console.log(hash);

// Other UI code (components, rendering etc..)
Just import a module and create a hash, right? Right? Nope!
But this doesn’t work, because
crypto
is a core Node JS module, which means it’s probably C++ code bundled with the Node JS binary, not Javascript. The React Native packager can’t package it[1] along with your app’s Javascript bundle, so you get a runtime error:
Unable to resolve module 'crypto'
.
This makes core modules like
crypto
,
stream
etc. and the thousands of npm modules that depend on them unusable from React Native. Fortunately, there’s a solution to this problem, but it takes some work.

Solution

If you’re familiar the module bundler Browserify, you might know that it allows you to use core Node JS modules like
crypto
in the browser using polyfills[2]. So let’s try to create a standalone Javascript file that contains a polyfill for the
crypto
module, and use it within our app:
1. First, install
browserify
:
npm install -g browserify
2. Create a file
crypto-in.js
that imports the
crypto
module and simply exports it:
var crypto = require("crypto");
module.exports = crypto;
3. Create a standalone Javascript bundle
crypto.js
using
browserify
:
browserify crypto-in.js -o crypto.js
This creates a file
crypto.js
 , which contains some 21,500 lines of code (containing the polyfills). The last few lines contain our code from
crypto-in.js
 :
// Some 21,500 lines of polyfills..
},{"indexof":100}],153:[function(require,module,exports){
  // Our code from crypto-in.js
  var crypto = require("crypto");
  module.exports = crypto;
},{"crypto":56}]},{},[153]);
At this point, it might seem like we’re done, but we’re not! If you replace the line
const crypto = require('crypto');
in the app code with
const crypto = require('./crypto');
and try to run the app, you get the following error:
Turns out the word
require
is given special treatment by the React Native packager, and you can’t redefine it to mean something else, which is what Browserify tries to do within the bundle. If you look carefully inside
crypto.js
, you will notice that it always passes in a custom
require
function as a parameter and never actually uses the global
require
. There is a hacky but simple way to fix this problem, explained below.
4. Open the file
crypto.js
in a text editor, and replace all instances of the word
require
with something else e.g.
reqqq
. Make sure it’s unique, so it doesn’t conflict with any existing code and mess things up.
If you save
crypto.js
and reload the app now, the previous error will go away, but a new error will appear:
The packager isn’t able to find the method
createHmac
, because
crypto
isn’t exported properly from
crypto.js
. If you look at the last few lines of the bundle, you’ll find that this is because we’re setting
module.exports
not on the global
modules
object, but on something else that’s passed in as a function argument:
// Some 21,500 lines of polyfills..
},{"indexof":100}],153:[function(require,module,exports){
  // Our code from crypto-in.js
  var crypto = require("crypto");
  module.exports = crypto;
},{"crypto":56}]},{},[153]);
5. To export
crypto
properly from
crypto.js
, we need to make a few more changes to the file:
  • Add the statement var crypto; at the top of the file to declare a new variable.
  • Replace the statement
    var crypto = require('crypto');
    with
    crypto = require('crypto');
    to set the outer variable, instead of creating a local variable inside the function body.
  • Move the line
    module.exports = crypto;
    outside the function body to the bottom of the file, so that it references the global
    module
    object.
After the changes,
crypto.js
should look something like this:
var crypto;
// Some 21,500 lines of polyfills..
},{"indexof":100}],153:[function(reqq,module,exports){
  crypto = reqq("crypto");
},{"crypto":56}]},{},[153]);

module.exports = crypto;
And that’s it! If you run the app now, it should all work perfectly:

Summary

Here’s a short summary of the steps it takes to use a core Node JS module like
crypto
(or something that depends on it) in a React Native app:
  1. Create a standalone Javascript bundle containing polyfills for the core modules using browserify (see crypto-in.js and crypto.js).
  2. Replace all instances of the
    require
    keyword inside the bundle with something unique e.g.
    reqqq
    , so that the React Native package manager leaves it alone.
  3. Export the
    crypto
    module correctly from the bundle, by importing it into a global variable, and setting the global
    module.exports
    to that variable (see the modified crypto.js).
Warning: The bundle generated by
browserify
can be rather large in size. For instance, the file
crypto.js
generated above is over 500kb in size. So keep an eye out for large bundles. Consider using a tool like Uglify to minify the bundle and reduce its size.

References

Thanks for reading! I hope you’ve found this article helpful. Feel free to leave comments or suggestions. I would love to know if there is a better way to do this.

Published by HackerNoon on 2020/04/09