summaryrefslogtreecommitdiffstats
path: root/doc/languages-frameworks/javascript.section.md
blob: 9a17aa25599886d5f1ac0e395619a84533d6f554 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
# JavaScript {#language-javascript}

## Introduction {#javascript-introduction}

Package JavaScript applications with the tools below.

## Tools overview {#javascript-tools-overview}

## General principles {#javascript-general-principles}

The principles below are ordered by importance.

### Use the project's Node.js version {#javascript-upstream-node-version}

It is often not documented which Node.js version the project uses, but if it is, use the same version when packaging.

This can be a problem if the project uses the latest and greatest and you are trying to use an earlier version of Node.js.
Some cryptic errors regarding V8 may appear.

### Use the project's package manager and lock file {#javascript-upstream-package-manager}

A lock file (package-lock.json, yarn.lock...) is supposed to make reproducible installations of `node_modules` for each tool.

Package manager guidelines recommend committing those lock files to the repository.
If a particular lock file is present, it is a strong indication of which package manager the project uses.

Use a Nix tool that understands the lock file.
Using a different tool might give you a hard-to-understand error because different packages have been installed.

Using a different tool forces you to commit a lock file to the repository.
These files are fairly large, so when packaging for nixpkgs, this approach does not scale well.

Exceptions to this rule are:

- When you encounter one of the bugs from a Nix tool. In each of the tool-specific instructions, known problems are detailed. If a tool has a problem, try another. You may have to re-create a lock file and commit it to Nixpkgs.
- Some lock files contain a particular version of a package that has been pulled off npm for some reason. In that case, you can recreate the lock file (by removing the original and running `npm install`, `yarn`, etc.) and commit this to Nixpkgs.

### Use the project's `package.json` {#javascript-upstream-package-json}

Exceptions to this rule are:

- Sometimes the project assumes some dependencies are installed globally. Add them to the `package.json` manually (`yarn add xxx` or `npm install xxx`). Run locally installed CLI tools with `npx`, for example `npx postcss`. That is how you call them in the phases.
- Sometimes there is a version conflict between some dependency requirements. In that case you can fix a version by removing the `^`.
- Sometimes a script in `package.json` does not work as is. It might call a CLI tool that is not available, or `cd` into a directory with a different `package.json`, which is common with workspaces. Read what the script does. Reproduce it in the build phases. For example, a `build` script may call `build:ui` and `build:server` in turn. If one fails, split them into separate steps.

  ```sh
  yarn build:ui
  yarn build:server
  # OR
  npm run build:ui
  npm run build:server
  ```

  When you need to override `package.json`, it is best to use the one from the project and make explicit overrides. Here is an example:

  ```nix
  {
    patchedPackageJSON = final.runCommand "package.json" { } ''
      ${jq}/bin/jq '.version = "0.4.0" |
        .devDependencies."@jsdoc/cli" = "^0.2.5"
        ${sonar-src}/package.json > $out
    '';
  }
  ```

  You still need to commit the modified version of the lock files, but at least the overrides are explicit for everyone to see.

### Use `node_modules` directly {#javascript-using-node_modules}

Each tool has an abstraction to build the node_modules (dependencies) directory.
You 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).
The `node_modules` abstraction can also be used to build some web framework frontends.
For an example of this, see how [plausible](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/pl/plausible/package.nix) is built.
Then, when building the frontend, you can symlink the `node_modules` directory.

## Tool-specific instructions {#javascript-tool-specific}

### buildNpmPackage {#javascript-buildNpmPackage}

`buildNpmPackage` packages npm-based projects in Nixpkgs without the use of an auto-generated dependencies file.
It uses npm's cache. It builds a reproducible cache of the project's dependencies and points npm at it.

Here's an example:

```nix
{
  lib,
  buildNpmPackage,
  fetchFromGitHub,
}:

buildNpmPackage (finalAttrs: {
  pname = "flood";
  version = "4.7.0";

  src = fetchFromGitHub {
    owner = "jesec";
    repo = "flood";
    tag = "v${finalAttrs.version}";
    hash = "sha256-BR+ZGkBBfd0dSQqAvujsbgsEPFYw/ThrylxUbOksYxM=";
  };

  npmDepsHash = "sha256-tuEfyePwlOy2/mOPdXbqJskO6IowvAP4DWg8xSZwbJw=";

  # The prepack script runs the build script, which we'd rather do in the build phase.
  npmPackFlags = [ "--ignore-scripts" ];

  NODE_OPTIONS = "--openssl-legacy-provider";

  meta = {
    description = "Modern web UI for various torrent clients with a Node.js backend and React frontend";
    homepage = "https://flood.js.org";
    license = lib.licenses.gpl3Only;
    maintainers = with lib.maintainers; [ winter ];
  };
})
```

