Skip to content

Instantly share code, notes, and snippets.

@alanbsmith
Created Jan 15, 2018
Embed
What would you like to do?

Module Development Workflow

a mini-tutorial for JavaScript January 🎉

Overview

This is a suggested workflow for building npm modules locally. We'll be building an input validation utility, but the principles can be applied to any library. Input validation is a common challenge for UI developers, and it's important to handle it consistently. This makes it a really good candidate for an npm module. So let's build a small library!

ASSUMPTIONS & EXPECTATIONS

This tutorial is designed to walk you through a good workflow as you develop an npm module. The example is fairly simple, but there are lots of good principles, practices, and tooling built-in. Those are really what I want you to take away from this exercise. I'm assuming you've never built an npm module before, but you'll need to be fairly proficient with JavaScript, familiar with npm, and comfortable using the command line to get the most out of this tutorial. I'm also assuming you're familiar with git and GitHub. However, if you don't want to code-along, you can certainly review the snippets in the article. Our playground app for integrating this library is a React app, and it would be helpful to have some knowledge of React. That said, our library isn't dependent on React, and you won't really need any React-specific knowledge to complete this. I'll use Yarn for my examples here, but you can certainly use npm if you'd prefer.

Many of the tools and tricks I'll show you do the same thing: automate manual effort. This allows you to focus on building a useful library instead of fumbling around managing configuration and processes.

Up & Running

I created a starter repo on Github to help with the setup and configuration. Go ahead, pull it down and install the dependencies:

https://gist.github.com/93a33e5996af15962a1d785404e350d3

Cool. Now let's take a look at some of our scripts in package.json:

https://gist.github.com/174add206206d54ddad3b79c42b68d9f

We're using Babel to transpile our code and put it in a new directory, /build, which is what will be pushed up to npm. This will allow us to use modern JS syntax in development, while ensuring our code will work in any browser. We could use Rollup to accomplish the same goal, but for this small library, Babel will be sufficient.

NOTE: If you're not familiar with the word "transpile", it's a mashup of transform + compile. You can mostly equate it with “compile” and not worry too much about the details. If you want to learn more though, there's a great article here.

Our prepublishOnly script automatically runs build anytime we run npm publish, which is really handy. We don't have to worry about whether the /build directory is up-to-date before we publish a new version of our library.

Adding a Phone Number Validator

Now let's add a validatePhone function to our library:

https://gist.github.com/f139313fea655bdfd36a8f264699372f

First, we'll add some tests. We're using Jest as our test runner. Jest is normally associated with React testing, but there are also a lot of helpful utils for testing outside of a React-specific context. For brevity, I'll list the tests below, but Jest's jest --watch script is really handy for test-driven development.

https://gist.github.com/2bd52b24b7a4d3d531d1830d15badfec

Now we'll add our validation function:

https://gist.github.com/03dc52bd82b77a47b0ad8171fc256146

Now when you run your tests (yarn test), you should see them pass as expected. Great! Tests give us a lot of confidence that our code is working as expected, and that we aren't pushing up broken code to npm. You probably also noticed that Jest gave us some detailed feedback on our test coverage as well:

https://gist.github.com/3e3354840dc4b091a43931a74ad61223

This helps us know that we haven't missed anything in our tests. You can find the configuration for this in the jest section of package.json. This may seem overkill for our tiny library, but as your modules grow, this tooling becomes invaluable to your workflow.

While our tests help us know our validator function works as expected, we also want to know if our library works properly before we publish. It would be great if we could test this locally in an actual input field. 🤔 Well, our friends at npm gave us a handy script, link, to do just that! But before we go any further, we need to make sure we properly export validatePhone so it will be included in our library. To do that, update lib/index.js to look like this:

https://gist.github.com/30e5a0c7efe90e3d05a79a06618cdd1c

Awesome! Now when the build script runs, Babel will transpile validatePhone and make it available for our users.

Using link

To test our new library, we'll create a separate playground application. I set up a small React app for you here. Let's pull it down from GitHub and install our dependencies:

NOTE: This playground app will live outside of our library. It doesn't matter where you pull it down, but be sure to hop out of the library before cloning.

