Turn Any* JavaScript Library into a React Native Component

Creating a Reusable Component by Wrapping a JS Library in to a React Component with No Native Code

Reginald Johnson
React Native Training
7 min readApr 11, 2018

--

Introduction

This post is a continuation and improvement of the techniques discussed in a previous post. In that article, I described a technique that combined Expo’s ease of development, Webpack’s packaging capability, Gulp’s task automation, and React Native’s WebView component in order to covert an UI oriented JavaScript library into React Native component. An advantage of using this technique is that it avoided any native code; making the component suitable for use with Expo.

While the previous post is not project specific, I did implement the technique in my react-native-webview-quilljs and react-native-webview-leaflet packages.

Problems

While functional, this technique possessed several shortfalls. First, the JSfiles used by the HTML file that was displayed by the WebView were not included in the package bundle due to a limitation of the Expo bundling process. The bundler did not import anything that was included using a <script src="myscripts.js"></script> tag. This limitation prevented the JS and CSS files relied on by the HTML file from being bundled in the Expo application.

While this may not be a problem for a regular NPM package, it prevented me from using the package with an Expo project since the bundler wouldn’t know to include the necessary files. This resulted in multiple errors ranging from the inability to find the JS files to file access denied errors.

The workaround described in the previous post was to download the required HTML, JS, and CSS files from the project’s GitHub repo prior to using them. This required file hosting, meant that the user had to have an Internet connection when the WebView was first used, and obligated filesystem access to write and read the downloaded files.

All this also meant that I needed to include error handling and tracking to prevent an error from occurring if there was no Internet connection. Additionally, I included the Expo filesystem API to read and write to the device filesystem. Unfortunately, this mean that Expo had to be included as an additional dependency when all I wanted was a subset of its impressive functionality.

The Fix

This post describes a technique to avoid those problems by in-lining JS and CSS into the HTML file the WebView loads. This has several advantages:

  1. Full off-line functionality since the files don’t need to be downloaded
  2. A smaller NPM package since Expo no longer needs to be included
  3. The ability to build the package and alongside an application created with Expo that can be used to test the package directly
  4. The package can be used in apps that don’t include Expo as a dependency

It also maintains the primary advantage of using this technique; the ability to wrap a JS library into a React Native component that is suitable for NPM publishing. In addition, this package is usable by Expo since it contains no native code.

Design

I was already using Webpack to bundle my JavaScript and CSS, so why not also use it to inline the JS and CSS?

I ended up adding “inline the JavaScript” to the list of Webpack’s responsibilities which already included creating the HTML pages from a HTML template file, optimizing and bundling my JS and CSS, and placing source links in the resultant HTML files that imported the JS bundles.

Implementation

The above requirement was satisfied by adding the html-webpack-inline-source-pluginplugin to my existing webpack.config.js file. Thehtml-webpack-inline-source-plugin does what its name implies; it places the JS and CSS source inline with the HTML. This way, if the WebView is able to load the HTML files, then it is guaranteed to be able to load the JS and CSS files.

Thanks to Webpack, I was able to create HTML files containing a nicely bundled chunk of JS and CSS. This file was now ready to use as a React Native WebView’s source. Webpack placed this file in the project’s build directory. I then used Gulp to copy that file from the build to the assets/dist directory. I ended up using Gulp because I couldn’t find a Webpack plugin that would delay copying until after packaging was complete.

Test and Evaluation

I tested and validated the file by getting the updated react-native-webview-quilljs package working in three different scenarios:

  1. Development mode in the Expo application that was built to test, and demonstrate the package.
  2. In the Expo published version of the demonstration application
  3. Imported as an NPM package into a completely separate application

The first problem I encountered was at step one. Ensuring that the Webview was accessing the correct file wasn’t trivial. Although the correct location for my file was the project’s asset/dist folder. The logging excerpt from the Expo SDK shows that the program follows the assets/assets/dist path to find the file

Notice the file path is “assets/assets/dist”
Even though the desired file in in an “assets/dist” directory
Just import it from the actual folder location

While somewhat disorienting, this did not prevent the project from working. However, ensuring the correct file path will be my first debugging starting point if any issues pop up.

A technique to ensure that the correct HTML file is being found and loaded is to include some inconsequential text in the HTML. That way the file’s presence can be confirmed even if an error prevents the JS from loading. If the text is visible, then the page is loading, otherwise there is a problem preventing the page from being loaded.

The “html” text confirms that the two WebViews were able to located and load the HTML files

Additional problems manifested once the WebView did display the content. For example, the text was too small on iOS devices. This was a relatively easy fix by adding scalesPageToFit={false} to the props of the WebView. Also, loading HTTP content from another source (for example, the map tiles required by Leaflet) may require setting the WebView prop mixedContentMode={‘always’}

Jumping to step two turned into an issue due to the difference in how file imports/requires are handled in Expo applications during development and and in production. This difference is described here in a section of Expo documentation that is aptly titled “Where assets live”. Bottom line is that the HTML files were not available to the production application because they were not bundled.

Fortunately, Expo forums user Jesse pointed me towards the following code that, when added to the app.json file, ensures HTML files are bundled for production:

"packagerOpts": {
"assetExts": ["html"]
},

Another problem that may occur at this step is related to file access and can manifest itself with the following error:

File Access Error due to incorrect file location

This happened because the WebView attempted to access a file that wasn’t available, or was not in the expected location. Once again, confirm the HTML file’s path is as expected, and the import statement importing the file is looking in the correct place.

Finally; for step three, I imported the published package into another Expo application, and verified that the WebView did actually render the content as expected.

Publishing

I wanted to publish to NPM directly from the Expo test application’s project directory. To do this, I created a Gulp script that updated the dependencies and index in package.json to be suitable for NPM publishing. The script also bumped the version value, and performed my git operations. Finally, it reverted the dependencies and index to make the project suitable for use as an Expo application again.

Summary

In summary, use the html-webpack-inline-source-plugin Webpack plugin to place JS and CSS source files inline with the HTML file. Additionally, use Gulp to automate publishing and git operations. Also, monitor the Expo XDE log to ensure the Webview is looking for the HTML file in the proper location. Finally, update your packagerOpts in app.json to include your HTML file in the published package bundle.

Conclusion

Including the JS and CSS in my main HTML file worked. It allowed me to create React Native components wrapping Quill and Leaflet and publish them to NPM for successful use in a completely separate project.

The biggest downside is that the WebView component doesn’t load as fast as a native component. This is logical since the JS has to be executed inside the browser that is part of the WebView vice being recompiled in Swift of Java. It would be nice if there was a way to load and run the WebView, possible up to just prior to component rendering, and then cache the results; with the goal of doing any JS heavy lifting prior to displaying the component.

As for the asterisk in the title, this technique should be pretty flexible. However, I’m sure there are caveats that will prevent it from being a universal solution. And, while I wouldn't bet that this technique allows every JS library to be turned into a React Native component, it did satisfy my requirement for these two libraries in particular, and will be a go-to technique in the future.

Reginald Johnson is a 20+ year military veteran who is blogging, creating, and teaching his way through his transition into software development. You can follow him on Twitter at @reginald3, or connect with him on LinkedIn at https://www.linkedin.com/in/reggie3/

--

--

Reginald Johnson
React Native Training

Freelance software developer and 21 year military vet. I build for the web, mobile, and server with React, React Native, and AWS & node. https://www.reggie3.com