fix(ext/node): apply Deno's resolver inside loader-hook defaultResolve#33964
Merged
Conversation
When any package calls module.register() (e.g. @tailwindcss/postcss), every subsequent bare-specifier resolution gets routed through the hooks-worker bridge. The worker's defaultResolve used new URL(spec, parentURL), which bypasses Deno's import map / jsr / npm resolution and produced "Loading unprepared module" errors for mapped specifiers like "fresh". Pre-compute Deno's default-resolved URL on the Rust side and pass it through to the hooks worker so defaultResolve() returns the real resolution. Also fall back to inner_resolve synchronously when V8's module_resolve_callback hits a cache miss (recursive_load can skip the async resolve when the placeholder URL coincidentally matches a registered module, leaving the cache empty).
When a module load redirects (e.g. `npm:foo@^1` aliased to its `file:///.../node_modules/...` location), `register_and_recurse_inner` was still using the original request specifier as the referrer for the module's child imports. With the resolve-hook bridge in the path, that caused `resolve_async` to run with an `npm:` URL as the referrer, where Deno's resolver can't locate the parent package's `package.json` and fails any bare intra-package import. Switch to the module's canonical post-redirect URL via `get_name_by_id`.
Add a spec test that registers a passthrough hook and dynamically imports `npm:@denotest/dual-cjs-esm-dep`, whose ESM entry does a bare intra-package dependency import. Without the canonical-referrer fix in `recursive_load`, the resolver sees the `npm:` request URL as the referrer and can't find the parent package's package.json, so the intra-package import fails. Also expand the sync-fallback comment in `cli/module_loader.rs` to be explicit that the user hook chain is bypassed for that single resolve, and why that's safe in practice.
Contributor
|
|
littledivy
pushed a commit
to crowlKats/deno
that referenced
this pull request
Jun 10, 2026
denoland#33964) When any package calls `module.register()` -- `@tailwindcss/postcss` does, transitively, on Fresh apps that use Tailwind -- the global `resolve_active` flag flips on and every subsequent bare-specifier resolution gets routed through the hooks-worker bridge. The worker's `defaultResolve` used `new URL(spec, parentURL)`, which bypasses Deno's import map / jsr / npm resolution. That turned `import "fresh"` into `file:///.../fresh` and produced `Loading unprepared module` errors for any mapped specifier, which broke things like `deno task dev` on the `dotcom` repo. This pre-computes Deno's default-resolved URL on the Rust side and passes it through to the hooks worker so `defaultResolve()` returns the real resolution, letting user hooks that chain `next()` (the common passthrough pattern) see the same URL Deno would have produced. It also falls back to `inner_resolve` synchronously when V8's `module_resolve_callback` hits a cache miss -- `recursive_load` can skip the async resolve when the placeholder URL coincidentally matches a registered module, which left the cache empty and panicked during instantiation. Closes denoland#33989
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When any package calls
module.register()--@tailwindcss/postcssdoes, transitively, on Fresh apps that use Tailwind -- the global
resolve_activeflag flips on and every subsequent bare-specifierresolution gets routed through the hooks-worker bridge. The worker's
defaultResolveusednew URL(spec, parentURL), which bypasses Deno'simport map / jsr / npm resolution. That turned
import "fresh"intofile:///.../freshand producedLoading unprepared moduleerrors forany mapped specifier, which broke things like
deno task devon thedotcomrepo.This pre-computes Deno's default-resolved URL on the Rust side and
passes it through to the hooks worker so
defaultResolve()returns thereal resolution, letting user hooks that chain
next()(the commonpassthrough pattern) see the same URL Deno would have produced. It also
falls back to
inner_resolvesynchronously when V8'smodule_resolve_callbackhits a cache miss --recursive_loadcan skipthe async resolve when the placeholder URL coincidentally matches a
registered module, which left the cache empty and panicked during
instantiation.
Closes #33989