projectsdrawing of a fox, shaded in rainbow accentsgallery

Optimizing JS for Nix

In the Before Times, I would just throw my code somewhere on a server, edit some config in the index.js, add a systemd unit (or start screen if I was really in a hurry) and be done with it.
But everything changed when the fire nation attacked I started switching everything to NixOS. The old approach still 'worked', but Nix had so much more to offer.

Along the way I've accrued a set of changes to my software that makes the whole packaging/running easier.

Packaging

I pass my name, version and source attributes to a simple function that configures mkYarnPackage:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # package.nix common.node-application rec { pname = "woei"; version = "v0.0.1"; source = fetchgit { url = "https://git.pixie.town/f0x/${pname}"; rev = "0fe28e90919929a88b2a35ed04aff00c395222fa"; # sha256 = lib.fakeSha256; sha256 = "sha256-ohpjQIK9ZaZNrizacckTEN9CEdIIgOoB0pS1ijP6ILE="; }; } # node-application.nix { pname, version, source }: mkYarnPackage { inherit pname; inherit version; src = source; packageJSON = "${source}/package.json"; yarnLock = "${source}/yarn.lock"; }

This will take care of fetching the dependencies, but the files are kinda hidden in $out/libexec/$package/deps/$package. You have to use that complex path and pass it to pkgs.nodejs, not ideal.

It gets a lot easier with some small changes to the codebase:
add to package.json:

1 2 3 "bin": { "woei": "./index.js" },

index.js first line:

1 #!/usr/bin/env node

chmod +x index.js
Nix will then automatically convert that shebang into whatever store path node is in, and now you can just run $out/bin/woei.

Configuration

1 2 3 const config = { ... };

Just hardcoding the config might be easy at first, but makes it real hard to configure from Nix, and tends to cause git merge conflicts. Multiple approaches:

Yargs

Throw in a command-line argument parser, in my case yargs, and use that to pass some config variables (as set in the Nix'd systemd unit ExecStart=).

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const yargs = require("yargs"); const args = yargs .option("port", { description: "listening port", default: 8080 }) .option("host", { description: "listening host", default: "localhost" }) .help() .alias('help', 'h') .argv;

This works best when there's only a few options, and yargs does a decent job of parsing + doing stuff like generating a help overview. When there's too many options though that gets bothersome, and it's a lot nicer to have Nix generate the config as JSON, and let the software read from there instead.

1 2 3 4 5 6 7 8 9 10 11 12 13 const yargs = require("yargs"); const path = require("path"); const args = yargs .option("config", { description: "config file (json)", default: __dirname + "/config.json" }) .help() .alias('help', 'h') .argv; const config = require(path.resolve(__dirname, args.config)); // automatically parses JSON

process.argv

Less robust, but maybe a little simpler:

1 2 3 4 #!/usr/bin/env node const configFile = process.argv[2]; // node file.js argument1 const config = require(path.resolve(__dirname, configFile));