https://gist.github.com/024804cbcac6b7616273d734cafd2a8e

You can fire up the playground by running yarn dev and opening your browser to http://localhost:8080/. The UI is pretty sparse. You should see a label, an input field, and a submit button.

playground app screenshot

Let's take a look at the code:

https://gist.github.com/205a9bd497ef38419882a300a9ecac86

If you're not familiar with React, that's okay. All you need to know about this is that we have a handler function, handleChange that's called any time the value of the input field changes. handleChange then updates the component's state based on the new value the user entered. We're going to use handleChange to also validate our input. But to do that we'll need to link our new module to this app. We can do that by hopping over to the root of our input-validator-lib directory and running yarn link.

NOTE: We'll be hopping back and forth between the library and playground app, and we'll be compiling them concurrently. So it would helpful for you to have two terminal panes or windows open for the rest of this tutorial.

https://gist.github.com/44d19efd4b2a59f8a203dfdfea643510

So, what did we just do? link creates a global symlinked copy of this module locally on your machine. That means when we make a change to our library, the global module is automatically updated!

NOTE: You can also use npm link. They effectively do the same thing, but they store the global module in a different location. This will be important when you want to unlink later.

Great! Now we can link this module to our app! Running yarn link input-validator-lib will add our library to our playground's dependencies.

https://gist.github.com/c85369e1c9208209756220e13e337303

NOTE: If input-validator-lib was already installed in input-validator-playground, this local module will replace the preexisting copy. You'll need to run yarn install to reinstall the original external module when you're finished.

Cool! Now we can import our validator function into our playground to see how it works:

https://gist.github.com/2617f9c12f52e9a804db3ae73c52225c

As you can see above, we are able to import and use this module just like we would any other external dependency. That's pretty cool! We'll pass the phoneNumber input value to validatePhone, and let it set the state of inputError. We can use this validation to give helpful feedback and disable the submit button until the input is valid.

proper input validation

Everything is looking great, and our module is working as expected. But I just noticed that we missed an edge case. What if the user enters a country code (e.g +1(202) 224-3121)? That would break our current validation.

NOTE: For this simple example, we're only validating the US country code (1). If you're not from the US, feel free to adapt this to use your country code if you'd like.

country code not supported

We should update our module to support that.

Updating Our Module

We'll add a simple if statement in our function to catch this:

https://gist.github.com/a40594f2416ea1f104307642d63f7999

We'll also add a test to validate it works as expected:

https://gist.github.com/1ff00ca0a525d37d2fc879cc6ddcc2ed

Easy enough, right? Except when we hop back to our playground; we're still seeing the same error. 🤔 Hmm, why is that? Remember how I said the module is globally symlinked and updates automatically? Well, that's mostly true. The module does update, but Babel does not auto-update the /build directory when changes are made. That means we need to run our build script every time we make a change. That's a pretty annoying workflow. Thankfully, I added a little script to help with that, yarn build:watch. 🎉

When you run yarn build:watch, Babel will watch for changes in /lib and update /build automatically. And because our module is symlinked to the playground app, it updates automatically as well! That's pretty cool!

country code supported

Because we're running yarn dev in our playground app, we should see it hot-reload to reflect the changes we made in our library. This streamlines our workflow significantly. We can add more input validations for email addresses, street addresses, credit card numbers, etc. and test them live in a separate application.

Using unlink

Once we're done testing locally, it's a good idea to clean up our dependencies. To remove the library from the playground's dependencies, run yarn unlink input-validator-lib in input-validator-playground. Also, remember hop back to input-validator-lib and remove the symlink as well:

https://gist.github.com/625c2999a3e6da067a6e24bbff2c1d57

This will keep you from having any unexpected side-effects down the road.

Thanks for Reading!

I hope you enjoyed this tutorial! Module development has been a huge win for our team's productivity, and this workflow has been really helpful for us. I hope you and your team find it beneficial as well.

You can find me on Twitter and GitHub. If you enjoyed this, you should also subscribe to my newsletter! https://tinyletter.com/alanbsmith.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment