Tag Archives: Git

Building your web app on the fly with Heroku

We all know that Git is the only way to deploy apps to Heroku, right? (Nope!) But the thing is, as good Git users, we don’t want to add our compiled stuff to source control. But our apps won’t work without them existing on the deployed app, right? That’s why we’ll want to build the stuff on the fly in Heroku’s servers themselves with an npm postinstall script. In fact, Heroku recommends this in their node.js support and best practices articles.

But how do we get about doing this?

Creating a build pipeline

First, we need a functioning build pipeline. We might have this setup, which copies Bootstrap’s CSS files to a certain folder, for example:

// package.json
{
  "scripts": {
    "copy": "cp -r node_modules/bootstrap/dist/css app
  }
  ...
}

This requires that we have bootstrap as a devDependency:

// package.json
{
  "devDependencies": {
    "bootstrap": "4.0.0-alpha.2"
  }
  ...
}

But then, these dev dependencies won’t get installed when they get deployed to Heroku. Now we have a dilemma. We can’t build these on Heroku since they’re dev dependencies. But they are dev dependencies, we don’t need them in our backend. So should we make them dependencies?

What should we do now? One option is, definitely, to make bootstrap a non-dev dependency. That way we can make sure the bootstrap package is installed when our app is deployed to Heroku.

The answer I came to after sitting under a tree to attain Heroku/node.js nirvana is to make a build script to install these build dependencies on Heroku. And also introduce the concept of build dependencies.

And oh, there’s no tree.

Build dependencies

build dependency
A dev dependency that is used to build an app.

So before building, we should call this script to first install the build dependencies we need. The script installs the build dependencies we have. But first, we need to list what our build dependencies are. To do that, we can use ye olde package.json. We can do it in the same format as the standard format for dependencies and devDependencies but since I explicitly defined build dependencies as a subset of dev dependencies, we could just specify our build dependencies as an array.

// package.json
{
  "buildDependencies": [
    "bootstrap"
  ]
  ...
}

Now my script goes something like:

// install-build-dependencies.js
'use strict'

const pkg = require('./package')
const ChildProcess = require('child_process')

let bd = pkg.buildDependencies
bd = bd.map(dep => `${dep}@${pkg.devDependencies[dep]}`)
bd = bd.join(' ')

try {
  ChildProcess.execSync(`npm install ${bd}`)
}
catch (err) {
  console.error(err.message)
}

Putting it all together

Now we’re almost done. Do I still need to do this? Presumably, you’re already a Heroku dev.

// is-heroku.js
process.exit(isHeroku ? 0 : 1)

// package.json
{
  "scripts": {
    "postinstall": "node is-heroku && npm run heroku || exit 0",
    "heroku": "npm run install-build-dependencies && npm run build", // depending on your setup, you might have some other stuff here. I know I do.
    "build": "npm run copy",
    "install-build-dependencies": "node install-build-dependencies.js"
    ...
  }
  ...
}

Reducing your slug size

Now you’re using this flow and you might wonder: I don’t really need these build dependencies while the app is running, right? What we can do is actually uninstall them after building. Doing so might significantly reduce your slug size. It’s the same script as above, just replace install with uninstall. Here’s the updated heroku script:

// package.json
{
  "scripts": {
    "heroku": "npm run install-build-dependencies && npm run build && npm run uninstall-build-dependencies"
    ...
  }
  ...
}

Runtime dependencies (bonus!)

Maybe you have some “dependencies” that aren’t actually required dependencies. Maybe things like forever. Or maybe you’re just like me that likes using babel-cli on production code without caring about compiling them. I would like to define these runtime dependencies.

runtime dependency
A dev dependency that is required to run the app when deployed.

At this point, you should really know what to do with these now. Just do some copypasta on the things above. Only, obviously we shouldn’t really uninstall them after installing them.

Let’s finish this post

And that’s it! Hopefully you’ll gain enlightenment on some of the things I’ve shared on this post. This is the flow that I use myself on my latest project on Heroku. Which I totally forgot to share here. Anyway, thanks for reading, and I hope you’ll find Heroku/node.js development fun too.