Yarn Plug'n'Play and direnv/packaging

I have a dream2nix PR for new build system for nodejs as well. It could be merged anytime soon.
But well it does not yet resolve peerDependency conflicts. To resolve such problems your first need to use pnpm with the pnpm lockfile and then a build system that is capable of producing the pnpm node-modules folder. (I had such a thing about 80% ready but would need to invest more time on it

I kind of forgot about my PR :-/ The problem is that it’s quite different, it builds everything with symlinks which is fine if you put cyclic deps together. I should look at it again.

Currently, we have the idea, that dream2nix could be more modular. I think of well-defined interfaces for atomic parts, such as dependency resolution. I didn’t entirely look into your approach yet. The yarn-plug-n-play solution is quite interesting also with using symlinks because we can omit creating the node_modules folder. (If I memorize correctly) With the evolving dream2nix architecture that logic could be encapsulated into a module that you or the user of dream2nix can provide to configure the build system.
Can I contact you about the plug-and-play system? Maybe we can provide a good module for it and leverage the power of the nix store.

+1 on more dream2nix API for frameworks.

floco was basically rewritten from scratch to use modules so that we could integrate with d2n but haven’t managed to work out exactly how external modules and frameworks are supposed to plug in without forking and extending or merging my framework into their codebase entirely.

1 Like

Right, so my PR:

  • reads the v3 package.lock format and gets all the peerDeps, optionals etc
  • changes the cyclic dep calculation so it groups cycles instead of the current snipping cycles (which is incorrect). This could be made to happen only for npm
  • changes the module build so each cycle group is copied together, their deps are symlinked and then the deps’ build scripts are run
  • makes it easy to generate a node_modules with or without devDeps

This way there’s lots of reuse, and devShells are trivial.

I got stuck on Node package-lock v2 by wmertens · Pull Request #246 · nix-community/dream2nix · GitHub

1 Like

@phaer and me have been working on testing the grounds for a new integration API based on drv-parts. That way the whole integration story should become a lot simpler. I’ll ping you once we got a first draft of it merged. I’m really looking forward to incorporating your work on floco.

6 Likes

Woohoo! :tada: Looking forward to it

1 Like

Anyone has a sample flake on how to build a web application like next? Or just to be able to run “npm build” with dream2nix?

@woile not sure you are asking for this, but with node2nix I use:

app.nix

{ pkgs }:

with pkgs;

let
  my_nodejs = pkgs.nodejs;
  nodeDependencies = pkgs.callPackage ./override.nix { inherit pkgs; nodejs = my_nodejs; };
in

stdenv.mkDerivation rec {
  name = "my-app";

  src = builtins.filterSource
    (path: _type: baseNameOf path != "node_modules")
    ./.;

  buildInputs = [
    my_nodejs
  ];

  buildPhase = ''
    ln -s ${nodeDependencies.package}/lib/node_modules/${name}/node_modules ./node_modules
    export PATH="${nodeDependencies.package}/lib/node_modules/${name}/node_modules/.bin:$PATH"
    npm run build
  '';

  installPhase = ''
    mkdir -p $out/web-app
    cp -r .next $out/web-app/
    ln -s ${nodeDependencies.package}/lib/node_modules/${name}/node_modules $out/web-app/node_modules
  '';
}

override.nix

{ lib
, pkgs
, nodejs
, system ? builtins.currentSystem
}:

let
  nodePackages = import ./default.nix {
    inherit pkgs system nodejs;
  };
in
nodePackages // {
  package = nodePackages.package.override {
    src = lib.cleanSourceWith { filter = name: _type: (builtins.baseNameOf name) != "node_modules"; src = ./.; };
  };
}

flake.nix

{
  description = "app";
  nixConfig.substituters = [
    "https://cache.nixos.org"
    "https://nix-community.cachix.org"
    "https://pre-commit-hooks.cachix.org"
  ];
  nixConfig.trusted-public-keys = [
    "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
    "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
    "pre-commit-hooks.cachix.org-1:Pkk3Panw5AW24TOv6kz3PvLhlH8puAsJTBbOPmBo7Rc="
  ];

  inputs = {
    nixpkgs.url = "nixpkgs/nixpkgs-unstable";
    flake-parts = {
      url = "flake-parts";
      inputs.nixpkgs-lib.follows = "nixpkgs";
    };
    pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
  };

  outputs = inputs@{ self, flake-parts, nixpkgs, pre-commit-hooks, ... }: flake-parts.lib.mkFlake { inherit inputs; } {
    systems = [
      "x86_64-linux"
    ];

    perSystem = { system, ... }:
      let
        pkgs = import nixpkgs {
          inherit system;
          config.permittedInsecurePackages = [
	 	        "nodejs-16.20.1"
          ];
        };

        pre-commit-check = pre-commit-hooks.lib.${system}.run {
          src = ./.;
          hooks = {
            nixpkgs-fmt.enable = true;
            prettier.enable = true;
            statix.enable = false;
            terraform-format.enable = true;
          };
          # generated files
          excludes = [
            "default.nix"
            "node-env.nix"
            "node-packages.nix"
          ];
        };

        app = import ./front.nix { inherit pkgs; };

        shell = import ./shell.nix {
          inherit pkgs pre-commit-check;
        };

        docker = import ./docker.nix {
          inherit pkgs app;
          lastModifiedDate = self.lastModifiedDate;
        };
      in
      {
        checks = {
          inherit pre-commit-check app shell docker;
        };

        packages.default = docker;
        devShells.default = shell;
      };
  };
}

shell.nix

{ pkgs, pre-commit-check }:

with pkgs;

mkShell {
  buildInputs = [
    nodejs
    skopeo
    nodePackages.eslint
    nodePackages.node2nix
    nodePackages.prettier
    nodePackages_latest.typescript-language-server
    playwright
    pre-commit
    sops
  ];

  PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = 1;
  PLAYWRIGHT_BROWSERS_PATH = "${playwright-driver.browsers}";

  shellHook = ''
    ${pre-commit-check.shellHook}
  '';
}

docker.nix

{ pkgs, lastModifiedDate, my-app }:

with pkgs;

dockerTools.streamLayeredImage {
  name = "my-app";
  tag = "latest";
  maxLayers = 100;
  created = builtins.substring 0 8 lastModifiedDate;

  contents = [
    my-app

    coreutils
    dockerTools.binSh
    fakeNss
  ];

  config = {
    User = "65534:65534"; # nobody
    WorkingDir = "/web-app";
    Cmd = [
      "node_modules/.bin/next"
      "start"
      "-p"
      "8102"
    ];
  };
}

EDIT: @woile I just realized that you wanted a whole flake.

1 Like