In the default `installPhase` set by `buildNpmPackage`, it uses `npm pack --json --dry-run` to decide what files to install. They go in `$out/lib/node_modules/$name/`, where `$name` is the `name` string in the package's `package.json`.
Additionally, the `bin` and `man` keys in the source's `package.json` are used to decide what binaries and manpages are supposed to be installed.
If these are not defined, `npm pack` may miss some files, and no binaries are produced.

#### Arguments {#javascript-buildNpmPackage-arguments}

* `npmDepsHash`: The output hash of the dependencies for this project. Can be calculated in advance with [`prefetch-npm-deps`](#javascript-buildNpmPackage-prefetch-npm-deps).
* `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.
* `npmBuildScript`: The script to run to build the project. Defaults to `"build"`.
* []{#javascript-buildNpmPackage-npmWorkspace} `npmWorkspace`: The workspace directory within the project to build and install.
* `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.
* `dontNpmInstall`: Option to disable running `npm install`. Defaults to `false`. Alternatively, setting `installPhase` explicitly also disables this.
* []{#javascript-buildNpmPackage-npmFlags} `npmFlags`: Flags to pass to all npm commands.
* `npmInstallFlags`: Flags to pass to `npm ci`.
* `npmBuildFlags`: Flags to pass to `npm run ${npmBuildScript}`.
* `npmPackFlags`: Flags to pass to `npm pack`.
* `npmPruneFlags`: Flags to pass to `npm prune`. Defaults to the value of `npmInstallFlags`.
* `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`.
* `nodejs`: The `nodejs` package to build against, using the corresponding `npm` shipped with that version of `node`. Defaults to `pkgs.nodejs`.
* `npmDeps`: The dependencies used to build the npm package. Especially useful to not have to recompute workspace dependencies.

#### prefetch-npm-deps {#javascript-buildNpmPackage-prefetch-npm-deps}

`prefetch-npm-deps` is a Nixpkgs package that calculates the hash of the dependencies of an npm project ahead of time.

```console
$ ls
package.json package-lock.json index.js
$ prefetch-npm-deps package-lock.json
...
sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
```

#### fetchNpmDeps {#javascript-buildNpmPackage-fetchNpmDeps}

`fetchNpmDeps` is a Nix function that requires the following mandatory arguments:

- `src`: A directory or tarball with a `package-lock.json` file
- `hash`: The output hash of the dependencies defined in `package-lock.json`.

It returns a derivation with all `package-lock.json` dependencies downloaded into `$out/`, usable as an npm cache.

#### importNpmLock {#javascript-buildNpmPackage-importNpmLock}

This function replaces the npm dependency references in `package.json` and `package-lock.json` with paths to the Nix store.
How each dependency is fetched can be customized with the `fetcherOpts` argument.

This is a simpler and more convenient alternative to [`fetchNpmDeps`](#javascript-buildNpmPackage-fetchNpmDeps) for managing npm dependencies in Nixpkgs.
There is no need to specify a `hash`, since it relies entirely on the integrity hashes already present in the `package-lock.json` file.

##### Inputs {#javascript-buildNpmPackage-inputs}

- `npmRoot`: Path to the package directory containing the source tree.
  If this is omitted, the `package` and `packageLock` arguments must be specified instead.
- `package`: Parsed contents of `package.json`
- `packageLock`: Parsed contents of `package-lock.json`
- `pname`: Package name
- `version`: Package version
- `fetcherOpts`: An attribute set of arguments forwarded to the underlying fetcher.

It returns a derivation with a patched `package.json` and `package-lock.json` with all dependencies resolved to Nix store paths.

:::{.note}
`npmHooks.npmConfigHook` cannot be used with `importNpmLock`.
Use `importNpmLock.npmConfigHook` instead.
:::

:::{.example}

##### `pkgs.importNpmLock` usage example {#javascript-buildNpmPackage-example}
```nix
{ buildNpmPackage, importNpmLock }:

buildNpmPackage {
  pname = "hello";
  version = "0.1.0";
  src = ./.;

  npmDeps = importNpmLock { npmRoot = ./.; };

  npmConfigHook = importNpmLock.npmConfigHook;
}
```
:::

:::{.example}
##### `pkgs.importNpmLock` usage example with `fetcherOpts` {#javascript-buildNpmPackage-example-fetcherOpts}

`importNpmLock` uses the following fetchers:

- `pkgs.fetchurl` for `http(s)` dependencies
- `fetchGit` for `git` dependencies

It is possible to provide additional arguments to individual fetchers as needed:

```nix
{ buildNpmPackage, importNpmLock }:

buildNpmPackage {
  pname = "hello";
  version = "0.1.0";
  src = ./.;

  npmDeps = importNpmLock {
    npmRoot = ./.;
    fetcherOpts = {
      # Pass 'curlOptsList' to 'pkgs.fetchurl' while fetching 'axios'
      "node_modules/axios" = {
        curlOptsList = [ "--verbose" ];
      };
    };
  };

  npmConfigHook = importNpmLock.npmConfigHook;
}
```
:::

#### importNpmLock.buildNodeModules {#javascript-buildNpmPackage-importNpmLock.buildNodeModules}

`importNpmLock.buildNodeModules` returns a derivation with a pre-built `node_modules` directory, as imported by `importNpmLock`.

This is to be used together with `importNpmLock.hooks.linkNodeModulesHook` to support `nix-shell`/`nix develop` development workflows.

It accepts an argument with the following attributes:

`npmRoot` (Path; optional)
: Path to the package directory containing the source tree. If not specified, the `package` and `packageLock` arguments must both be specified.

`package` (Attrset; optional)
: Parsed contents of `package.json`, as returned by `lib.importJSON ./my-package.json`. If not specified, the `package.json` in `npmRoot` is used.

`packageLock` (Attrset; optional)
: Parsed contents of `package-lock.json`, as returned by `lib.importJSON ./my-package-lock.json`. If not specified, the `package-lock.json` in `npmRoot` is used.

`derivationArgs` (`mkDerivation` attrset; optional)
: Arguments passed to `stdenv.mkDerivation`

For example:

```nix
pkgs.mkShell {
  packages = [
    importNpmLock.hooks.linkNodeModulesHook
    nodejs
  ];

  npmDeps = importNpmLock.buildNodeModules {
    npmRoot = ./.;
    inherit nodejs;
  };
}
```
creates a development shell where a `node_modules` directory is created and packages are symlinked to the Nix store when activated.

:::{.note}
Commands like `npm install` and `npm add` that write packages and executables need to be used with `--package-lock-only`.

This means `npm` installs dependencies by writing into `package-lock.json` without modifying the `node_modules` folder. It installs by reloading the devShell.
This gives the `nix shell` near-exclusive ownership over your `node_modules` folder.

Set `package-lock-only = true` in your project-local [`.npmrc`](https://docs.npmjs.com/cli/v11/configuring-npm/npmrc).
:::

### corepack {#javascript-corepack}

This package puts the corepack wrappers for pnpm and yarn in your PATH, and they honor the `packageManager` setting in the `package.json`.

### pnpm {#javascript-pnpm}

pnpm is available as the top-level package `pnpm`. Additionally, there are variants pinned to certain major versions, like `pnpm_9`, `pnpm_10`, `pnpm_10_29_2` and `pnpm_11`, which support different sets of lock file versions.

When 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` prepares the build environment to install the pre-fetched dependencies store. The example below uses the fetcher and setup hook for a package that has `package.json` and `pnpm-lock.yaml`:

There is also the [`pnpmBuildHook`](#pnpm-build-hook) for building packages with `pnpm`, as seen in [](#ex-pnpm-build-hook).

```nix
{
  fetchPnpmDeps,
  nodejs,
  pnpm_11,
  pnpmConfigHook,
  stdenv,
}:
let
  # It is recommended to pin pnpm to a major version, due to regular breaking changes in the store format
  # The latest major version is always available under `pkgs.pnpm`
  # Optionally override pnpm to use a custom nodejs version
  # Make sure that the same nodejs version is referenced in nativeBuildInputs
  # pnpm = pnpm_11.override { nodejs = nodejs_24; };
  pnpm = pnpm_11;
in
stdenv.mkDerivation (finalAttrs: {
  pname = "foo";
  version = "0-unstable-1980-01-01";

  src = {
    #...
  };

  nativeBuildInputs = [
    nodejs # in case scripts are run outside of a pnpm call
    pnpmConfigHook
    pnpm # At least required by pnpmConfigHook, if not other (custom) phases
  ];

  pnpmDeps = fetchPnpmDeps {
    inherit (finalAttrs) pname version src;
    inherit pnpm;
    fetcherVersion = 4;
    hash = "...";
  };
})
```

Use a pinned version of pnpm (for example `pnpm_9` or `pnpm_10`) to increase reproducibility. An older version may be required if the package needs a certain lock file version. To do so, pass the `pnpm` argument to `fetchPnpmDeps`. Then override the `pnpm` arg in `pnpmConfigHook`. Here are the changes in the example above to use a pinned pnpm version:

<!-- TODO: Does splicing still work when overriding in nativeBuildInputs here? -->

```diff
 {
   fetchPnpmDeps,
   nodejs,
-  pnpm,
+  pnpm_10,
   pnpmConfigHook,
   stdenv,
 }:
+let
+  # Optionally override pnpm to use a custom nodejs version
+  # Make sure that the same nodejs version is referenced in nativeBuildInputs
+  # pnpm = pnpm_10.override { nodejs-slim = nodejs-slim_22; };
+in
 stdenv.mkDerivation (finalAttrs: {
   pname = "foo";
   version = "0-unstable-1980-01-01";

   src = {
     #...
   };

   nativeBuildInputs = [
     nodejs # in case scripts are run outside of a pnpm call
     pnpmConfigHook
-    pnpm # At least required by pnpmConfigHook, if not other (custom) phases
+    pnpm_10 # At least required by pnpmConfigHook, if not other (custom) phases
   ];

   pnpmDeps = fetchPnpmDeps {
     inherit (finalAttrs) pname version src;
+    pnpm = pnpm_10;
     fetcherVersion = 4;
     hash = "...";
   };
 })
```

In 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`).

`pnpmConfigHook` supports adding additional `pnpm install` flags via `pnpmInstallFlags` which can be set to a Nix string array:

```nix
{
  # ...
  pnpmDeps = fetchPnpmDeps {
    # ...
    inherit (finalAttrs) pnpmInstallFlags;
  };

  pnpmInstallFlags = [ "--shamefully-hoist" ];
}
```

If needed, set `dontPnpmConfigure = true;` to fully disable `pnpmConfigHook` without removing it from inputs manually.

#### Dealing with `sourceRoot` {#javascript-pnpm-sourceRoot}

If the pnpm project is in a subdirectory, you can define `sourceRoot` or `setSourceRoot` for `fetchPnpmDeps`.
If `sourceRoot` is different between the parent derivation and `fetchPnpmDeps`, you have to set `pnpmRoot` to effectively be the same location as it is in `fetchPnpmDeps`.

Assuming the directory structure below, you can define `sourceRoot` and `pnpmRoot`:

```
.
├── frontend
│   ├── ...
│   ├── package.json
│   └── pnpm-lock.yaml
└── ...
```

```nix
{
  # ...
  pnpmDeps = fetchPnpmDeps {
    # ...
    sourceRoot = "${finalAttrs.src.name}/frontend";
  };

  # by default the working directory is the extracted source
  pnpmRoot = "frontend";
}
```

#### pnpm workspaces {#javascript-pnpm-workspaces}

For a pnpm workspace, set `pnpmWorkspaces = [ "<workspace project name 1>" "<workspace project name 2>" ]` in your `fetchPnpmDeps` call. pnpm then installs only the dependencies for those workspace packages.

For example:

```nix
{
  # ...
  pnpmWorkspaces = [ "@astrojs/language-server" ];
  pnpmDeps = fetchPnpmDeps {
    #...
    inherit (finalAttrs) pnpmWorkspaces;
  };
}
```

The above would make `fetchPnpmDeps` call only install dependencies for the `@astrojs/language-server` workspace package.
You do not need to set `sourceRoot` to make this work.

For these projects, build with `pnpm --filter=<pnpm workspace name> build`, because `npmHooks.npmBuildHook` may not work. The example below fits most workspace projects:

```nix
{
  buildPhase = ''
    runHook preBuild

    pnpm --filter=@astrojs/language-server build

    runHook postBuild
  '';
}
```

#### Additional pnpm commands and settings {#javascript-pnpm-extraCommands}

If you require setting an additional pnpm configuration setting (such as `dedupe-peer-dependents` or similar),
set `prePnpmInstall` to the right commands to run. For example:

```nix
{
  prePnpmInstall = ''
    pnpm config set dedupe-peer-dependents false
  '';
  pnpmDeps = fetchPnpmDeps {
    inherit (finalAttrs) prePnpmInstall;
    # ...
  };
}
```

In this example, `prePnpmInstall` runs in both `pnpmConfigHook` and the `fetchPnpmDeps` builder.

#### pnpm `fetcherVersion` {#javascript-pnpm-fetcherVersion}

This is the version of the output of `fetchPnpmDeps`. Use `4` for new packages:

```nix
{
  # ...
  pnpmDeps = fetchPnpmDeps {
    # ...
    fetcherVersion = 4;
    hash = "..."; # clear this hash and generate a new one
  };
}
```

When upgrading to a newer `fetcherVersion`, you need to regenerate the hash.

This variable ensures that we can make changes to the output of `fetchPnpmDeps` without breaking existing hashes.
Changes can include workarounds or bug fixes to existing pnpm issues.

##### Version history {#javascript-pnpm-fetcherVersion-versionHistory}

Version 3 is the minimum supported value. Versions 1 and 2 were removed in the 26.11 release; packages that still use them fail to evaluate and must migrate to `fetcherVersion = 3` (or later) and regenerate their hashes.

- 1: Initial version, nothing special. (removed in 26.11)
- 2: [Ensure consistent permissions](https://github.com/NixOS/nixpkgs/pull/422975) (removed in 26.11)
- 3: [Build a reproducible tarball](https://github.com/NixOS/nixpkgs/pull/469950)
- 4: [Dump SQLite database to an SQL file](https://github.com/NixOS/nixpkgs/pull/522703)

### Yarn {#javascript-yarn}

Yarn-based projects use a `yarn.lock` file instead of a `package-lock.json` to pin dependencies.

To package Yarn-based applications, you need to distinguish by the version pointers in the `yarn.lock` file. See the following sections.

#### Yarn v1 {#javascript-yarn-v1}

Yarn v1 lockfiles contain a comment `# yarn lockfile v1` at the beginning of the file.

Nixpkgs 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:

- `yarnConfigHook`: Fetches the dependencies from the offline cache and installs them into `node_modules`.
- `yarnBuildHook`: Runs `yarn build` or a specified `yarn` command that builds the project.
- `yarnInstallHook`: Runs `yarn install --production` to prune dependencies and installs the project into `$out`.

An example usage of the above attributes is:

```nix
{
  lib,
  stdenv,
  fetchFromGitHub,
  fetchYarnDeps,
  yarnConfigHook,
  yarnBuildHook,
  yarnInstallHook,
  nodejs,
}:

stdenv.mkDerivation (finalAttrs: {
  pname = "...";
  version = "...";

  src = fetchFromGitHub {
    owner = "...";
    repo = "...";
    tag = "v${finalAttrs.version}";
    hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };

  yarnOfflineCache = fetchYarnDeps {
    yarnLock = finalAttrs.src + "/yarn.lock";
    hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };

  nativeBuildInputs = [
    yarnConfigHook
    yarnBuildHook
    yarnInstallHook
    # Needed for executing package.json scripts
    nodejs
  ];

  meta = {
    # ...
  };
})
```

##### `yarnConfigHook` arguments {#javascript-yarnconfighook}

By 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`.

##### `yarnBuildHook` arguments {#javascript-yarnbuildhook}

This 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:

- `yarnBuildScript`: Sets a different `yarn --offline` subcommand (defaults to `build`).
- `yarnBuildFlags`: Single string list of additional flags to pass the above command, or a Nix list of such additional flags.

##### `yarnInstallHook` arguments {#javascript-yarninstallhook}

To install the package, `yarnInstallHook` uses both `npm` and `yarn` to clean up 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:

- `yarnKeepDevDeps`: Disables the removal of devDependencies from `node_modules` before installation.

#### Yarn Berry v3/v4 {#javascript-yarn-v3-v4}
Yarn Berry (v3 / v4) versions have similar formats. They start with blocks like these:

```yaml
__metadata:
  version: 6
  cacheKey: 8[cX]
```

```yaml
__metadata:
  version: 8
  cacheKey: 10[cX]
```

For these packages, we have some helpers exposed under the respective `yarn-berry_3` and `yarn-berry_4` packages:

- `yarn-berry-fetcher`
- `fetchYarnBerryDeps`
- `yarnBerryConfigHook`

Explicitly pin the major version. For example, capture the `yarn-berry_Xn` argument and re-define it as a `yarn-berry` `let` binding.

```nix
{
  stdenv,
  nodejs,
  yarn-berry_4,
}:

let
  yarn-berry = yarn-berry_4;

in
stdenv.mkDerivation (finalAttrs: {
  pname = "foo";
  version = "0-unstable-1980-01-01";

  src = {
    #...
  };

  nativeBuildInputs = [
    nodejs
    yarn-berry.yarnBerryConfigHook
  ];

  offlineCache = yarn-berry.fetchYarnBerryDeps {
    inherit (finalAttrs) src;
    hash = "...";
  };
})
```

##### `yarn-berry_X.fetchYarnBerryDeps` {#javascript-fetchYarnBerryDeps}
`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).

To produce the `hash` argument for the `fetchYarnBerryDeps` call, run `yarn-berry-fetcher prefetch`:

```console
$ yarn-berry-fetcher prefetch </path/to/yarn.lock> [/path/to/missing-hashes.json]
```

This prints the hash to stdout. Use it in update scripts to recalculate the hash for a new `yarn.lock`.

##### `yarn-berry_X.yarnBerryConfigHook` {#javascript-yarnBerryConfigHook}
`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.

Internally, this uses a patched version of Yarn to ensure git dependencies are re-packed and any attempted downloads fail immediately.

##### Patching the project's `package.json` or `yarn.lock` files {#javascript-yarnBerry-patching}
In case patching the project's `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`).

##### Missing hashes in the `yarn.lock` file {#javascript-yarnBerry-missing-hashes}
Unfortunately, `yarn.lock` files do not include hashes for optional/platform-specific dependencies. This is [by design](https://github.com/yarnpkg/berry/issues/6759).

To compensate for this, run the `yarn-berry-fetcher missing-hashes` subcommand to produce all missing hashes. These are stored in a `missing-hashes.json` file, which needs to be passed to both the build itself, as well as the `fetchYarnBerryDeps` helper:

```nix
{
  stdenv,
  nodejs,
  yarn-berry_4,
}:

let
  yarn-berry = yarn-berry_4;

in
stdenv.mkDerivation (finalAttrs: {
  pname = "foo";
  version = "0-unstable-1980-01-01";

  src = {
    #...
  };

  nativeBuildInputs = [
    nodejs
    yarn-berry.yarnBerryConfigHook
  ];

  missingHashes = ./missing-hashes.json;
  offlineCache = yarn-berry.fetchYarnBerryDeps {
    inherit (finalAttrs) src missingHashes;
    hash = "...";
  };
})
```

## Outside Nixpkgs {#javascript-outside-nixpkgs}

There are some other tools available, which are written in the Nix language.
These can't be used inside Nixpkgs because they require [Import From Derivation](#ssec-import-from-derivation), which is not allowed in Nixpkgs.

If you are packaging something outside Nixpkgs, consider the following:

### npmlock2nix {#javascript-npmlock2nix}

[npmlock2nix](https://github.com/nix-community/npmlock2nix) aims at building `node_modules` without code generation. It hasn't reached v1 yet; the API may change.

#### Pitfalls {#javascript-npmlock2nix-pitfalls}

There are some [problems with npm v7](https://github.com/tweag/npmlock2nix/issues/45).

### nix-npm-buildpackage {#javascript-nix-npm-buildpackage}

[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 may change. It supports both `package-lock.json` and yarn.lock.

#### Pitfalls {#javascript-nix-npm-buildpackage-pitfalls}

There are some [problems with npm v7](https://github.com/serokell/nix-npm-buildpackage/issues/33).