denoland/deno
npm support
Active contributors: David Sherret, Bartek Iwańczuk, Marvin Hagemeister
Purpose
Deno can import directly from the npm registry. import express from "npm:express@4" works without a package.json, without a build step, and without a separate install command. Behind the scenes this involves: parsing the specifier, querying the npm registry, resolving the dependency graph, downloading and extracting tarballs, and serving the resulting modules to the runtime through Node-style resolution rules.
This page maps the layers and points you at the specific code for each.
The layers
graph TD
User["import 'npm:express@4'"] --> ML["cli/module_loader.rs"]
ML --> Resolver["libs/resolver"]
Resolver --> NpmRes["NpmResolver<br/>libs/npm"]
NpmRes -->|registry call| Registry["registry.npmjs.org<br/>(or .npmrc-configured registry)"]
NpmRes -->|version solve| Graph["libs/npm/resolution/graph.rs<br/>(9,478 lines)"]
Graph --> Lockfile["libs/lockfile<br/>(deno.lock)"]
NpmRes --> Cache["libs/npm_cache"]
Cache --> Tarball["DENO_DIR/npm/registry.npmjs.org/.../tarball.tgz"]
Cache --> Installer["libs/npm_installer"]
Installer --> Layout{"layout?"}
Layout -->|auto| GlobalCache["DENO_DIR/npm/<br/>(unique paths)"]
Layout -->|BYONM| LocalNM["./node_modules/"]
GlobalCache --> NodeRes["libs/node_resolver"]
LocalNM --> NodeRes
NodeRes -->|file URL| MLCrate roles
libs/npm
The npm client. Handles:
- npm registry HTTP queries (with the
.npmrc-configured registries and auth) - semver constraint parsing
- the dependency-graph solver —
libs/npm/resolution/graph.rs(9,478 lines) — implementing peer-dependency hoisting, optional deps, npm's particular conflict rules - Lockfile interaction (read pinned versions, write new resolutions)
libs/npm_cache
On-disk caching of registry metadata and package tarballs. Lives under DENO_DIR/npm/registry.npmjs.org/<package>/. Implements the cache invalidation: registry metadata is refreshed on deno install or when a new version is requested; tarballs are immutable once extracted.
libs/npm_installer
Actual installation: tarball extraction, file layout, optional dependency handling. The --allow-scripts flag is enforced here — npm packages with postinstall scripts only run those scripts when explicitly allowed by name (deno install --allow-scripts=esbuild,sharp).
libs/npmrc
Parser for .npmrc files. Supports per-scope registries (@my-scope:registry=https://npm.pkg.github.com), auth tokens (//registry.example.com:_authToken=...), and the env-var interpolation (${NPM_TOKEN}) standard.
libs/node_resolver
Once a package is on disk, this crate handles require('foo') and ESM imports per Node's algorithm: walking node_modules/ directories, applying package.json exports/imports, conditional exports (browser, node, import, require, default), CJS/ESM extension rules.
cli/npm.rs
The CLI-side glue (~736 lines): wires the above crates into cli/factory.rs, drives deno install/deno add/deno remove, and implements the BYONM detection logic.
Two layouts
Auto-managed (default)
With no nodeModulesDir in deno.json (or with "nodeModulesDir": "auto"), Deno stores npm packages in DENO_DIR/npm/. Each package version lives in its own directory; the resolver maps import 'foo' to that path directly. There's no project-local node_modules/ directory.
Pros: shared cache across projects, no duplication, no install step.
BYONM ("bring your own node_modules")
With "nodeModulesDir": "manual" in deno.json, Deno expects a project-local node_modules/ directory that the user manages with npm install (or pnpm, yarn, etc.). Deno reads from it but doesn't write to it.
Pros: compatibility with tools that walk node_modules/ directly (some bundlers, IDEs, type tools), familiar workflow for npm-using teams. Cons: gives up Deno's automatic caching.
The detection and switching logic lives in cli/npm.rs and cli/factory.rs.
Subcommand surface
The npm-related subcommands are implemented under cli/tools/pm/:
| Subcommand | What it does |
|---|---|
deno add <pkg> |
Add a dependency to deno.json imports (or package.json in BYONM) |
deno remove <pkg> |
Remove a dependency |
deno install |
Install all dependencies; populates node_modules/ in BYONM, or warms the cache otherwise |
deno install <pkg> |
Install a single package |
deno uninstall |
Reverse of install |
deno outdated |
List packages with newer versions available |
deno audit |
Run npm audit-equivalent vulnerability check |
deno approve-scripts |
Approve postinstall scripts that were blocked |
deno install recently grew --prod (skip dev deps and @types/*) and --os/--arch for cross-platform installs (commits in late April 2026).
Lockfile interaction
deno.lock records the resolved version and integrity hash for every npm package. When you do deno run script.ts:
- If the lockfile has an entry for an
npm:specifier, the recorded version is used and the integrity hash is checked against the cache. - If a specifier isn't in the lockfile, npm is queried, the result is recorded, and the lockfile is updated (unless
--frozen-lockfileis set).
The recent commit test(npm): add regression test for update breaking peer dep lockfile entries reflects the kind of subtle interactions that show up — peer dep changes can invalidate lockfile entries even when no direct dep changed.
package.json support
When a project has a package.json, Deno respects:
dependencies,devDependencies,peerDependencies,optionalDependenciesimports(thepackage.jsonfield, not Deno's separateimports)exports- The
scriptsfield is read bydeno taskto expose npm scripts as Deno tasks
Implementation: libs/package_json parses the file; libs/node_resolver honors exports/imports; cli/tools/task.rs exposes scripts.
Tests
- Spec tests under
tests/specs/install/,tests/specs/npm/,tests/specs/node/. - Registry fixtures live in
tests/registry/. - Big chunks of integration logic are exercised by the LSP suite
tests/integration/lsp_tests.rs.
Entry points for modification
- Resolution algorithm change —
libs/npm/resolution/(most likelygraph.rs). - Cache layout change —
libs/npm_cache/pluslibs/cache_dir. - Installation behavior —
libs/npm_installer/. - CLI behavior (e.g., new
deno installflag) —cli/args/flags.rsfor the flag,cli/tools/pm/for the handler. - Node-style resolution semantics —
libs/node_resolver/.
Key source files
| File | Purpose |
|---|---|
cli/npm.rs |
CLI-side npm glue |
cli/tools/pm/ |
deno add/remove/install/outdated/audit handlers |
libs/npm/lib.rs |
Crate root — public API |
libs/npm/resolution/graph.rs |
Dependency-graph solver (9,478 lines) |
libs/npm_cache/ |
Tarball/registry-response cache |
libs/npm_installer/ |
Package installation |
libs/npmrc/ |
.npmrc parser |
libs/node_resolver/ |
Node-style resolution |
libs/lockfile/ |
deno.lock parser/writer |
libs/package_json/ |
package.json parser |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.