Node.js: Difference between revisions
imported>Alper Update things so Node is no longer outdated and it works on macOS |
m various formatting and style improvements |
||
| (16 intermediate revisions by 10 users not shown) | |||
| Line 1: | Line 1: | ||
__TOC__ | __TOC__ | ||
[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"> | ||
$ 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: | |||
$ 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> | ||
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] | ||
== | == 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> | ||
< | <syntaxhighlight lang="console"> | ||
$ npm set prefix ~/.npm-global | |||
</ | </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> | ||
< | <syntaxhighlight lang="console"> | ||
$ npm install uglify-es | |||
[ ... ] | [ ... ] | ||
$ 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 | |||
Usage: uglifyjs [options] [files...] | Usage: uglifyjs [options] [files...] | ||
</ | </syntaxhighlight> | ||
===== direnv ===== | ===== direnv ===== | ||
| Line 111: | Line 157: | ||
==== Using <code>npx</code> ==== | ==== Using <code>npx</code> ==== | ||
< | <syntaxhighlight lang="console"> | ||
$ nix-shell -p nodejs-8_x | |||
$ 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] | ||
[...] | [...] | ||
</ | </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: | ||
< | <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 | ||
</ | </syntaxhighlight> | ||
One quick workaround for this is [ | 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: | ||
< | <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! | ||
</ | </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]) | ||
'''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"> | |||
<syntaxhighlight lang=" | $ 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"; | |||
... | |||
# see https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/cr/crabfit-frontend/package.nix | |||
preBuild = '' | |||
cp "${ | |||
google-fonts.override { fonts = [ "Inter" ]; } | |||
}/share/fonts/truetype/Inter[slnt,wght].ttf" src/app/Inter.ttf | |||
''; | ''; | ||
... | |||
} | } | ||
</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. | |||
== Tips and tricks == | |||
=== Example nix flake shell for Node.js development === | |||
== Example nix flake shell for Node.js development == | [[Flake]] example: (Note: the `${&lt;nixpkgs&gt;}` needs to be replaced by `${<nixpkgs>}` | ||
{{file|flake.nix|nix| | |||
<nowiki> | |||
< | |||
{ | { | ||
description = "example-node-js-flake"; | description = "example-node-js-flake"; | ||
| Line 206: | Line 280: | ||
}; | }; | ||
buildNodeJs = pkgs.callPackage "${nixpkgs}/pkgs/development/web/nodejs/nodejs.nix" { | buildNodeJs = pkgs.callPackage "${<nixpkgs>}/pkgs/development/web/nodejs/nodejs.nix" { | ||
python = pkgs.python3; | python = pkgs.python3; | ||
}; | }; | ||
| Line 229: | Line 303: | ||
} | } | ||
</ | </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; | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
< | <syntaxhighlight lang="console"> | ||
$ pnpm node --version | $ pnpm node --version | ||
v16.17.1 | v16.17.1 | ||
</ | </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 247: | Line 354: | ||
=== References === | === References === | ||
[[Category:JavaScript]] | |||