#4611·nitro

Nitro 2: node-externals constructs a trailing-slash package specifier, triggering DEP0155 for @vue/shared

Author: lvzhenboCreated Sep 11, 2026Updated Sep 13, 2026
Labelspending triage

Environment

  • OS: Windows
  • Node.js: 24.19.0
  • Package manager: pnpm 12.3.4
  • Nuxt: 4.5.2
  • Nitro / nitropack: 2.13.4
  • Vue / @vue/shared: 3.5.42
  • mlly: 1.8.2
  • exsolve: 1.1.1
  • Build preset: node-server
  • Compatibility date: 2025-12-21

Relevant configuration:

typescript
export default defineNuxtConfig({
  compatibilityDate: '2025-12-21',
  nitro: {
    experimental: {
      websocket: true,
    },
  },
})

### Reproduction

he warning occurs during the Nitro server build phase of an existing Nuxt application.

To capture the stack trace, run the build with deprecation tracing enabled. In PowerShell:

```powershell
$env:NODE_OPTIONS = '--trace-deprecation'
pnpm build

I do not currently have a public minimal reproduction repository. However, I traced the warning in the actual build and independently reproduced the dependency-resolution sequence using the installed dependencies.

The isolated sequence is:

javascript
// vueFile is the resolved absolute path to:
// @vue/shared/dist/shared.esm-bundler.js

const subpath = await lookupNodeModuleSubpath(vueFile)
// "./"

const specifier = join('@vue/shared', subpath)
// "@vue/shared/"

resolveModuleURL(specifier, {
  from: importer,
  try: true,
  conditions: ['node', 'import', 'production'],
})
// Emits DEP0155 and returns undefined.

Here:

  • lookupNodeModuleSubpath is from mlly.
  • join is from pathe, as used by Nitro.
  • resolveModuleURL is from exsolve.
  • importer is the file URL of Nuxt's installed error-500.mjs template.

As a control, resolving @vue/shared with the same resolver options succeeds without the warning.

Describe the bug

During the Nitro server build, the node-externals plugin constructs the package specifier @vue/shared/ while trying to infer an importable package subpath. Passing this specifier to exsolve triggers a DEP0155 deprecation warning.

The original import in @nuxt/nitro-server/dist/runtime/templates/error-500.mjs does not contain a trailing slash:

javascript
import { escapeHtml } from '@vue/shared'

The observed resolution sequence is:

  1. mlly.lookupNodeModuleSubpath() returns "./" for the package root export.
  2. Nitro joins the package name with this subpath.
  3. join('@vue/shared', './') produces @vue/shared/.
  4. Nitro passes that specifier to exsolve.resolveModuleURL().
  5. exsolve emits DEP0155 while resolving the resulting "./" package subpath.

The actual build stack points to this statement in nitropack/dist/rollup/index.mjs:

javascript
const resolvedGuess =
  guessedSubpath && tryResolve(join(pkgName, guessedSubpath), importer)

Expected behavior

When the inferred subpath represents the package root, Nitro should resolve the bare package name, such as @vue/shared, without introducing a trailing slash.

Actual behavior

Nitro attempts to resolve @vue/shared/, producing the deprecation warning.

Additional context

The installed @vue/shared/package.json contains export keys "." and "./*". It does not contain a separate "./" export key.

Directly importing the Nuxt error-page template with Node.js and --trace-deprecation succeeds without emitting this warning.

Although mlly returns the "./" subpath, the warning in the actual build is emitted by exsolve, called from Nitro's node-externals plugin.

Logs

bash
Local absolute paths have been shortened for readability.


[DEP0155] DeprecationWarning: Use of deprecated trailing slash pattern mapping "./"
in the "exports" field module resolution of the package at
<project>/node_modules/.pnpm/@[email protected]_<hash>/node_modules/@vue/shared/package.json
imported from
<project>/node_modules/.pnpm/@[email protected]_<hash>/node_modules/@nuxt/nitro-server/dist/runtime/templates/error-500.mjs.
Mapping specifiers ending in "/" is no longer supported.

    at emitTrailingSlashPatternDeprecation (exsolve/dist/index.mjs:650:12)
    at packageExportsResolve (exsolve/dist/index.mjs:669:38)
    at packageResolve (exsolve/dist/index.mjs:771:82)
    at moduleResolve (exsolve/dist/index.mjs:809:14)
    at _tryModuleResolve (exsolve/dist/index.mjs:915:10)
    at resolveModuleURL (exsolve/dist/index.mjs:866:16)
    at tryResolve (nitropack/dist/rollup/index.mjs:346:17)
    at Object.resolveId (nitropack/dist/rollup/index.mjs:417:51)
    at async PluginDriver.hookFirstAndGetPlugin (rollup/dist/es/shared/node-entry.js:22743:28)
    at async resolveId (rollup/dist/es/shared/node-entry.js:21205:26)


Isolated reproduction output:


subpath: "./"
specifier: "@vue/shared/"

Resolving: @vue/shared
file:///<project>/node_modules/.pnpm/@[email protected]/node_modules/@vue/shared/dist/shared.cjs.prod.js

Resolving: @vue/shared/
undefined

[DEP0155] DeprecationWarning: Use of deprecated trailing slash pattern mapping "./" ...