Node.js: Difference between revisions

imported>Milahu
m typo: wiki -> manual
DHCP (talk | contribs)
m various formatting and style improvements
 
(19 intermediate revisions by 13 users not shown)
Line 1: Line 1:
__TOC__
__TOC__
{{expansion}}


== Install ==
[https://nodejs.org Node.js] is an open-source, cross-platform [[JavaScript]] runtime environment that allows developers to execute JavaScript code on the server side. Built on the V8 JavaScript engine, it enables the creation of scalable and high-performance applications, particularly for real-time web services.


<syntaxhighlight lang="nix>
== Setup ==
Adapt or add following line to your system configuration:<syntaxhighlight lang="nix>
   environment.systemPackages = with pkgs; [ nodejs ];
   environment.systemPackages = with pkgs; [ nodejs ];
</syntaxhighlight>
</syntaxhighlight>
Line 11: Line 11:


== Packaging ==
== Packaging ==
=== Packaging with <code>buildNpmPackage</code> ===
=== Packaging with <code>buildNpmPackage</code> ===
From the [https://nixos.org/manual/nixpkgs/stable/#javascript-tool-specific Nixpkgs manual]: "<code>buildNpmPackage</code> allows you to package npm-based projects in Nixpkgs without the use of an auto-generated dependencies file (as used in node2nix). It works by utilizing npm’s cache functionality – creating a reproducible cache that contains the dependencies of a project, and pointing npm to it."
From the [https://nixos.org/manual/nixpkgs/stable/#javascript-tool-specific Nixpkgs manual]: "<code>buildNpmPackage</code> allows you to package npm-based projects in Nixpkgs without the use of an auto-generated dependencies file (as used in node2nix). It works by utilizing npm’s cache functionality – creating a reproducible cache that contains the dependencies of a project, and pointing npm to it."
To better understand what happens under the hood and see the latest features [https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/node/build-npm-package/default.nix see the build-npm-package source].
Here's a <code>flake.nix</code> example to build a node package from the current directory.
<syntaxhighlight lang="nix">
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
  };
  outputs = {
    self,
    nixpkgs,
  }: let
    pkgs = nixpkgs.legacyPackages."x86_64-linux";
  in {
    packages."x86_64-linux".default = pkgs.buildNpmPackage {
      pname = "my-node-script";
      version = "0.1.0";
      src = ./.;
      npmDepsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
    };
  };
}
</syntaxhighlight>
By default, the build phase runs the <code>build</code> script defined in <code>package.json</code>.
<syntaxhighlight lang="json">
{
  "scripts": {
    "build": "npm install"
  }
}
</syntaxhighlight>
The binaries created in this package are defined by the <code>bin</code> key in <code>package.json</code>. This example will result in a <code>my-node-script</code> binary being created that basically runs <code>node main.js</code>.
<syntaxhighlight lang="json">
{
  "bin": {
    "my-node-script": "main.js"
  }
}
</syntaxhighlight>


==== Packaging electron applications ====
==== Packaging electron applications ====
Line 22: Line 70:
yarn2nix uses the yarn nodejs tool to create a file called yarn.lock, which in return can be used by yarn2nix to generate a usable yarn expression.
yarn2nix uses the yarn nodejs tool to create a file called yarn.lock, which in return can be used by yarn2nix to generate a usable yarn expression.
This is what was needed to convert a small application server  [https://git.shackspace.de/rz/muellshack/tree/3be09715911628b164fa1cf346387555ca26a5b1 shackspace muellshack]:
This is what was needed to convert a small application server  [https://git.shackspace.de/rz/muellshack/tree/3be09715911628b164fa1cf346387555ca26a5b1 shackspace muellshack]:
<syntaxHighlight lang=console>
<syntaxhighlight lang="console">
$ nix-shell -p yarn yarn2nix
$ nix-shell -p yarn yarn2nix
$ yarn install
$ yarn install # creates yarn.lock
# creates yarn.lock
$ yarn2nix > yarn.nix
$ yarn2nix > yarn.nix
$ vim package.json
$ vim package.json # add: "bin": "app.js",
# add:   "bin": "app.js",
$ cat > default.nix <<EOF
$ cat > default.nix <<EOF
with (import <nixpkgs> {});
with (import <nixpkgs> {});
Line 46: Line 92:


$ result/bin/muellshack
$ result/bin/muellshack
</syntaxHighlight>
</syntaxhighlight>


The complete diff can be found at [https://git.shackspace.de/rz/muellshack/commit/f5e498acd47695c4947dc1b5ddebfad2eee8d653 the respective diff]
The complete diff can be found at [https://git.shackspace.de/rz/muellshack/commit/f5e498acd47695c4947dc1b5ddebfad2eee8d653 the respective diff]


== FAQ ==
== Troubleshooting ==


=== Using <code>npm install -g</code> fails ===
=== Using <code>npm install -g</code> fails ===
Line 77: Line 123:
This is done through configuring npm and amending your <tt>PATH</tt>.<ref>[https://logs.nix.samueldr.com/nixos/2018-07-09#1358500 joepie91 on #nixos, 2018-07-09]</ref>
This is done through configuring npm and amending your <tt>PATH</tt>.<ref>[https://logs.nix.samueldr.com/nixos/2018-07-09#1358500 joepie91 on #nixos, 2018-07-09]</ref>


<pre>
<syntaxhighlight lang="console">
$ npm set prefix ~/.npm-global
$ npm set prefix ~/.npm-global
</pre>
</syntaxhighlight>


Then, amend your <tt>PATH</tt> so it looks into <tt>$HOME/.npm-global</tt>.
Then, amend your <tt>PATH</tt> so it looks into <tt>$HOME/.npm-global</tt>.
Line 87: Line 133:
This is a bit harder to implement, but creates a bit more strictness in your environment; it will be impossible accidentally make use of what would have been a globally installed package. The idea is to install it to either a temporary transitory folder or to the project folder, then run the locally installed instance of the package, the binaries are found under <tt>node_packages/.bin/</tt>.<ref>[https://logs.nix.samueldr.com/nixos/2018-07-17#1386090; samueldr on #nixos, 2018-07-17]</ref>
This is a bit harder to implement, but creates a bit more strictness in your environment; it will be impossible accidentally make use of what would have been a globally installed package. The idea is to install it to either a temporary transitory folder or to the project folder, then run the locally installed instance of the package, the binaries are found under <tt>node_packages/.bin/</tt>.<ref>[https://logs.nix.samueldr.com/nixos/2018-07-17#1386090; samueldr on #nixos, 2018-07-17]</ref>


<pre>
<syntaxhighlight lang="console">
$ npm install uglify-es
$ npm install uglify-es
[ ... ]
[ ... ]


$ ls -l node_modules/.bin/
$ ls -l node_modules/.bin/
total 0
total 0
lrwxrwxrwx 1 user users 25 Jul 17 15:34 uglifyjs -> ../uglify-es/bin/uglifyjs
lrwxrwxrwx 1 user users 25 Jul 17 15:34 uglifyjs -> ../uglify-es/bin/uglifyjs


$ node_modules/.bin/uglifyjs --help
$ node_modules/.bin/uglifyjs --help
   Usage: uglifyjs [options] [files...]
   Usage: uglifyjs [options] [files...]
</pre>
</syntaxhighlight>


===== direnv =====
===== direnv =====
Line 111: Line 157:
==== Using <code>npx</code> ====
==== Using <code>npx</code> ====


<pre>
<syntaxhighlight lang="console">
$ nix-shell -p nodejs-8_x
$ nix-shell -p nodejs-8_x


$ npx create-react-app --help
$ npx create-react-app --help
npx: installed 67 in 1.671s
npx: installed 67 in 1.671s
   Usage: create-react-app <project-directory> [options]
   Usage: create-react-app <project-directory> [options]
[...]
[...]
</pre>
</syntaxhighlight>


==== Using <code>npx</code> with binaries ====
==== Using <code>npx</code> with binaries ====
Line 127: Line 173:
For example, <code>npx cypress open</code> might give an error like:
For example, <code>npx cypress open</code> might give an error like:


<pre>
<syntaxhighlight lang="console">
$ npx cypress open
$ npx cypress open


Line 137: Line 183:
----------
----------
spawn /home/rkb/.cache/Cypress/4.10.0/Cypress/Cypress ENOENT
spawn /home/rkb/.cache/Cypress/4.10.0/Cypress/Cypress ENOENT
</pre>
</syntaxhighlight>


One quick workaround for this is [https://nixos.wiki/wiki/Steam#steam-run to use <code>steam-run</code>] to provide a placeholder FHS environment that *should* work; e.g. for the Cypress example above:
One quick workaround for this is [[Steam#FHS environment only| to use <code>steam-run</code>]] to provide a placeholder FHS environment that *should* work; e.g. for the Cypress example above:


<pre>
<syntaxhighlight lang="console">
$ nix-env -iA nixos.steam-run
$ nix-env -iA nixos.steam-run


Line 147: Line 193:


-- Cypress opens successfully!
-- Cypress opens successfully!
</pre>
</syntaxhighlight>


(Inspired by [https://discourse.nixos.org/t/how-to-make-nixos-so-easy-that-people-can-be-productive-up-front-without-having-to-first-learn-the-nix-language/5625 this discussion on discourse.nixos.org])
(Inspired by [https://discourse.nixos.org/t/how-to-make-nixos-so-easy-that-people-can-be-productive-up-front-without-having-to-first-learn-the-nix-language/5625 this discussion on discourse.nixos.org])


== Example nix shell for Node.js development ==
'''Google-fonts fetch failure with NextJS'''
 
Nextjs is a popular React framework and comes with built-in support with support for Google fonts. If a NPM project uses it, <syntaxhighlight lang="console">
$ npm run build # which calls "next build"
</syntaxhighlight>will try to fetch and optimize the Google fonts during a  nix build run, which will fail in Nix's isolated sandbox without internet:<syntaxhighlight lang="shell">
...
Running phase: buildPhase
Executing npmBuildHook
 
> nextjs-ollama-local-ai@0.1.0 build
> next build
 
  ▲ Next.js 14.1.0
 
  Creating an optimized production build ...
request to https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap failed, reason: getaddrinfo EAI_AGAIN fonts.googleapis.com
    at ClientRequest.<anonymous> (/build/source/node_modules/next/dist/compiled/node-fetch/index.js:1:66160)
    at ClientRequest.emit (node:events:518:28)
    at TLSSocket.socketErrorListener (node:_http_client:500:9)
    at TLSSocket.emit (node:events:518:28)
    at emitErrorNT (node:internal/streams/destroy:169:8)
    at emitErrorCloseNT (node:internal/streams/destroy:128:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
  type: 'system',
  errno: 'EAI_AGAIN',
  code: 'EAI_AGAIN'
}
Failed to compile.
 
src/app/layout.tsx
`next/font` error:
Failed to fetch `Inter` from Google Fonts.
 
> Build failed because of webpack errors
 
ERROR: `npm build` failed
</syntaxhighlight>You have to patch the Javascript code <syntaxhighlight lang="javascript">
// In layout.tsx file replace
//
// import {Inter} from "next/font/google"; // or any other Google font like Inter
// const inter = Inter({ subsets: ["latin"] });
//
// with ("src:" must be relative to the src/app/layout.tsx file):
import localFont from "next/font/local";
const inter = localFont({ src: './Inter.ttf' });
</syntaxhighlight>and place the Google fonts from Nixpkgs into the project, e.g. in a <code>preBuild</code> phase:<syntaxhighlight lang="nix">
buildNpmPackage {
  pname = "myproject";
  version = "1.0.0";
  ...


`shell.nix` example:
  # see https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/cr/crabfit-frontend/package.nix
<syntaxhighlight lang="nix>
  preBuild = ''
{ pkgs ? import <nixpkgs> {} }:
    cp "${
      google-fonts.override { fonts = [ "Inter" ]; }
    }/share/fonts/truetype/Inter[slnt,wght].ttf" src/app/Inter.ttf
  '';


let
  ...
  lib = import <nixpkgs/lib>;
}
  buildNodeJs = pkgs.callPackage <nixpkgs/pkgs/development/web/nodejs/nodejs.nix> {};
</syntaxhighlight>You can take a look at what fonts are available in the Nix <code>google-fonts</code> package by calling:<syntaxhighlight lang="console">
$ ls -ahl $(nix build --no-link --print-out-paths nixpkgs#google-fonts)/share/fonts/truetype/
</syntaxhighlight>Take a look at [https://github.com/NixOS/nixpkgs/blob/8358fd43a66594d8b3445d87006185fa76d4be6e/pkgs/by-name/ho/homepage-dashboard/package.nix homepage-dashboard package in nixpkgs] for further workarounds for Nextjs in Nix.


  nodejsVersion = lib.fileContents ./.nvmrc;
== Tips and tricks ==
=== Example nix flake shell for Node.js development ===
[[Flake]] example: (Note: the `${&amp;lt;nixpkgs&amp;gt;}` needs to be replaced by `${<nixpkgs>}`
{{file|flake.nix|nix|
<nowiki>
{
  description = "example-node-js-flake";


   nodejs = buildNodeJs {
   inputs = {
     enableNpm = false;
     flake-utils.url = "github:numtide/flake-utils";
    version = nodejsVersion;
    sha256 = "1a0zj505nhpfcj19qvjy2hvc5a7gadykv51y0rc6032qhzzsgca2";
   };
   };


   NPM_CONFIG_PREFIX = toString ./npm_config_prefix;
   outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
        };


in pkgs.mkShell {
        buildNodeJs = pkgs.callPackage "${<nixpkgs>}/pkgs/development/web/nodejs/nodejs.nix" {
  packages = with pkgs; [
          python = pkgs.python3;
    nodejs
        };
    nodePackages.npm
  ];


  inherit NPM_CONFIG_PREFIX;
        nodejs = buildNodeJs {
          enableNpm = true;
          version = "20.5.1";
          sha256 = "sha256-Q5xxqi84woYWV7+lOOmRkaVxJYBmy/1FSFhgScgTQZA=";
        };
      in rec {
        flakedPkgs = pkgs;


  shellHook = ''
        # enables use of `nix shell`
    export PATH="${NPM_CONFIG_PREFIX}/bin:$PATH"
        devShell = pkgs.mkShell {
  '';
          # add things you want in your shell here
          buildInputs = with pkgs; [
            nodejs
          ];
        };
      }
    );
}
}


</syntaxhighlight>
</nowiki>
}}


== Using nodePackages with a different node version ==
=== Using nodePackages with a different node version ===
Packages in {{ic|nixpkgs.nodePackages}} are built using {{ic|nixpkgs.nodejs}}, so if you [[Overlays|overlay that package]] to a different version, the {{ic|nodePackages}} will be built using that:
Packages in {{ic|nixpkgs.nodePackages}} are built using {{ic|nixpkgs.nodejs}}, so if you [[Overlays|overlay that package]] to a different version, the {{ic|nodePackages}} will be built using that:
<syntaxhighlight lang="nix>
<syntaxhighlight lang="nix>
final: prev: {
final: prev: {
      nodejs = prev.nodejs-16_x;
  nodejs = prev.nodejs-16_x;
}
}
</syntaxhighlight>
</syntaxhighlight>
<pre>
<syntaxhighlight lang="console">
$ pnpm node --version
$ pnpm node --version
v16.17.1
v16.17.1
</pre>
</syntaxhighlight>
 
=== Override NodeJS package ===
Overriding a Nix package which is based on ''buildNpmPackage'' can be challeging because not only the source hash has to get changed but sometimes also the ''package-lock.json'' file and the ''npmDepsHash''.
 
Unfortunately it is not possible to directly access and change ''npmDepsHash'' inside ''overrideAttrs'', so this is an example workaround for changing the version, ''package-lock.json'' and hashes of the package ''eslint'':<syntaxhighlight lang="nix">
environment.systemPackages = [
  (eslint.overrideAttrs (oldAttrs: rec {
    version = "8.57.0";
    src = fetchFromGitHub {
      owner = "eslint";
      repo = "eslint";
      rev = "refs/tags/v${version}";
      hash = "sha256-nXlS+k8FiN7rbxhMmRPb3OplHpl+8fWdn1nY0cjL75c=";
    };
    postPatch = ''
      cp ${./package-lock.json} package-lock.json
    '';
    npmDepsHash = "sha256-DiXgAD0PvIIBxPAsdU8OOJIyvYI0JyPqu6sj7XN94hE=";
    npmDeps = pkgs.fetchNpmDeps {
      src = lib.fileset.toSource {
        root = ./.;
        fileset = lib.fileset.unions [
          ./package-lock.json
          ./package.json
        ];
      };
      name = "eslint-${version}-npm-deps";
      hash = npmDepsHash;
    };
  }))
];
</syntaxhighlight>


== External Links ==
== External Links ==
Line 202: Line 354:


=== References ===
=== References ===
[[Category:JavaScript]]