Exposing Environment Variables to Static Spine.js Apps

If you're using Spine.app to build your static Spine.js app, you might run into the same problem that we did: there was some configuration (such as the host of our API) that needed to change from environment to environment. I wanted to retain the completely static compilation process that Spine affords us without doing anything terribly hacky like client-side host-based conditionals.

Luckily, Spine.js apps are compiled using hem and hem has a (not so well documented) way to write custom compilers! I wanted to be able to selectively expose environment variables (which are very easy to set on Heroku) and also supply sensible defaults. So I decided that I would create a .env compiler that would parse a JSON hash as default values and override it with any available environment variables.

All you have to do is put this into slug.js in your project's root:

var hem = new (require(hem));
var fs   = require(fs);
var argv = process.argv.slice(2);

hem.compilers.env = function(path) {
  var content = fs.readFileSync(path, utf8);
  var envHash = JSON.parse(content);

  for (key in envHash) {
    if (process.env[key]) {
      envHash[key] = process.env[key];
    }
  }

  return "module.exports = " + JSON.stringify(envHash);
};

hem.exec(argv[0]);

This simple script declares a compiler, then reads and parses a JSON file from disk, replacing any declared keys with any environment variables that are present. Now I can define a file (say app/environment.env) that looks like this:

{"API_HOST":"https://api.divshot.com","RUNTIME":"web"}

And I'm able to access and/or override those variables per-environment. For instance, in local development I can run:

API_HOST=http://localhost:8080 hem server

Or even better, if I'm compiling my app for offline use as a packaged app, I can pass a flag:

RUNTIME=chrome hem compile

All that is required to access these variables is to require the environment file like you would any other:

window.env = require("environment")
console.log(env.API_HOST)

This is a simple hack that provides some powerful configurability. If you're using Spine.app, it might just come in handy!

Bonus: Making it work on Heroku

When I did this I did it with the target of deploying to Heroku using our Spine Heroku buildpack. When I deployed to Heroku...it didn't work! That's because by default Heroku doesn't expose config variables to the build process.

Luckily, there is a Heroku labs feature to expose config variables during the build process. Just run:

heroku labs:add user-env-compile

Now when the buildpack compiles your assets, it will detect and assign environment variables appropriately. We think this is a fantastic way to deploy static HTML with not-quite-static results.