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.
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.
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
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
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.
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
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
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
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
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
jest on my package. Hence, there is no need for them to be anything but a
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.
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
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.
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.
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