nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
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).