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
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
.