nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at r-updates 884 lines 34 kB view raw view rendered
1# Javascript {#language-javascript} 2 3## Introduction {#javascript-introduction} 4 5This contains instructions on how to package JavaScript applications. 6 7The various tools available will be listed in the [tools-overview](#javascript-tools-overview). 8Some general principles for packaging will follow. 9Finally, some tool-specific instructions will be given. 10 11## Getting unstuck / finding code examples {#javascript-finding-examples} 12 13If you find you are lacking inspiration for packaging JavaScript applications, the links below might prove useful. 14Searching online for prior art can be helpful if you are running into solved problems. 15 16### Github {#javascript-finding-examples-github} 17 18- Searching Nix files for `yarnConfigHook`: <https://github.com/search?q=yarnConfigHook+language%3ANix&type=code> 19- Searching just `flake.nix` files for `yarnConfigHook`: <https://github.com/search?q=yarnConfigHook+path%3A**%2Fflake.nix&type=code> 20 21### Gitlab {#javascript-finding-examples-gitlab} 22 23- Searching Nix files for `yarnConfigHook`: <https://gitlab.com/search?scope=blobs&search=yarnConfigHook+extension%3Anix> 24- Searching just `flake.nix` files for `yarnConfigHook`: <https://gitlab.com/search?scope=blobs&search=yarnConfigHook+filename%3Aflake.nix> 25 26## Tools overview {#javascript-tools-overview} 27 28## General principles {#javascript-general-principles} 29 30The following principles are given in order of importance with potential exceptions. 31 32### Try to use the same node version used upstream {#javascript-upstream-node-version} 33 34It is often not documented which node version is used upstream, but if it is, try to use the same version when packaging. 35 36This can be a problem if upstream is using the latest and greatest and you are trying to use an earlier version of node. 37Some cryptic errors regarding V8 may appear. 38 39### Try to respect the package manager originally used by upstream (and use the upstream lock file) {#javascript-upstream-package-manager} 40 41A lock file (package-lock.json, yarn.lock...) is supposed to make reproducible installations of `node_modules` for each tool. 42 43Guidelines of package managers, recommend to commit those lock files to the repos. 44If a particular lock file is present, it is a strong indication of which package manager is used upstream. 45 46It's better to try to use a Nix tool that understands the lock file. 47Using a different tool might give you a hard-to-understand error because different packages have been installed. 48An example of problems that could arise can be found [here](https://github.com/NixOS/nixpkgs/pull/126629). 49Upstream use npm, but this is an attempt to package it with `yarn2nix` (that uses yarn.lock). 50 51Using a different tool forces you to commit a lock file to the repository. 52These files are fairly large, so when packaging for nixpkgs, this approach does not scale well. 53 54Exceptions to this rule are: 55 56- When you encounter one of the bugs from a Nix tool. In each of the tool-specific instructions, known problems will be detailed. If you have a problem with a particular tool, then it's best to try another tool, even if this means you will have to re-create a lock file and commit it to Nixpkgs. In general `yarn2nix` has fewer known problems, and so a simple search in Nixpkgs will reveal many `yarn.lock` files committed. 57- Some lock files contain particular version of a package that has been pulled off npm for some reason. In that case, you can recreate upstream lock (by removing the original and `npm install`, `yarn`, ...) and commit this to nixpkgs. 58- The only tool that supports workspaces (a feature of npm that helps manage sub-directories with different package.json from a single top level package.json) is `yarn2nix`. If upstream has workspaces you should try `yarn2nix`. 59 60### Try to use upstream package.json {#javascript-upstream-package-json} 61 62Exceptions to this rule are: 63 64- Sometimes the upstream repo assumes some dependencies should be installed globally. In that case, you can add them manually to the upstream `package.json` (`yarn add xxx` or `npm install xxx`, ...). Dependencies that are installed locally can be executed with `npx` for CLI tools (e.g. `npx postcss ...`, this is how you can call those dependencies in the phases). 65- Sometimes there is a version conflict between some dependency requirements. In that case you can fix a version by removing the `^`. 66- Sometimes the script defined in the package.json does not work as is. Some scripts for example use CLI tools that might not be available, or cd in directory with a different package.json (for workspaces notably). In that case, it's perfectly fine to look at what the particular script is doing and break this down in the phases. In the build script you can see `build:*` calling in turns several other build scripts like `build:ui` or `build:server`. If one of those fails, you can try to separate those into, 67 68 ```sh 69 yarn build:ui 70 yarn build:server 71 # OR 72 npm run build:ui 73 npm run build:server 74 ``` 75 76 when you need to override a package.json. It's nice to use the one from the upstream source and do some explicit override. Here is an example: 77 78 ```nix 79 { 80 patchedPackageJSON = final.runCommand "package.json" { } '' 81 ${jq}/bin/jq '.version = "0.4.0" | 82 .devDependencies."@jsdoc/cli" = "^0.2.5" 83 ${sonar-src}/package.json > $out 84 ''; 85 } 86 ``` 87 88 You will still need to commit the modified version of the lock files, but at least the overrides are explicit for everyone to see. 89 90### Using node_modules directly {#javascript-using-node_modules} 91 92Each tool has an abstraction to just build the node_modules (dependencies) directory. 93You can always use the `stdenv.mkDerivation` with the node_modules to build the package (symlink the node_modules directory and then use the package build command). 94The node_modules abstraction can be also used to build some web framework frontends. 95For an example of this see how [plausible](https://github.com/NixOS/nixpkgs/blob/master/pkgs/servers/web-apps/plausible/default.nix) is built. `mkYarnModules` to make the derivation containing node_modules. 96Then when building the frontend you can just symlink the node_modules directory. 97 98## Tool-specific instructions {#javascript-tool-specific} 99 100### buildNpmPackage {#javascript-buildNpmPackage} 101 102`buildNpmPackage` allows you to package npm-based projects in Nixpkgs without the use of an auto-generated dependencies file (as used in [node2nix](#javascript-node2nix)). 103It works by utilizing npm's cache functionality -- creating a reproducible cache that contains the dependencies of a project, and pointing npm to it. 104 105Here's an example: 106 107```nix 108{ 109 lib, 110 buildNpmPackage, 111 fetchFromGitHub, 112}: 113 114buildNpmPackage (finalAttrs: { 115 pname = "flood"; 116 version = "4.7.0"; 117 118 src = fetchFromGitHub { 119 owner = "jesec"; 120 repo = "flood"; 121 tag = "v${finalAttrs.version}"; 122 hash = "sha256-BR+ZGkBBfd0dSQqAvujsbgsEPFYw/ThrylxUbOksYxM="; 123 }; 124 125 npmDepsHash = "sha256-tuEfyePwlOy2/mOPdXbqJskO6IowvAP4DWg8xSZwbJw="; 126 127 # The prepack script runs the build script, which we'd rather do in the build phase. 128 npmPackFlags = [ "--ignore-scripts" ]; 129 130 NODE_OPTIONS = "--openssl-legacy-provider"; 131 132 meta = { 133 description = "Modern web UI for various torrent clients with a Node.js backend and React frontend"; 134 homepage = "https://flood.js.org"; 135 license = lib.licenses.gpl3Only; 136 maintainers = with lib.maintainers; [ winter ]; 137 }; 138}) 139``` 140 141In the default `installPhase` set by `buildNpmPackage`, it uses `npm pack --json --dry-run` to decide what files to install in `$out/lib/node_modules/$name/`, where `$name` is the `name` string defined in the package's `package.json`. 142Additionally, the `bin` and `man` keys in the source's `package.json` are used to decide what binaries and manpages are supposed to be installed. 143If these are not defined, `npm pack` may miss some files, and no binaries will be produced. 144 145#### Arguments {#javascript-buildNpmPackage-arguments} 146 147* `npmDepsHash`: The output hash of the dependencies for this project. Can be calculated in advance with [`prefetch-npm-deps`](#javascript-buildNpmPackage-prefetch-npm-deps). 148* `makeCacheWritable`: Whether to make the cache writable prior to installing dependencies. Don't set this unless npm tries to write to the cache directory, as it can slow down the build. 149* `npmBuildScript`: The script to run to build the project. Defaults to `"build"`. 150* `npmWorkspace`: The workspace directory within the project to build and install. 151* `dontNpmBuild`: Option to disable running the build script. Set to `true` if the package does not have a build script. Defaults to `false`. Alternatively, setting `buildPhase` explicitly also disables this. 152* `dontNpmInstall`: Option to disable running `npm install`. Defaults to `false`. Alternatively, setting `installPhase` explicitly also disables this. 153* `npmFlags`: Flags to pass to all npm commands. 154* `npmInstallFlags`: Flags to pass to `npm ci`. 155* `npmBuildFlags`: Flags to pass to `npm run ${npmBuildScript}`. 156* `npmPackFlags`: Flags to pass to `npm pack`. 157* `npmPruneFlags`: Flags to pass to `npm prune`. Defaults to the value of `npmInstallFlags`. 158* `makeWrapperArgs`: Flags to pass to `makeWrapper`, added to executable calling the generated `.js` with `node` as an interpreter. These scripts are defined in `package.json`. 159* `nodejs`: The `nodejs` package to build against, using the corresponding `npm` shipped with that version of `node`. Defaults to `pkgs.nodejs`. 160* `npmDeps`: The dependencies used to build the npm package. Especially useful to not have to recompute workspace dependencies. 161 162#### prefetch-npm-deps {#javascript-buildNpmPackage-prefetch-npm-deps} 163 164`prefetch-npm-deps` is a Nixpkgs package that calculates the hash of the dependencies of an npm project ahead of time. 165 166```console 167$ ls 168package.json package-lock.json index.js 169$ prefetch-npm-deps package-lock.json 170... 171sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 172``` 173 174#### fetchNpmDeps {#javascript-buildNpmPackage-fetchNpmDeps} 175 176`fetchNpmDeps` is a Nix function that requires the following mandatory arguments: 177 178- `src`: A directory / tarball with `package-lock.json` file 179- `hash`: The output hash of the node dependencies defined in `package-lock.json`. 180 181It returns a derivation with all `package-lock.json` dependencies downloaded into `$out/`, usable as an npm cache. 182 183#### importNpmLock {#javascript-buildNpmPackage-importNpmLock} 184 185This function replaces the npm dependency references in `package.json` and `package-lock.json` with paths to the Nix store. 186How each dependency is fetched can be customized with the `fetcherOpts` argument. 187 188This is a simpler and more convenient alternative to [`fetchNpmDeps`](#javascript-buildNpmPackage-fetchNpmDeps) for managing npm dependencies in Nixpkgs. 189There is no need to specify a `hash`, since it relies entirely on the integrity hashes already present in the `package-lock.json` file. 190 191##### Inputs {#javascript-buildNpmPackage-inputs} 192 193- `npmRoot`: Path to package directory containing the source tree. 194 If this is omitted, the `package` and `packageLock` arguments must be specified instead. 195- `package`: Parsed contents of `package.json` 196- `packageLock`: Parsed contents of `package-lock.json` 197- `pname`: Package name 198- `version`: Package version 199- `fetcherOpts`: An attribute set of arguments forwarded to the underlying fetcher. 200 201It returns a derivation with a patched `package.json` & `package-lock.json` with all dependencies resolved to Nix store paths. 202 203:::{.note} 204`npmHooks.npmConfigHook` cannot be used with `importNpmLock`. 205Use `importNpmLock.npmConfigHook` instead. 206::: 207 208:::{.example} 209 210##### `pkgs.importNpmLock` usage example {#javascript-buildNpmPackage-example} 211```nix 212{ buildNpmPackage, importNpmLock }: 213 214buildNpmPackage { 215 pname = "hello"; 216 version = "0.1.0"; 217 src = ./.; 218 219 npmDeps = importNpmLock { npmRoot = ./.; }; 220 221 npmConfigHook = importNpmLock.npmConfigHook; 222} 223``` 224::: 225 226:::{.example} 227##### `pkgs.importNpmLock` usage example with `fetcherOpts` {#javascript-buildNpmPackage-example-fetcherOpts} 228 229`importNpmLock` uses the following fetchers: 230 231- `pkgs.fetchurl` for `http(s)` dependencies 232- `fetchGit` for `git` dependencies 233 234It is possible to provide additional arguments to individual fetchers as needed: 235 236```nix 237{ buildNpmPackage, importNpmLock }: 238 239buildNpmPackage { 240 pname = "hello"; 241 version = "0.1.0"; 242 src = ./.; 243 244 npmDeps = importNpmLock { 245 npmRoot = ./.; 246 fetcherOpts = { 247 # Pass 'curlOptsList' to 'pkgs.fetchurl' while fetching 'axios' 248 "node_modules/axios" = { 249 curlOptsList = [ "--verbose" ]; 250 }; 251 }; 252 }; 253 254 npmConfigHook = importNpmLock.npmConfigHook; 255} 256``` 257::: 258 259#### importNpmLock.buildNodeModules {#javascript-buildNpmPackage-importNpmLock.buildNodeModules} 260 261`importNpmLock.buildNodeModules` returns a derivation with a pre-built `node_modules` directory, as imported by `importNpmLock`. 262 263This is to be used together with `importNpmLock.hooks.linkNodeModulesHook` to facilitate `nix-shell`/`nix develop` based development workflows. 264 265It accepts an argument with the following attributes: 266 267`npmRoot` (Path; optional) 268: Path to package directory containing the source tree. If not specified, the `package` and `packageLock` arguments must both be specified. 269 270`package` (Attrset; optional) 271: Parsed contents of `package.json`, as returned by `lib.importJSON ./my-package.json`. If not specified, the `package.json` in `npmRoot` is used. 272 273`packageLock` (Attrset; optional) 274: Parsed contents of `package-lock.json`, as returned `lib.importJSON ./my-package-lock.json`. If not specified, the `package-lock.json` in `npmRoot` is used. 275 276`derivationArgs` (`mkDerivation` attrset; optional) 277: Arguments passed to `stdenv.mkDerivation` 278 279For example: 280 281```nix 282pkgs.mkShell { 283 packages = [ 284 importNpmLock.hooks.linkNodeModulesHook 285 nodejs 286 ]; 287 288 npmDeps = importNpmLock.buildNodeModules { 289 npmRoot = ./.; 290 inherit nodejs; 291 }; 292} 293``` 294will create a development shell where a `node_modules` directory is created & packages symlinked to the Nix store when activated. 295 296:::{.note} 297Commands like `npm install` & `npm add` that write packages & executables need to be used with `--package-lock-only`. 298 299This means `npm` installs dependencies by writing into `package-lock.json` without modifying the `node_modules` folder. Installation happens through reloading the devShell. 300This might be best practice since it gives the `nix shell` virtually exclusive ownership over your `node_modules` folder. 301 302It's recommended to set `package-lock-only = true` in your project-local [`.npmrc`](https://docs.npmjs.com/cli/v11/configuring-npm/npmrc). 303::: 304 305### corepack {#javascript-corepack} 306 307This package puts the corepack wrappers for pnpm and yarn in your PATH, and they will honor the `packageManager` setting in the `package.json`. 308 309### node2nix {#javascript-node2nix} 310 311#### Preparation {#javascript-node2nix-preparation} 312 313You will need to generate a Nix expression for the dependencies. Don't forget the `-l package-lock.json` if there is a lock file. Most probably you will need the `--development` to include the `devDependencies` 314 315So the command will most likely be: 316```sh 317node2nix --development -l package-lock.json 318``` 319 320See `node2nix` [docs](https://github.com/svanderburg/node2nix) for more info. 321 322#### Pitfalls {#javascript-node2nix-pitfalls} 323 324- If upstream package.json does not have a "version" attribute, `node2nix` will crash. You will need to add it like shown in [the package.json section](#javascript-upstream-package-json). 325- `node2nix` has some [bugs](https://github.com/svanderburg/node2nix/issues/238) related to working with lock files from npm distributed with `nodejs_16`. 326- `node2nix` does not like missing packages from npm. If you see something like `Cannot resolve version: vue-loader-v16@undefined` then you might want to try another tool. The package might have been pulled off of npm. 327 328### pnpm {#javascript-pnpm} 329 330pnpm is available as the top-level package `pnpm`. Additionally, there are variants pinned to certain major versions, like `pnpm_8`, `pnpm_9` and `pnpm_10`, which support different sets of lock file versions. 331 332When packaging an application that includes a `pnpm-lock.yaml`, you need to fetch the pnpm store for that project using a fixed-output-derivation. The function `fetchPnpmDeps` can create this pnpm store derivation. In conjunction, the setup hook `pnpmConfigHook` will prepare the build environment to install the pre-fetched dependencies store. Here is an example for a package that contains `package.json` and a `pnpm-lock.yaml` files using the fetcher and setup hook above: 333 334```nix 335{ 336 fetchPnpmDeps, 337 nodejs, 338 pnpm, 339 pnpmConfigHook, 340 stdenv, 341}: 342 343stdenv.mkDerivation (finalAttrs: { 344 pname = "foo"; 345 version = "0-unstable-1980-01-01"; 346 347 src = { 348 #... 349 }; 350 351 nativeBuildInputs = [ 352 nodejs # in case scripts are run outside of a pnpm call 353 pnpmConfigHook 354 pnpm # At least required by pnpmConfigHook, if not other (custom) phases 355 ]; 356 357 pnpmDeps = fetchPnpmDeps { 358 inherit (finalAttrs) pname version src; 359 fetcherVersion = 3; 360 hash = "..."; 361 }; 362}) 363``` 364 365It is highly recommended to use a pinned version of pnpm (i.e., `pnpm_9` or `pnpm_10`), to increase future reproducibility. It might also be required to use an older version if the package needs support for a certain lock file version. To do so, you can pass the `pnpm` argument to `fetchPnpmDeps` and override the `pnpm` arg in `pnpmConfigHook`. Here are the changes in the example above to use a pinned pnpm version: 366 367<!-- TODO: Does splicing still work when overriding in nativeBuildInputs here? --> 368 369```diff 370 { 371 fetchPnpmDeps, 372 nodejs, 373- pnpm, 374+ pnpm_10, 375 pnpmConfigHook, 376 stdenv, 377 }: 378+let 379+ # Optionally override pnpm to use a custom nodejs version 380+ # Make sure that the same nodejs version is referenced in nativeBuildInputs 381+ # pnpm = pnpm_10.override { nodejs = nodejs_20; }; 382+in 383 stdenv.mkDerivation (finalAttrs: { 384 pname = "foo"; 385 version = "0-unstable-1980-01-01"; 386 387 src = { 388 #... 389 }; 390 391 nativeBuildInputs = [ 392 nodejs # in case scripts are run outside of a pnpm call 393 pnpmConfigHook 394- pnpm # At least required by pnpmConfigHook, if not other (custom) phases 395+ pnpm_10 # At least required by pnpmConfigHook, if not other (custom) phases 396 ]; 397 398 pnpmDeps = fetchPnpmDeps { 399 inherit (finalAttrs) pname version src; 400+ pnpm = pnpm_10; 401 fetcherVersion = 3; 402 hash = "..."; 403 }; 404 }) 405``` 406 407In case you are patching `package.json` or `pnpm-lock.yaml`, make sure to pass `finalAttrs.patches` to the function as well (i.e., `inherit (finalAttrs) patches`. 408 409`pnpmConfigHook` supports adding additional `pnpm install` flags via `pnpmInstallFlags` which can be set to a Nix string array: 410 411```nix 412{ 413 # ... 414 pnpmDeps = fetchPnpmDeps { 415 # ... 416 inherit (finalAttrs) pnpmInstallFlags; 417 }; 418 419 pnpmInstallFlags = [ "--shamefully-hoist" ]; 420} 421``` 422 423#### Dealing with `sourceRoot` {#javascript-pnpm-sourceRoot} 424 425If the pnpm project is in a subdirectory, you can just define `sourceRoot` or `setSourceRoot` for `fetchPnpmDeps`. 426If `sourceRoot` is different between the parent derivation and `fetchPnpmDeps`, you will have to set `pnpmRoot` to effectively be the same location as it is in `fetchPnpmDeps`. 427 428Assuming the following directory structure, we can define `sourceRoot` and `pnpmRoot` as follows: 429 430``` 431. 432├── frontend 433│   ├── ... 434│   ├── package.json 435│   └── pnpm-lock.yaml 436└── ... 437``` 438 439```nix 440{ 441 # ... 442 pnpmDeps = fetchPnpmDeps { 443 # ... 444 sourceRoot = "${finalAttrs.src.name}/frontend"; 445 }; 446 447 # by default the working directory is the extracted source 448 pnpmRoot = "frontend"; 449} 450``` 451 452#### PNPM Workspaces {#javascript-pnpm-workspaces} 453 454If you need to use a PNPM workspace for your project, then set `pnpmWorkspaces = [ "<workspace project name 1>" "<workspace project name 2>" ]`, etc, in your `fetchPnpmDeps` call, 455which will make PNPM only install dependencies for those workspace packages. 456 457For example: 458 459```nix 460{ 461 # ... 462 pnpmWorkspaces = [ "@astrojs/language-server" ]; 463 pnpmDeps = fetchPnpmDeps { 464 #... 465 inherit (finalAttrs) pnpmWorkspaces; 466 }; 467} 468``` 469 470The above would make `fetchPnpmDeps` call only install dependencies for the `@astrojs/language-server` workspace package. 471Note that you do not need to set `sourceRoot` to make this work. 472 473Usually, in such cases, you'd want to use `pnpm --filter=<pnpm workspace name> build` to build your project, as `npmHooks.npmBuildHook` probably won't work. A `buildPhase` based on the following example will probably fit most workspace projects: 474 475```nix 476{ 477 buildPhase = '' 478 runHook preBuild 479 480 pnpm --filter=@astrojs/language-server build 481 482 runHook postBuild 483 ''; 484} 485``` 486 487#### Additional PNPM Commands and settings {#javascript-pnpm-extraCommands} 488 489If you require setting an additional PNPM configuration setting (such as `dedupe-peer-dependents` or similar), 490set `prePnpmInstall` to the right commands to run. For example: 491 492```nix 493{ 494 prePnpmInstall = '' 495 pnpm config set dedupe-peer-dependents false 496 ''; 497 pnpmDeps = fetchPnpmDeps { 498 inherit (finalAttrs) prePnpmInstall; 499 # ... 500 }; 501} 502``` 503 504In this example, `prePnpmInstall` will be run by both `pnpmConfigHook` and by the `fetchPnpmDeps` builder. 505 506#### pnpm `fetcherVersion` {#javascript-pnpm-fetcherVersion} 507 508This is the version of the output of `fetchPnpmDeps`, if you haven't set it already, you can use `1` with your current hash: 509 510```nix 511{ 512 # ... 513 pnpmDeps = fetchPnpmDeps { 514 # ... 515 fetcherVersion = 1; 516 hash = "..."; # you can use your already set hash here 517 }; 518} 519``` 520 521After upgrading to a newer `fetcherVersion`, you need to regenerate the hash: 522 523```nix 524{ 525 # ... 526 pnpmDeps = fetchPnpmDeps { 527 # ... 528 fetcherVersion = 2; 529 hash = "..."; # clear this hash and generate a new one 530 }; 531} 532``` 533 534This variable ensures that we can make changes to the output of `fetchPnpmDeps` without breaking existing hashes. 535Changes can include workarounds or bug fixes to existing PNPM issues. 536 537##### Version history {#javascript-pnpm-fetcherVersion-versionHistory} 538 539- 1: Initial version, nothing special 540- 2: [Ensure consistent permissions](https://github.com/NixOS/nixpkgs/pull/422975) 541- 3: [Build a reproducible tarball](https://github.com/NixOS/nixpkgs/pull/469950) 542 543### Yarn {#javascript-yarn} 544 545Yarn based projects use a `yarn.lock` file instead of a `package-lock.json` to pin dependencies. 546 547To package yarn-based applications, you need to distinguish by the version pointers in the `yarn.lock` file. See the following sections. 548 549#### Yarn v1 {#javascript-yarn-v1} 550 551Yarn v1 lockfiles contain a comment `# yarn lockfile v1` at the beginning of the file. 552 553Nixpkgs provides the Nix function `fetchYarnDeps` which fetches an offline cache suitable for running `yarn install` before building the project. In addition, Nixpkgs provides the hooks: 554 555- `yarnConfigHook`: Fetches the dependencies from the offline cache and installs them into `node_modules`. 556- `yarnBuildHook`: Runs `yarn build` or a specified `yarn` command that builds the project. 557- `yarnInstallHook`: Runs `yarn install --production` to prune dependencies and installs the project into `$out`. 558 559An example usage of the above attributes is: 560 561```nix 562{ 563 lib, 564 stdenv, 565 fetchFromGitHub, 566 fetchYarnDeps, 567 yarnConfigHook, 568 yarnBuildHook, 569 yarnInstallHook, 570 nodejs, 571}: 572 573stdenv.mkDerivation (finalAttrs: { 574 pname = "..."; 575 version = "..."; 576 577 src = fetchFromGitHub { 578 owner = "..."; 579 repo = "..."; 580 tag = "v${finalAttrs.version}"; 581 hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; 582 }; 583 584 yarnOfflineCache = fetchYarnDeps { 585 yarnLock = finalAttrs.src + "/yarn.lock"; 586 hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; 587 }; 588 589 nativeBuildInputs = [ 590 yarnConfigHook 591 yarnBuildHook 592 yarnInstallHook 593 # Needed for executing package.json scripts 594 nodejs 595 ]; 596 597 meta = { 598 # ... 599 }; 600}) 601``` 602 603##### `yarnConfigHook` arguments {#javascript-yarnconfighook} 604 605By default, `yarnConfigHook` relies upon the attribute `${yarnOfflineCache}` (or `${offlineCache}` if the former is not set) to find the location of the offline cache produced by `fetchYarnDeps`. To disable this phase, you can set `dontYarnInstallDeps = true` or override the `configurePhase`. 606 607##### `yarnBuildHook` arguments {#javascript-yarnbuildhook} 608 609This script by default runs `yarn --offline build`, and it relies upon the project's dependencies installed at `node_modules`. Below is a list of additional `mkDerivation` arguments read by this hook: 610 611- `yarnBuildScript`: Sets a different `yarn --offline` subcommand (defaults to `build`). 612- `yarnBuildFlags`: Single string list of additional flags to pass the above command, or a Nix list of such additional flags. 613 614##### `yarnInstallHook` arguments {#javascript-yarninstallhook} 615 616To install the package `yarnInstallHook` uses both `npm` and `yarn` to cleanup project files and dependencies. To disable this phase, you can set `dontYarnInstall = true` or override the `installPhase`. Below is a list of additional `mkDerivation` arguments read by this hook: 617 618- `yarnKeepDevDeps`: Disables the removal of devDependencies from `node_modules` before installation. 619 620#### yarn2nix {#javascript-yarn2nix} 621 622> [!WARNING] 623> The `yarn2nix` functions have been deprecated in favor of `yarnConfigHook`, `yarnBuildHook` and `yarnInstallHook` (for Yarn v1) and `yarn-berry_*.*` tooling (Yarn v3 and v4). Documentation for `yarn2nix` functions still appears here for the sake of the packages that still use them. See also a tracking issue [#324246](https://github.com/NixOS/nixpkgs/issues/324246). 624 625##### Preparation {#javascript-yarn2nix-preparation} 626 627You will need at least a `yarn.lock` file. If upstream does not have one you need to generate it and reference it in your package definition. 628 629If the downloaded files contain the `package.json` and `yarn.lock` files they can be used like this: 630 631```nix 632{ 633 offlineCache = fetchYarnDeps { 634 yarnLock = src + "/yarn.lock"; 635 hash = "...."; 636 }; 637} 638``` 639 640##### mkYarnPackage {#javascript-yarn2nix-mkYarnPackage} 641 642> [!WARNING] 643> The `mkYarnPackage` functions have been deprecated in favor of `yarnConfigHook`, `yarnBuildHook` and `yarnInstallHook` (for Yarn v1) and `yarn-berry_*.*` tooling (Yarn v3 and v4). Documentation for `mkYarnPackage` functions still appears here for the sake of the packages that still use them. See also a tracking issue [#324246](https://github.com/NixOS/nixpkgs/issues/324246). 644 645`mkYarnPackage` will by default try to generate a binary. For packages only generating static assets (Svelte, Vue, React, Webpack, ...), you will need to explicitly override the build step with your instructions. 646 647It's important to use the `--offline` flag. For example if you script is `"build": "something"` in `package.json` use: 648 649```nix 650{ 651 nativeBuildInputs = [ writableTmpDirAsHomeHook ]; 652 653 buildPhase = '' 654 runHook preBuild 655 656 yarn --offline build 657 658 runHook postBuild 659 ''; 660} 661``` 662 663The `distPhase` is packing the package's dependencies in a tarball using `yarn pack`. You can disable it using: 664 665```nix 666{ doDist = false; } 667``` 668 669The configure phase can sometimes fail because it makes many assumptions that may not always apply. One common override is: 670 671```nix 672{ 673 configurePhase = '' 674 runHook preConfigure 675 676 ln -s $node_modules node_modules 677 678 runHook postConfigure 679 ''; 680} 681``` 682 683or if you need a writeable node_modules directory: 684 685```nix 686{ 687 configurePhase = '' 688 runHook preConfigure 689 690 cp -r $node_modules node_modules 691 chmod +w node_modules 692 693 runHook postConfigure 694 ''; 695} 696``` 697 698##### mkYarnModules {#javascript-yarn2nix-mkYarnModules} 699 700This will generate a derivation including the `node_modules` directory. 701If you have to build a derivation for an integrated web framework (Rails, Phoenix, etc.), this is probably the easiest way. 702 703#### Overriding dependency behavior {#javascript-mkYarnPackage-overriding-dependencies} 704 705In the `mkYarnPackage` record the property `pkgConfig` can be used to override packages when you encounter problems building. 706 707For instance, say your package is throwing errors when trying to invoke node-sass: 708 709``` 710ENOENT: no such file or directory, scandir '/build/source/node_modules/node-sass/vendor' 711``` 712 713To fix this we will specify different versions of build inputs to use, as well as some post install steps to get the software built the way we want: 714 715```nix 716mkYarnPackage rec { 717 pkgConfig = { 718 node-sass = { 719 buildInputs = with final; [ 720 python 721 libsass 722 pkg-config 723 ]; 724 postInstall = '' 725 LIBSASS_EXT=auto yarn --offline run build 726 rm build/config.gypi 727 ''; 728 }; 729 }; 730} 731``` 732 733##### Pitfalls {#javascript-yarn2nix-pitfalls} 734 735- If version is missing from upstream package.json, yarn will silently install nothing. In that case, you will need to override package.json as shown in the [package.json section](#javascript-upstream-package-json) 736- Having trouble with `node-gyp`? Try adding these lines to the `yarnPreBuild` steps: 737 738 ```nix 739 { 740 yarnPreBuild = '' 741 mkdir -p $HOME/.node-gyp/${nodejs.version} 742 echo 9 > $HOME/.node-gyp/${nodejs.version}/installVersion 743 ln -sfv ${nodejs}/include $HOME/.node-gyp/${nodejs.version} 744 export npm_config_nodedir=${nodejs} 745 ''; 746 } 747 ``` 748 749 - The `echo 9` steps comes from this answer: <https://stackoverflow.com/a/49139496> 750 - Exporting the headers in `npm_config_nodedir` comes from this issue: <https://github.com/nodejs/node-gyp/issues/1191#issuecomment-301243919> 751- `offlineCache` (described [above](#javascript-yarn2nix-preparation)) must be specified to avoid [Import From Derivation](#ssec-import-from-derivation) (IFD) when used inside Nixpkgs. 752 753#### Yarn Berry v3/v4 {#javascript-yarn-v3-v4} 754Yarn Berry (v3 / v4) have similar formats, they start with blocks like these: 755 756```yaml 757__metadata: 758 version: 6 759 cacheKey: 8[cX] 760``` 761 762```yaml 763__metadata: 764 version: 8 765 cacheKey: 10[cX] 766``` 767 768For these packages, we have some helpers exposed under the respective `yarn-berry_3` and `yarn-berry_4` packages: 769 770- `yarn-berry-fetcher` 771- `fetchYarnBerryDeps` 772- `yarnBerryConfigHook` 773 774It's recommended to ensure you're explicitly pinning the major version used, for example by capturing the `yarn-berry_Xn` argument and then re-defining it as a `yarn-berry` `let` binding. 775 776```nix 777{ 778 stdenv, 779 nodejs, 780 yarn-berry_4, 781}: 782 783let 784 yarn-berry = yarn-berry_4; 785 786in 787stdenv.mkDerivation (finalAttrs: { 788 pname = "foo"; 789 version = "0-unstable-1980-01-01"; 790 791 src = { 792 #... 793 }; 794 795 nativeBuildInputs = [ 796 nodejs 797 yarn-berry.yarnBerryConfigHook 798 ]; 799 800 offlineCache = yarn-berry.fetchYarnBerryDeps { 801 inherit (finalAttrs) src; 802 hash = "..."; 803 }; 804}) 805``` 806 807##### `yarn-berry_X.fetchYarnBerryDeps` {#javascript-fetchYarnBerryDeps} 808`fetchYarnBerryDeps` runs `yarn-berry-fetcher fetch` in a fixed-output-derivation. It is a custom fetcher designed to reproducibly download all files in the `yarn.lock` file, validating their hashes in the process. For git dependencies, it creates a checkout at `${offlineCache}/checkouts/<40-character-commit-hash>` (relying on the git commit hash to describe the contents of the checkout). 809 810To produce the `hash` argument for `fetchYarnBerryDeps` function call, the `yarn-berry-fetcher prefetch` command can be used: 811 812```console 813$ yarn-berry-fetcher prefetch </path/to/yarn.lock> [/path/to/missing-hashes.json] 814``` 815 816This prints the hash to stdout and can be used in update scripts to recalculate the hash for a new version of `yarn.lock`. 817 818##### `yarn-berry_X.yarnBerryConfigHook` {#javascript-yarnBerryConfigHook} 819`yarnBerryConfigHook` uses the store path `offlineCache` points to, to run a `yarn install` during the build, producing a usable `node_modules` directory from the downloaded dependencies. 820 821Internally, this uses a patched version of Yarn to ensure git dependencies are re-packed and any attempted downloads fail immediately. 822 823##### Patching upstream `package.json` or `yarn.lock` files {#javascript-yarnBerry-patching} 824In case patching the upstream `package.json` or `yarn.lock` is needed, it's important to pass `finalAttrs.patches` to `fetchYarnBerryDeps` as well, so the patched variants are picked up (i.e., `inherit (finalAttrs) patches`. 825 826##### Missing hashes in the `yarn.lock` file {#javascript-yarnBerry-missing-hashes} 827Unfortunately, `yarn.lock` files do not include hashes for optional/platform-specific dependencies. This is [by design](https://github.com/yarnpkg/berry/issues/6759). 828 829To compensate for this, the `yarn-berry-fetcher missing-hashes` subcommand can be used to produce all missing hashes. These are usually stored in a `missing-hashes.json` file, which needs to be passed to both the build itself, as well as the `fetchYarnBerryDeps` helper: 830 831```nix 832{ 833 stdenv, 834 nodejs, 835 yarn-berry_4, 836}: 837 838let 839 yarn-berry = yarn-berry_4; 840 841in 842stdenv.mkDerivation (finalAttrs: { 843 pname = "foo"; 844 version = "0-unstable-1980-01-01"; 845 846 src = { 847 #... 848 }; 849 850 nativeBuildInputs = [ 851 nodejs 852 yarn-berry.yarnBerryConfigHook 853 ]; 854 855 missingHashes = ./missing-hashes.json; 856 offlineCache = yarn-berry.fetchYarnBerryDeps { 857 inherit (finalAttrs) src missingHashes; 858 hash = "..."; 859 }; 860}) 861``` 862 863## Outside Nixpkgs {#javascript-outside-nixpkgs} 864 865There are some other tools available, which are written in the Nix language. 866These can't be used inside Nixpkgs because they require [Import From Derivation](#ssec-import-from-derivation), which is not allowed in Nixpkgs. 867 868If you are packaging something outside Nixpkgs, consider the following: 869 870### npmlock2nix {#javascript-npmlock2nix} 871 872[npmlock2nix](https://github.com/nix-community/npmlock2nix) aims at building `node_modules` without code generation. It hasn't reached v1 yet, the API might be subject to change. 873 874#### Pitfalls {#javascript-npmlock2nix-pitfalls} 875 876There are some [problems with npm v7](https://github.com/tweag/npmlock2nix/issues/45). 877 878### nix-npm-buildpackage {#javascript-nix-npm-buildpackage} 879 880[nix-npm-buildpackage](https://github.com/serokell/nix-npm-buildpackage) aims at building `node_modules` without code generation. It hasn't reached v1 yet, the API might change. It supports both `package-lock.json` and yarn.lock. 881 882#### Pitfalls {#javascript-nix-npm-buildpackage-pitfalls} 883 884There are some [problems with npm v7](https://github.com/serokell/nix-npm-buildpackage/issues/33).