Skip to content

fix: resolve scoped npm packages and warn on missing deps - #88

Open
uriva wants to merge 2 commits into
denoland:mainfrom
uriva:fix/npm-specifier-resolution
Open

uriva wants to merge 2 commits into
denoland:mainfrom
uriva:fix/npm-specifier-resolution

Conversation

@uriva

@uriva uriva commented Mar 4, 2026 •

Copy link
Copy Markdown

Summary

Fixes two bugs in prefixPlugin.ts when resolving npm: specifiers. Closes #40.

Bug 1: Scoped package names parsed incorrectly

deno info --json returns npmPackage identifiers like:

  • Unscoped: preact@10.25.4
  • Scoped: @preact/signals@2.8.1_preact@10.25.4

The existing code uses indexOf("@") to find the version separator. For scoped packages starting with @, this returns 0, so slice(0, 0) produces an empty string "". The fix finds the @ after the / in scoped package names.

Bug 2: Silent externalization on resolution failure

When this.resolve(packageName) fails (package not in node_modules), the old code returned the bare package name as a string. Vite interpreted this as an external module and silently externalized it — the error only appeared at runtime in the browser as:

Module "xyz" has been externalized for browser compatibility

Now the plugin emits a warning with actionable guidance and returns undefined, allowing Vite to report the unresolved import at build time instead.

Why packages might not be in node_modules

This commonly happens when an npm: specifier comes from a JSR package. Deno resolves JSR transitive npm dependencies through its own module graph, but they don't get installed into the project's node_modules/. The warning tells users to add the package to their import map or package.json.

Tests

  • Added extractPackageName unit tests covering unscoped, scoped, scoped with peer dep suffixes, and versionless package identifiers
  • Added integration test for inline npm:@preact/signals (scoped package)
  • All 17 tests pass (11 existing + 6 new)

@uriva

uriva commented Mar 4, 2026

Copy link
Copy Markdown
Author

Note: the lint-and-format check is failing, but this is a pre-existing failure on main. The no-import-prefix and no-unversioned-import rules flag all the inline test fixtures (inlineNpm.ts, inlineJsr.ts, inlineHttp.ts, etc.) which intentionally use inline specifiers since that's what the plugin resolves. My new inlineScopedNpm.ts follows the same pattern.

This PR fixes #40.

@uriva

uriva commented Mar 9, 2026

Copy link
Copy Markdown
Author

@marvinhagemeister could you ptal?

@uriva

uriva commented Sep 13, 2026

Copy link
Copy Markdown
Author

@bartlomieju could you ptal? this has been a problem that trips us every time

@uriva
uriva force-pushed the fix/npm-specifier-resolution branch from 15b4eb9 to f42e4dd Compare September 13, 2026 21:31
Two bugs in prefixPlugin's npm: specifier handling:

1. Scoped package names (e.g. @preact/signals@2.8.1_preact@10.25.4)
   were parsed incorrectly: indexOf('@') returns 0 for '@preact/...',
   resulting in an empty package name. Fixed by finding the version
   separator '@' after the scope's '/'.

2. When this.resolve() fails (package not in node_modules), the bare
   package name was returned as-is, causing Vite to silently
   externalize it. The error only surfaced at runtime in the browser.
   Now emits a warning and returns undefined so Vite can report the
   unresolved import at build time.

Closes denoland#40
@uriva
uriva force-pushed the fix/npm-specifier-resolution branch from f42e4dd to 57fb984 Compare September 13, 2026 21:33
@uriva
uriva force-pushed the fix/npm-specifier-resolution branch from 4327ffe to 41c06b5 Compare September 13, 2026 21:36
@marvinhagemeister

Copy link
Copy Markdown
Contributor

I'm afraid I can't. I don't work for Deno anymore and don't have any maintainer permissions anymore. Please contact other members of the Deno team.

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.

npm prefix imports externalized in vite.

2 participants