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));