How to Test Your NPM Module in Multiple Environments
By: Nikki Moore
Tags: Npm Package, Heroku, Deployment, NPM, React

Deploying to a Staging Environment

Now that local development is working nicely, we need to do a few new things in order to get the example app to install the package correctly once it’s been deployed.

I use Heroku to host my example app since the setup is easy and you can enable automatic deploys from a GitHub branch.

1. Pointing to the right package to install

You won’t be able to use a relative path to install the npm package once the example app is deployed. So we will need to pack the module into a tarball file and save that in your repo, then find a way to point to that tarball file for the staging environment only.

First create a script that will pack your npm module into the example folder in your package.json:

// package.json (main dir)

... "scripts": {

"packexample": "cd example && npm pack .."

}

I set up a pre-commit hook to run this script so every commit will have an updated version of the tarball that can be used to install the package in the example app.

Make sure husky is installed in your main directory as a dev dependency. Then you will want to set up a pre-commit hook that runs “packexample” then immediately performs a “git add” on the new zipped tarball file before committing:

// package.json (main dir)

... “husky”: {

“hooks”: {

“pre-commit”: “npm run packexample && git add example/my-package-0.0.0.tgz"

}

},

Note: I highly recommend using Semantic Release to handle all versioning. This also allows the above file name to always be the same (v0.0.0). Take a look at this awesome article to help you get started:

Note: If you don’t want to set up Semantic Release, you will need to make a script to rename the file to something that stays consistent. Unfortunately “npm pack” doesn’t offer the ability to directly specify the output file name.

Ok so now that you have the tarball packed into the example folder for every commit, you will need to tell your package.json to use the tarball file for the staging environment, but keep using the relative package for local use. You can do this with penv.

Basically it changes your package.json to use specific parameters for different environments with an environments.json file:

my-app

├── ...

├── node_modules

├── example

│ ├── ...

│ ├── node_modules

│ ├── src

│ ├── environments.json

│ └── package.json

├── src

└── package.json

For the dev environment, you want to use the relative path, and for the staging environment you want to use the tarball:

// environments.json {

"staging": {

"dependencies": {

"my-package": "file:my-package-0.0.0.tgz"

}

},

"dev": {

"dependencies": {

"my-package": "file:../"

}

}

}

Now that the different environments have been set, we want to figure out how to execute penv before any packages are installed in the staging environment. To do this we can add a preinstall script. We also want to make sure that penv is executed before “npm start”:

"scripts": {

"preinstall": "npx penv $NODE_ENV",

"start": "npx penv $NODE_ENV && SKIP_PREFLIGHT_CHECK=true react-scripts start",

}

Now when your example app is built, penv will update the package.json first depending on the current env. You can also add a production env, but I decided that wasn’t necessary in my case.

2. Deploying a subdirectory

Heroku expects your main directory to be the target directory and is not designed to build an app in a subdirectory out of the box. So we need to use a custom buildpack to do this.

First, add both of these buildpacks to your Heroku project in the following order:

1. https://github.com/sjtrny/subdir-depend-heroku-buildpack.git

2. https://github.com/mars/create-react-app-buildpack.git

You must also specify the subdirectory that your app is in as an env variable called PROJECT_PATH:

And make sure you also specify the correct NODE_ENV so that penv works properly.

One more thing!! The way this buildpack works is it removes everything except for your project subdirectory, then moves it all into the main directory.

To make sure all the needed files are moved into the main directory, you will need to create a MANIFEST_GLOBAL file and a MANIFEST_MV_TO_ROOT file.

my-app

├── ...

├── node_modules

├── example

│ ├── ...

│ ├── node_modules

│ ├── src

│ ├── environments.json

│ ├── MANIFEST_GLOBAL

│ ├── MANIFEST_MV_TO_ROOT

│ └── package.json

├── src

└── package.json

The MANIFEST_GLOBAL contains folders and files you want to keep from the main directory

// MANIFEST_GLOBAL example/

The MANIFEST_MV_TO_ROOT contains the folders and files you want to keep from the subdirectory. Make sure you include at least the following files in here:

// MANIFEST_MV_TO_ROOT example/public/

example/src/

example/package.json

example/environments.json

example/my-package-0.0.0.tgz

Now all you need to do is enable automatic deploys from the branch of your choice, then any push to that branch will kick off a deployment!