Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/privatenumber/link

🔗 A better `npm link`
https://github.com/privatenumber/link

Last synced: 4 days ago
JSON representation

🔗 A better `npm link`

Awesome Lists containing this project

README

        





npx link



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






Already a sponsor? Join the discussion in the Development repo!

## 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
```

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.

Here's an example

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.

_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
```

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

2. Link the _Dependency package_

```sh
npx link publish
```

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


Why hard links instead of symbolic links?

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_.

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