---
# System prepended metadata

title: Package Distributions

---

# Package Distributions

## Team Meeting

### Ideas
- registry-side dist/target?
    - Blocked by registry/product investment
- [ ] **Action**: "conditonal everything except deps"
- [ ] **Action**: explore "pre-fail optional deps"
    - ie. distributions implemented **not** as an override but rather link post/during reify
- [ ] **Action**: @isaac will engage on Yarn's RFC

--- 

## Goals

- [ ] Allow maintainers to define/distribute different packages under a singular name based on conditional logic for the consuming environment's platform/engines
- [ ] Limit the amount of net-new concepts & scope (ie. try to use existing paradigms & building blocks as much as possible)
- [ ] Make this feature opt-in (at least initially)

### Current Work-Around

The current work-around is to define all of your package distributions/variants as `optionalDeps` & rely on the environment failing to install them - then testing for the dep that got properly reified.

```json=
{
  "name": "foo",
  "optionalDependencies": {
    "win-foo": "...",
    "darwin-foo": "..."
  }
}
```

### Initial Proposal

- Introduce a new field called `distributions` which will be utilized by `npm` to determine how/what `overrides` to create in a consuming project's `package.json`.

### Implementation

- `distributions` be an `Array` of objects defining conditions in which to **override** the root/default package in the consuming project's `package.json` 
- Conditions should cascade, so maintaining order is important here (ie. this must be an `Array`)
- `engines` & `platform` fields should respect the existing shape/understanding `npm` is already aware of
- `override` will define a package spec to be used in the `overrides` definitions of the consuming project when the conditions for `engines` & `platform` are met

```json=
{ 
    "name": "foo-native",
    "distributions": [
        {
            "engines": {
                "node": "^10",
            },
            "platform": "win32",
            "override": "foo-native-win32-10"
        }, {
            "override": "foo-native-default"
        }
    ],
    "engines": {
        "npm": "^7.12.0"
    }
}
```

#### New flags/config

- `--with-distributions` Defaults to `false` - when `true`, will respect `distributions` defined in a package's `package.json`
- `--save-overrides` Defaults to `false` - when `true`, will write `overrides` into `package.json`
  - will also be useful for the [**Audit Overrides** RFC that has already been accepted](https://github.com/npm/rfcs/blob/latest/accepted/0037-audit-overrides.md)


#### Example `install`
```bash=
npm install foo-native --with-distributions --save-overrides
```

```json=
{
    "name": "myapp",
    "dependencies": {
        "foo": "^1"
    },
    "overrides": {
        "foo@^1": {
            "foo-native@^1": "foo-native-darwin-10@^1"
        }
    }
}
```

### References & Links
- Yarn's **Package Variants** Proposal: https://github.com/yarnpkg/berry/issues/2751

### Parking Lot

#### Best practice for maintainers to ensure they're consumers are using `distributions` properly

A best practice we could recommend, to avoid confusion, is to set `engines` value for `npm` & educate maintainers/consumers on `--engines-strict`

```json=
{
  ...
  "engines": {
      "npm": "^7.12.0"
  }
  ...
}
```

```bash=
npm install --engines-strict
```

#### Best practice to support widest audience

Utilize the default `optionalDependencies` environment fallback w/ an optional shim while also still defining `distributions`.

```json=
{ 
    "name": "foo-native",
    "main": "lib/shim.js",
    "dependencies": {
        "foo-native-win32-10": "*",
        "foo-native-source": "*",
        "@npmcli/optional-shim": "^1",
        "debug": "^4"
    },
    "optionalDependencies": {
        "foo-native-win32-10": "*",
        "foo-native-source": "*"
    },
    "distributions": [
        {
            "engines": {
                "node": "^10",
            },
            "platform": "win32",
            "override": "foo-native-win32-10"
        }, {
            "override": "foo-native-source"
        }
    ],
    "engines": {
        "npm": "^7.12.0"
    }
}
```

#### Concept: use `dependencyMeta` vs. `distributions`

Zoltan (pnpm) & Mael (Yarn) have started talking more about `dependencyMeta` to store information about your dependencies; This seems to only serve to further nest information that can live in the top level of a `package.json` since this is "metadata" is directly associated with that package. `dependencyMeta` is better served at the consumer level where we already have `overrides` that can solve this problem.

`depdencyMeta` may be useful in the future, but not necessarily for this feature.

## Alternative: "choose and link"

This avoids the "different platforms result in different trees" problem.

```json=
{
  "name": "foo",
  "version": "1.2.3",
  "main": "index.js",
  "scripts": {
    "preinstall": "node-gyp rebuild"
  },
  "distributions": [
    {
      "engines": {
        "node": "10"
      },
      "platform": "win32",
      "package": "foo-native-win32-10@1.x"
    },
    {
      "platform": "linux",
      "arch": "x64",
      "package": "foo-native-linux-x64@2.x"
    },
    "..."
  ]
}
```

### buildIdealTree

* Place _all_ distribution targets as peers of the `foo` package
* Any that cannot be placed are not placed (eg, package not found, cannot be placed for conflicting peer deps, etc.), similar to `optionalDependencies`
* Record status of `idealTree` placed distributions of the main "foo" package (?somehow?), along with their selection criteria.

Thus the idealTree is not platform-specific.

### reify

* Choose one distribution of the main package, and reify that.
* Do not reify the others, or any of their dependencies (unless they are required to meet another dependency in the tree.)
* Reify the main package as a `Link` to the chosen distribution.

### publisher

* Publish the main package with the source, pointing to distribution package specifiers and selection criteria.
* Publish pre-built distributions as needed.

This is amenable to publishing distributions post-hoc as a CI build.  If any fail to build and publish, no matter, they will simply fail to install, and fall back to the main package.

Constraints: distributions cannot conflict with one another or other dependencies within the tree.  (So, not possible to have one distribution with a peer on `react@15` and another on `react@16`, for example.)

### consumer

* Add `foo` as a dependency.
* Pre-built distributions will be added to lockfile.
* `require('foo')` will return the appropriate context-specific distribution if one was found successfully, or the original package if it was not.

The package-lock.json file will include _all_ the distributions (as it would for `optionalDependencies` today), but only one of these will be reified on disk.  No chance of an exponential explosion in lockfile size, as we calculate every possible combination of distribution matrices.

### Polyfill Use Case

While this is most useful for slow and costly binary builds, it is also interesting for providing polyfills for node features!

```json=
{
  "name": "fs-readdir",
  "version": "fs.readdir() guaranteed to have withFileTypes:true support",
  "distributions": [
    {
      "engines": { "node": "<v10.11.0"},
      "package": "fs-readdir-polyfill@1"
    },
    {
      "package": "fs-readdir-native@1"
    }
  ]
}
```

```json=
{
  "name": "fs-readdir-polyfill",
  "version": "1.2.3",
  "description": "polyfill the fs.readdir withFileTypes using fs.stat"
}
```


```json=
{
  "name": "fs-readdir-native",
  "version": "1.2.3",
  "description": "just export require('fs').readdir"
}
```

Of course, we could just as easily have the `fs-readdir-polyfill` define `fs-readdir-native` as a distribution when `"engines": {"node": ">=10.11.0" }`.