Skip to content

fix: declare the dependencies the npm package imports - #26

Merged
taras merged 1 commit into
mainfrom
tm/npm-deps
Oct 3, 2026
Merged

taras merged 1 commit into
mainfrom
tm/npm-deps

Conversation

@taras

@taras taras commented Oct 3, 2026

Copy link
Copy Markdown
Member

Closes #25.

Motivation

@frontside/revolution@0.9.0 cannot be imported. It ships 28 bare import statements across nine packages and declares none of them:

16  "effection"            ← the only one declared, as a peerDependency
 4  "@std/http"            ← not on npm
 2  "hast"
 1  "@std/path"            ← not on npm
 1  "esbuild-deno-loader"  ← not on npm
 1  "esbuild"
 1  "hast-util-to-html"
 1  "hastx/html"
 1  "path-to-regexp"

There is no deps/ directory in the tarball, so every one of these has to resolve from the consumer's node_modules. Three of them are JSR-only and have no npm package under that name, which means no consumer can satisfy them. @std/http is simply the first one the module graph reaches:

error: Could not find package '@std/http' from referrer
  .../node_modules/@frontside/revolution/esm/lib/sse.js

This is what currently keeps effection's website pinned to deno.land/x/revolution@0.8.0 instead of the published package.

Approach

The cause is tasks/build-npm.ts running dnt 0.41.3, which emits JSR specifiers verbatim. Bumping to 0.44.0 fixes it at the source: dnt vendors those modules into esm/deps/jsr.io/… and rewrites the imports to relative paths, and it records the npm ones in package.json.

dnt would vendor effection the same way, which would quietly defeat peerDependencies. Revolution would then run a private copy, so contexts and scopes would not cross the boundary into the consumer's effection. A mappings entry keeps it a bare import backed by the peer dependency.

hast stays a bare import on purpose. dnt declares @types/hast for it, which is how a declaration-only package is meant to be consumed — TypeScript errors if you import an @types/ package by its own name.

Result

dependencies:     @types/hast ^3.0.0, esbuild 0.24.2, hast-util-to-html 9.0.0,
                  hastx 0.0.11, path-to-regexp 8.2.0
peerDependencies: effection ^4.0.0
vendored:         esm/deps/jsr.io/@std, esm/deps/jsr.io/@luca

Every remaining bare import is either a declared dependency or a node: builtin, and no effection is vendored.

Verification

Packed the build and installed it into a scratch project alongside effection@4, then served a route and fetched it:

STATUS 200 BODY OK

deno check on the consumer passes, including JSX types. node_modules holds exactly one effection, resolved from the consumer.

deno fmt --check, deno lint and deno task test (6 passed, 26 steps) all pass.

The published package imports nine bare specifiers and declares none of
them, so nothing supplies them at resolution time. Three — @std/http,
@std/path and esbuild-deno-loader — are JSR-only and have no npm package
under that name, so no consumer can supply them either. Importing
@frontside/revolution fails on the first of them:

    Could not find package '@std/http' from referrer
    .../@frontside/revolution/esm/lib/sse.js

dnt 0.41.3 emitted the JSR specifiers verbatim. 0.44.0 vendors those
modules into the package and rewrites the imports to relative paths, and
it records the npm ones in package.json. That leaves effection, which
dnt would also vendor; a mapping keeps it a bare import backed by the
peer dependency, so revolution and its consumer share one copy and
contexts cross the boundary.
@taras
taras merged commit 4ed2d3e into main Oct 3, 2026
2 checks passed
@taras
taras deleted the tm/npm-deps branch October 3, 2026 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 0.9.0 on npm declares none of its runtime dependencies

2 participants