Creating an NPM package for a component

The other day we faced a minor pain point in React Native, which I decided to turn into the experiment of creating an NPM package. This is a high level overview of various steps I took through the process.

The problem was displaying readable JS objects on an actual phone with a non-development build. On dev environments it’s typically easy to just hook into tools such as the built in React Native debugging, the standalone React Native Debugger, or a networking tool like Charles. However, there was no easy way to inspect an object within the app itself. Hence I created a new React Native component to accomplish this.

The DrillableObjectView

The result

I matched the look/colors of the Chrome object inspector that we’re all familiar with. I won’t go into too much detail about the DrillableObjectView itself, but it is a recursive component that renders sub DrillableObjectViews based on whether or not the parent object is in an open state. It’s basically a variation on the age old phone interview question to implement JSON.stringify or recursively flatten an array.

Initially I built the component locally in our main app. However, I concluded that there was a chance that it might be useful to others and decided to pull it out into it’s own NPM package.

The package.json

You’ll find a package.json in any module. All of the important info about the package is stored here. I often find myself looking at the package.json of other modules if I start to run into dependency issues. Running npm init will get you started in setting up a package.json file.

The entry point into the package is derived from the main field. So you could just point it to index.js, or wherever your starting point resides. In my package, I’m pointing it to src/index.js, which means that if I import react-native-drillable-object-view in another app like such:

import SomeName from 'react-native-drillable-object-view';

SomeName will be set to whatever was exported in src/index.js.

Another crucial field is the version field. I’d suggest reading up on semantic versioning. Versioning is important to any consumers of your module, so keep a close eye on how you’re bumping publishing versions if you want to avoid breaking your consumers.

Dependencies

Dependency management is crucial if other apps are going to use your package. I’ve seen numerous issues arise in the past as a result of a lack of attention to dependencies. You’ll notice that I have 3 types in my package.json: dependencies, peerDependencies, and devDependencies.

Normal dependencies are what are required for your code to run. peerDependencies are also required, but are expected requirements of the consumer of your package.

For example, I have react-native listed as a peerDependency in react-native-drillable-object-view. I need react-native to run my code. However, the DrillableObjectView is never intended to be run on its own. It’s always intended to be imported by an external React Native app and run there. It’s safe to say that any app that imports my package will already have react-native, hence I’m listing it as a peerDependency. If I were to list it as a normal dependency then using my package would result in two versions of react-native being installed in the consumer’s project. This is obviously unnecessary.

lodash is an example of a normal dependency. Although the consuming app may already be using lodash, we can’t be certain that they are. Therefore, we want to require it in dependencies.

devDependencies are more straightforward to understand. If you intend to develop and work on the package, these are all of the things that you’ll need. You can see that I have many more devDependencies. They include things like eslint and jest. When developing we want things like linters and tests which are only useful during development. No consumer of my package would likely run want to run eslint or jest on my package. Hence, there is no need for them to be anything but a devDependency.

Setting up hooks

For my own sake, and also the scenario that anybody else is working on the package tools such as linters and tests are extremely useful to keep the code clean and make sure things don’t break.

eslint is easy to setup and get running. I even comes with a eslint --init command that sets up your .eslintrc config file for you where you can specify certain rules etc.

It’s pretty easy to run manually. However, I wanted to be a bit more restrictive and run it as a prepush hook before a branch would be pushed to github. Luckily husky is easy to get running. It allows you to add precommit and prepush scripts to the scripts field in your package.json, so I just added the linter to that prepush script and we were good to go.

Tests

Although a linter prevents quite a few errors, I decided to use this opportunity to explore jest and snapshot testing a bit. I found this article to be quite useful. Setting up jest was a bit more complicated than lint as there are a number of dependencies to install as well as a few options and things to setup with babel.

One interesting note is the snapshotSerializers option. A “snapshot” is essentially the DOM tree captured in text form. If you know a component is working properly, it allows you to take a snapshot of the rendered component and compare against a previously snapshotted component. It’s almost like a form of visual testing.

If a property like the color or margin changes, the test will fail, highlighting those differences. Oftentimes the change may be intended, but that’s up to the developer to determine and take a new “snapshot.” Definitely a different but interesting approach to most testing I’ve done in the past where I’ve taken more of a TDD approach. In this style, built it then take a snapshot when it’s working.

The tests themselves were easy to write because you’re letting the snapshot do most of the work. Once I had a few of them for various scenarios, I added running jest to my prepush hook as well.

Publishing

Once your package is ready, publishing is fairly easy with npm publish. Just don’t forget to bump your package version in package.json! You should be able to find your package on npm like such: https://www.npmjs.com/package/react-native-drillable-object-view

I’ve covered a lot a high level content in this post and you should definitely dig into the docs more where necessary. Also feel free to use or contribute to the DrillableObjectView.