Contents

Building a Yarn Project With Nix Flake

Contents

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