Building a Yarn Project With Nix Flake
Nix is the weirdest software I have worked with. After about a year of playing with it, I am still unsure if I should continue with my experimentations. Nonetheless, if you are interested to build a yarn project with nix, I will explain my solution.
To improve the readability of this post, there is an example project (yarn-nix-example) that demonstrates the idea.
yarn-nix-example is a basic node/yarn project that builds a static website
using parcel. The root directory contains a package.json
and a yarn.lock
file. The traditional way to build the project is to use yarn install
to
fetch all the dependencies and create a node_modules
directory. Then
yarn build
(defined in package.json
) delegates to parcel the
compilation of the typescript assets into a standalone javascript file.
stateDiagram-v2 compiled_assets: compiled assets source --> node_modules: yarn node_modules --> compiled_assets: parcel
Nix will define the same two steps; the first builds the node_modules
and the second builds the compiled asset. The big difference is the
usage of yarn2nix to build the node_modules
.
stateDiagram-v2 compiled_assets: compiled assets source --> node_modules: yarn2nix node_modules --> compiled_assets: parcel
Building the node_modules
is not very hard. Just use
mkYarnPackage
with name
and src
parameters. The default arguments should be good enough for the other
parameters. mkYarnPackage
will create a directory where the node_modules
directory will be located at libexec/{package-name}/node_modules
. Below
is an example of what it looks like. Use nix build .#node-modules
to view
the results.
{
description = "Example of using yarn with nix";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
node-modules = pkgs.mkYarnPackage {
name = "node-modules";
src = ./.;
};
in
{
packages = {
node-modules = node-modules;
};
}
);
}
The last step is to build compiled assets via yarn build
. This will be done
via a “classical” mkDerivation. We can do this because parcel’s
does not need to fetch any resources over the network. All the resource fetching
should have been done in the previous step. The step’s logic will be to symlink the
previously built node_module
and call yarn build
.
pkgs.stdenv.mkDerivation {
name = "frontend";
src = ./.;
buildInputs = [pkgs.yarn node-modules];
buildPhase = ''
ln -s ${node-modules}/libexec/yarn-nix-example/node_modules node_modules
${pkgs.yarn}/bin/yarn build
'';
installPhase = ''
mkdir $out
mv dist $out/lib
'';
}
Finally, the complete flake.nix is relatively small. The result
is that anyone (with nix flakes installed) can build the project using
nix build gitlab:/all-dressed-programming/yarn-nix-example
.
{
description = "Example of a project that integrates nix flake with yarn.";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
node-modules = pkgs.mkYarnPackage {
name = "node-modules";
src = ./.;
};
frontend = pkgs.stdenv.mkDerivation {
name = "frontend";
src = ./.;
buildInputs = [pkgs.yarn node-modules];
buildPhase = ''
ln -s ${node-modules}/libexec/yarn-nix-example/node_modules node_modules
${pkgs.yarn}/bin/yarn build
'';
installPhase = ''
mkdir $out
mv dist $out/lib
'';
};
in
{
packages = {
node-modules = node-modules;
default = frontend;
};
}
);
}