npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

importmap-check

v0.1.0

Published

Check for updates to your ESM packages.

Readme

importmap-check

Dependency checker/updater for ESM dependencies from common CDNs.

Overview

importmap-check reads your ESM import maps and identifies dependency versions and available upgrades. The default invocation is check-only: it analyzes targets and reports findings without modifying files. Pass --update / -u to rewrite updateable entries in place.

Features:

Installation

Run it on demand with npx:

$ npx importmap-check <target-path>

Or install it globally:

$ npm install --global importmap-check

Usage

$ importmap-check [options] <target-path>

Options:

  • --sources — Show the import-map entry origins that contributed to each conflated package row.
  • --width <num> — Override the available report width (used with --sources). Defaults to the terminal width, or 120 when not running in a TTY.
  • -u, --update — Rewrite updateable entries in the target file in place. See Update Mode below for behavior, judgment calls, and caveats.
  • --dry-run — Preview the entries --update would rewrite as a unified diff, without writing the target file. Usable on its own, and takes precedence over --update when both are passed. --sources / --width have no effect in this mode. See Update Mode below.

Environment:

  • IMPORTMAP_CHECK_REGISTRY_URL — Base URL of the npm registry used to resolve versions and dist-tags. Defaults to https://registry.npmjs.org. Point it at a private registry mirror if needed.

Supported target types:

  • Standalone import map JSON files
  • HTML files with one or more inline <script type="importmap"> blocks

What importmap-check analyzes:

  • Parses import map imports entries from JSON and inline HTML
  • Supports package-style keys and remap-style URL/path keys
  • Analyzes jsdelivr and esm.sh destination URLs
  • Combines multiple inline import maps into one package-level analysis result
  • Warns for supported-but-unparseable entries and unsupported scopes

Update Mode

$ importmap-check --update [options] <target-path>

--update / -u rewrites updateable import map entries in the target file in place and prints a post-rewrite summary to stdout describing what changed. The default target is the npm registry's latest dist-tag.

⚠️ Make sure your target file is in version control and all changes are committed before running --update, or preview the exact edits first with --dry-run (see Previewing changes below). importmap-check does not snapshot before writing; the atomic write step protects against interrupted writes but not against losing work you hadn't committed.

Previewing changes with --dry-run

$ importmap-check --dry-run <target-path>

--dry-run computes the same rewrite plan --update would apply and prints it as a unified line diff, without writing the target file. It is usable on its own, and when combined with --update the dry run wins — no file is written either way.

$ importmap-check --dry-run ./public/index.html
Dry run — no files written.

--- ./public/index.html
+++ ./public/index.html
@@ -6,8 +6,8 @@
         "imports": {
-          "react": "https://esm.sh/[email protected]",
-          "react-dom/client": "https://esm.sh/[email protected]/client"
+          "react": "https://esm.sh/[email protected]",
+          "react-dom/client": "https://esm.sh/[email protected]/client"
         }

Because the diff is computed from the bytes --update would write, integrity entries that would be stripped appear as removed (-) lines. The same Warnings, Stripped integrity entries, Lookup Failures, and Notes sections that follow an --update are printed after the diff. When nothing would change, --dry-run prints No changes would be written. plus any applicable warnings or notes.

Rewrite behavior by specifier class

importmap-check preserves the specifier style you wrote. The exact rewrite depends on the specifier class:

| Specifier class | Example | When --update finds an update, the entry becomes | | ------------------- | --------------- | ---------------------------------------------------------------------------------------------------------- | | Pinned | [email protected] | react@<latest> | | Caret range | react@^19.2.3 | react@^<latest major>.0.0 | | Tilde range | react@~19.2.3 | react@~<latest major>.0.0 (cross-major) or react@~<latest>.<latest minor>.0 (cross-minor within major) | | Major-only selector | react@18 | react@<latest major> | | Minor-only selector | [email protected] | react@<latest major>.<latest minor> | | Dist-tag | react@beta | not rewritten — see Dist-tag entries below |

Each rewrite preserves every other portion of the URL (CDN family, scoped package name, subpath, and non-deps query parameters). Updateable ?deps= pins on the same URL are rewritten too — see ?deps= query pins are rewritten in place below. Caret and tilde rewrites follow npm's semver locking rules: caret locks the leftmost non-zero element of [major, minor, patch]; tilde locks the position immediately left of the rightmost-specified position.

Major-version-zero ranges

Per npm's "0.x changes are breaking" rule, ranges on 0.x packages lock the minor position (not the major):

  • react@^0.1.0 with latest=0.2.5 rewrites to ^0.2.0 (within-0.x lift, locked-minor moves from 0.1 to 0.2)
  • react@^0.1.0 with latest=1.0.0 rewrites to ^1.0.0 (fallthrough to major-locked semantics)
  • react@^0.0.3 with latest=0.0.5 rewrites to ^0.0.5 (within-0.0.x lift, only the locked patch position moves)
  • react@^0.0.3 with latest=0.1.0 rewrites to ^0.1.0 (promotion from patch-locked to minor-locked)
  • react@~0.0.3 follows npm's quirk where ~0.0.z := >=0.0.z <0.0.z+1 (same as caret on 0.0.x), and rewrites the same way as ^0.0.3

The narrow ^0.0.z handling is intentional: ESM CDNs apply actual npm semver rules at request time, so broadening ^0.0.3 to ^0.5.0 would silently admit any 0.x version esm.sh serves — which is not what the ^0.0.3 specifier expresses.

Dist-tag entries are not rewritten

