Vendoring

# Pinning packages

bin/importmap pin resolves a package on a CDN, downloads it to vendor/javascript and writes the pin.

## Vendoring from a CDN

`bin/importmap` pins, unpins and updates npm packages in your import map. By default it asks [JSPM](https://jspm.org)'s API to resolve a package and its dependencies, downloads each file and adds the pins to `config/importmap.rb`:

```console
$ ./bin/importmap pin react
Pinning "react" to vendor/javascript/react.js via download from https://ga.jspm.io/npm:react@19.1.0/index.js
```

```ruby
pin "react" # @19.1.0
```

The files land in `vendor/javascript`, which you check into source control; the asset pipeline serves them like any other asset. A package with a `/` in its name is stored with `--` instead (`@hotwired/stimulus` becomes `@hotwired--stimulus.js`) and the pin gets a `to:` pointing at that file.

A version goes in the spec: `pin react@18`, `pin luxon@3.7.2`. A subpath works the way it does on npm: `pin apexcharts/core`.

To remove a downloaded pin and its file:

```console
$ ./bin/importmap unpin react
Unpinning and removing "react"
```

## Choosing a CDN

Other CDNs are one flag away. `--from` takes `jspm` (the default), `unpkg`, `jsdelivr`, `esm.sh`, `skypack`, or `esm.run` for jsDelivr's bundled builds (see [esm.run bundles](https://importmap-plus.zoolutions.llc/docs/esm-run)):

```console
$ ./bin/importmap pin react --from unpkg
Pinning "react" to vendor/javascript/react.js via download from https://unpkg.com/react@19.1.0/index.js

$ ./bin/importmap pin react --from jsdelivr
Pinning "react" to vendor/javascript/react.js via download from https://cdn.jsdelivr.net/npm/react@19.1.0/index.js
```

The CDN is recorded in the pin comment when it isn't jspm, and later commands go back to it — an unpkg download stays on unpkg through `update` and `pristine`. Pass `--from` again to move a package to another CDN. See [Provenance](https://importmap-plus.zoolutions.llc/docs/provenance).

## Pinning to a remote URL

--remote pins the CDN URL instead of vendoring a download.

```console
$ ./bin/importmap pin react --remote
Pinning "react" to https://ga.jspm.io/npm:react@19.1.0/index.js
```

```ruby
pin "react", to: "https://ga.jspm.io/npm:react@19.1.0/index.js"
```

A remote pin is respected from then on, no flag needed. When the package is pinned again or picked up by `update` — directly or as a dependency of another package — the pin stays remote: the URL is re-resolved from the CDN provider it already points at (`ga.jspm.io`, `unpkg.com`, `cdn.jsdelivr.net`, `cdn.skypack.dev` or `esm.sh`) instead of being replaced with a download. `pristine` skips remote pins, since there is nothing to redownload.

An explicit `--from` moves a remote pin to that CDN; without it the current provider wins. `--remote` on a package that is vendored today converts it: the pin gets the URL and the file in `vendor/javascript` is removed.

## Custom URLs

A pin pointing at any other host is yours: `pin` and `update` leave it completely untouched and report it as skipped.

```console
$ ./bin/importmap pin md5
Skipping "md5" pinned to custom URL https://cdn.example.com/md5.js
```

## Pin options survive a rewrite

When a pin is rewritten — by `pin`, `update` or `pristine` — the options on it are carried over: `preload: false`, `preload: "admin"`, `integrity: true` and `integrity: false` all stay. An explicit `integrity:` *hash* is dropped when the URL changes, since the old hash would no longer match the new file; see [Subresource integrity](https://importmap-plus.zoolutions.llc/docs/integrity) for pinning fresh hashes.

Pins that a resolution touches only as dependencies keep their options the same way. Pinning `md5` re-resolves `charenc` and `crypt` with it, but a `pin "crypt", preload: false` you wrote stays `preload: false`.