<p align="center">
	<img width="180" src=".github/logo.webp">
</p>
<h1 align="center">
	<sup>npx link</sup>
	<br>
	<a href="https://npm.im/link"><img src="https://badgen.net/npm/v/link"></a> <a href="https://npm.im/link"><img src="https://badgen.net/npm/dm/link"></a>
</h1>

A safer and enhanced version of [`npm link`](https://docs.npmjs.com/cli/v8/commands/npm-link).

Why is `npm link` unsafe? Read the [blog post](https://hirok.io/posts/avoid-npm-link).

### Features
- 🔗 Link dependencies without removing previous links
- 🛡 Only resolves to local paths
- 🔥 Config file quickly linking multiple packages
- 💫 Deep linking for quickling linking multilple packages

<br>

<p align="center">
	<a href="https://github.com/sponsors/privatenumber/sponsorships?tier_id=398771"><img width="412" src="https://raw.githubusercontent.com/privatenumber/sponsors/master/banners/assets/donate.webp"></a>
	<a href="https://github.com/sponsors/privatenumber/sponsorships?tier_id=397608"><img width="412" src="https://raw.githubusercontent.com/privatenumber/sponsors/master/banners/assets/sponsor.webp"></a>
</p>
<p align="center"><sup><i>Already a sponsor?</i> Join the discussion in the <a href="https://github.com/pvtnbr/link">Development repo</a>!</sup></p>

## Terminology

- **Dependency package**

	The package getting linked. This is usually a library.

- **Consuming package**

	The project you want to link the _Dependency package_ as a dependency of. This is usually an application.

	`consuming-package/node_modules/dependency-package` → `dependency-package`


## Usage

### Linking a package

From the _Consuming package_ directory, link the _Dependency package_:

```sh
npx link <dependency-package-path>
```

This creates a symbolic link inside the `node_modules` of _Consuming package_, referencing the _Dependency package_.


> **🛡️ Secure linking**
>
> Unlike `npm link`, it doesn't install the _Dependency package_ globally or re-install project dependencies.

### Publish mode

Using symbolic links may not replicate the exact environment you get from a standard `npm install`. This discrepancy primarily arises from symlinked packages retaining their development `node_modules` directory. This can lead to issues, especially when multiple packages depend on the same library.

<details>
	<summary>Here's an example</summary>
	<br>

In a production environment, `npm install` detects common dependencies and installs only one instance of a shared dependency. However, when there's a symbolic link to the development directory of a dependency, separate copies of those dependencies are resolved from the development `node_modules`.

Let's say there's an _App A_ with a dependency on _Package B_, and they both depend on _Library C_:

- Production environment

	`npm install` detects that both _App A_ and _Package B_ depends on _Library C_, and only installs one copy of _Library C_ for them to share.

- Symbolic link environment

	_App A_ has its copy of _Library C_, and _Package B_ also has its development copy of _Library C_—possibly with different versions. Consequently, when you run the application, it will load two different versions of _Library C_, leading to unexpected outcomes.

</details>

_Publish mode_ helps replicate the production environment in your development setup.

#### Setup instructions

1. In the _Dependency package_, run `npm pack` to create a tarball:

	```sh
	cd dependency-package-path
	npm pack
	```

	This generates a tarball (`.tgz`) file in the current directory. Installing from this simulates the conditions of a published package without actually publishing it.

	> **Tip:** You can skip this step if this dependency is already installed from npm and there are no changes to the dependency's `package.json`

2. In the _Consuming package_

	1. Install the Dependency tarball from _Step 1_

		```sh
		npm install --no-save <dependency-tarball-path>
		```

		This sets up the same `node_modules` tree used in a production environment.

	2. Link the _Dependency package_

		```sh
		npx link publish <dependency-package-path>
		```

		This creates hard links in `node_modules/dependency` to the specific publish assets of the _Dependency package_.

		<details>
		<summary><em>Why hard links instead of symbolic links?</em></summary>
		<br>

		Another issue with the symlink approach is that Node.js, and popular bundlers, looks up the `node_module` directory relative to a module's realpath rather than the import path (symlink path). By using hard links, we can prevent this behavior and ensure that the `node_modules` directory is resolved using the production tree we set up in _Step 2_.
		</details>

4. Start developing!

	Any changes you make to the _Dependency package_ will be reflected in the `node_modules` directory of the _Consuming package_.

	> **Note:** If the _Dependency package_ emits new files, you'll need to re-run `npx link publish <dependency-package-path>` to create new hard links.
	
### Configuration file

Create a `link.config.json` (or `link.config.js`) configuration file at the root of the _Consuming package_ to automatically setup links to multiple _Dependency packages_.

Example _link.config.json_:
```json5
{
    "packages": [
        "/path/to/dependency-path-a",
        "../dependency-path-b",
    ],
}
```

The configuration has the following type schema:
```ts
type LinkConfig = {

    // Whether to run `npx link` on dependency packages with link.config.json
    deepLink?: boolean

    // List of dependency packages to link
    packages?: string[]
}
```

> **Note:** It's not recommended to commit this file to source control since this is for local development with local paths.


To link the dependencies defined in `link.config.json`, run:
```sh
npx link
```

### Deep linking

By default, `npx link` only links packages in the _Consuming package_. However, there are cases where the _Dependency packages_ also needs linking setup.

Deep linking recursively runs link on every linked dependency that has a `link.config.json` file.

Enable with the `--deep` flag or `deepLink` property in `link.config.json`.

```sh
npx link --deep
```

## FAQ

### Why should I use `npx link` over `npm link`?
Because `npm link` [is complicated and dangerous to use](https://hirok.io/posts/avoid-npm-link). And `npx link` offers more features such as _Publish mode_.

### How do I remove the links?
Run `npm install` and it should remove them.

`npm install` enforces the integrity of `node_modules` by making sure all packages are correctly installed. Reverting the links is a side effect of this.

### Why does `npx link` point to `ln`?

You must use npx v7 or higher. Check the version with `npx -v`.

In the obsolete npx v6, local binaries take precedence over npm modules so  `npx link` can point to the native `link`/`ln` command:
```
$ npx link
usage: ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file [target_file]
       ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file ... target_dir
       link source_file target_file
```

To work around this, install `link` globally first:
```sh
$ npm i -g link
$ npx link
```

## Related

- [`npx ci`](https://github.com/privatenumber/ci) - A better `npm ci`.


## Sponsors

<p align="center">
	<a href="https://github.com/sponsors/privatenumber">
		<img src="https://cdn.jsdelivr.net/gh/privatenumber/sponsors/sponsorkit/sponsors.svg">
	</a>
</p>