Dist-tag entries (e.g. react@beta, react@next, react@latest) are not rewritten by --update. ESM CDNs re-resolve dist-tags from the npm registry live at request time, so a react@beta URL already floats to whatever dist-tags.beta currently points to — there is no time-of-authoring version lag the tool is responsible for closing. Rewriting react@beta to e.g. [email protected]... would only snapshot a value the user already had access to via the floating tag, which is an opt-in decision the user should make explicitly.

If the analyzer produces informational notes about dist-tag entries during check-only analysis (for example, when the stable latest differs from the resolved tag), those notes are preserved verbatim in the post-rewrite summary. Future changes will add --pin and --pin-channel flags for opt-in dist-tag flattening.

Integrity entries are stripped, not regenerated

When --update rewrites a URL that has a corresponding entry in the import map's integrity section, importmap-check strips that integrity entry and emits a hard warning:

## Stripped integrity entries
- https://esm.sh/[email protected] (triggered by react)

Stripped entries are not regenerated in this version. SRI hashes are author-time commitments to specific bytes the browser will fetch; computing a replacement hash requires fetching the new URL's body and matching the request-shaping a browser would use, which has documented edge cases around brotli/gzip encodings and esm.sh's user-agent-sensitive build-target selection. Rather than ship brittle regeneration silently, importmap-check leaves re-pinning to you: validate the new URL in a browser, then re-add the SRI hash manually.

A future change will add --regenerate-integrity (with a --no-regenerate-integrity opt-out) once the browser-vs-CLI byte-stability question has been empirically validated.

?deps= query pins are rewritten in place

esm.sh URLs may carry a ?deps=react@18,[email protected] query string pinning dependency versions. --update rewrites updateable dependency pins the same way it rewrites the outer package: each pin's specifier class (pinned, caret, tilde, major-only, minor-only) is resolved and lifted per the same rules in the table above. Dist-tag dependency pins (e.g. ?deps=react@beta) are left floating, matching outer dist-tag behavior.

Dependency pins are rewritten by splicing the individual dependency token in place — the query string's dependency order, separators, and per-token encoding are preserved. importmap-check never re-serializes the query string through a URL parser, so a rewrite touches only the version tokens that actually change (keeping the diff minimal and avoiding the +→space decoding a round-trip would introduce).

When a single URL needs several edits at once — an outer version bump plus one or more dependency-pin bumps — all edits are coalesced into one rewrite, so no edit overwrites another:

- "app": "https://esm.sh/[email protected][email protected],scheduler@^0.23.0"
+ "app": "https://esm.sh/[email protected][email protected],scheduler@^0.24.0"

If a rewritten URL has a corresponding integrity entry, that entry is stripped and a hard warning is emitted (see Integrity entries are stripped, not regenerated above) — this applies whether the URL changed because of the outer package, a ?deps= pin, or both.

Known limitation: the post-rewrite summary groups rewrites by package name, so a package that appears both as an outer import-map entry and as a ?deps= pin inside another package's URL produces summary lines that cannot be told apart. The rewrites themselves are still applied correctly to each distinct URL; only the summary is ambiguous. Distinguishing dependency-context rows is deferred to a future summary-clarity change.

Encoded separators between dependencies (a %2C in place of the literal , esm.sh emits) are out of scope: both the parser and the rewriter operate on the literal-separator form esm.sh produces. If encoded separators surface in the wild, parse and rewrite support will be added together.

Resolution tightening (check-only behavior change)

This change also tightens importmap-check's existing range-resolution logic for ^0.x.y and ~0.0.z specifiers in check-only mode. Previously, ^0.2.3 was treated as ^0 (resolved to the highest 0.x version published), and ~0.0.3 was treated as ~0.0. Both were looser than npm's strict semver rules.

Going forward, ^0.2.3 resolves to the highest 0.2.x version, and ^0.0.3 resolves to the highest 0.0.x version — matching what esm.sh itself serves at request time. This may change the Resolved value importmap-check reports for some import map entries versus prior versions of the tool. The previous values were reflecting looser-than-npm semantics; the new values reflect what the CDN actually serves.

Single target, single invocation

importmap-check operates on exactly one target path per invocation. Multi-target scanning and --filter/--reject (per-package targeting) are deferred to future changes. --update rewrites every updateable entry in the file; to apply updates selectively, use version control to revert unwanted changes after the write, or wait for the --filter / --interactive follow-up changes.

Atomic write

--update writes the rewritten content to a temporary file in the same directory as the target, then atomically moves the temporary file onto the target path. An interrupted update never leaves a half-written target file. The temporary file is cleaned up if the write or move step fails.

Development Workflow

  • Run npm run format after repo changes from a shell where the expected Node version is already active.
  • Treat formatting as part of done: auto-fix what can be fixed, and investigate any remaining failures before considering the work complete.

Example Output

$ importmap-check
Target: ./public/index.html

## Updates
Package    Resolved  Latest
---------  --------  ------
react      19.2.3    19.3.0
react-dom  19.2.3    19.3.0

## Warnings
- ./public/index.html contains `scopes`, which are not yet supported.

Example: Update Mode

$ importmap-check --update ./public/index.html
Updated ./public/index.html:

  react         19.2.3 → 19.3.0
  react-dom     ^19.2.3 → ^20.0.0
  swr           18 → 19
  @scope/lib    ^0.1.0 → ^0.2.0

## Stripped integrity entries
- https://esm.sh/[email protected] (triggered by react)
- https://esm.sh/react-dom@^20.0.0 (triggered by react-dom)

## Notes
- typescript has import map integrity metadata tied to a URL that would need review if updated.

Run importmap-check --update again against the same file with stable latest dist-tags and you'll see No changes to write — the update is idempotent until the npm registry's latest moves.