Need help with buildNpmPackage

Imagine you have a NodeJS monorepository, and the structure looks like this:

flake.nix
─ frontend
── package.json
── package-lock.json
─ common
── package.json
── package-lock.json

And now you want to build the frontend project using buildNpmPackage.

That alone is very trivial in the flake.nix:

pkgs.buildNpmPackage {
  name = "frontend";
  src = ./frontend;

  npmDepsHash = "sha256-...";

  installPhase = ''
    mkdir $out
    cp -r build/* $out
  '';
}

But where I had trouble for a few hours now is, how would I do that when frontend is dependant on the common directory, since it uses file:../common in its package.json to import some shared utility functions, etc. from there.

We now somehow have to include the common directory in the build, so there are no errors when transpiling the frontend code (since it imports variables and functions from common).

The first thing that came to mind for me was to just use ./. for the src attribute, since then I would have access to both frontend and common in the build sandbox.

The problem with that however is, that buildNpmPackage looks for the package-lock.json in the root directory and there is none.

I thought I could somehow preserve this src and make Nix pick the package-lock.json file from a subdirectory, so I tried the following approaches:

npmRoot = ./frontend
postPatch = ''
  ln -s ${./package-lock.json} package-lock.json
''

Unfortunately those did not do the trick for me either.

Could someone tell me how this is usually done? I could not find anything after a long search, and those options that I found do not work.

You probably want

npmRoot = "frontend"

since it is entering that directory directly:

Using a path expression as a value here (eventually passed to derivation builtin) will cause the directory copied into Nix store, just like it did in the src attribute.

@jtojnar I tried this now, but I get the error “Cannot stat ‘package.json’. No such file or directory”.

frontend = pkgs.buildNpmPackage {
  name = "frontend";
  src = src;

  npmDeps = pkgs.importNpmLock {
    npmRoot = "${src}/frontend";
  };

  npmConfigHook = pkgs.importNpmLock.npmConfigHook;

  installPhase = ''
    mkdir $out
    cp -r build/* $out
  '';
};

You need to do it in the outer layer (adjacent to installPhase) too.

You may be interested in my pagefind derivation