Skip to content

Import attributes don't survive the rollup builder: emitted with the removed assert key, and dropped in .ts sources on v2 #4518

Description

@agantelin

Environment

  • Nitro 2.13.4 (v2, rollup) and 3.0.260610-beta (main, builder: "rollup")
  • Node.js v24.19.0 — the failure applies to every Node.js ≥ 22
  • Windows 11, npm

Reproduction

https://github.com/agantelin/nitro-import-attributes-repro — four self-contained cases, each with
its own npm install and the exact commands in the README.

Describe the bug

Two independent defects. Both are quiet: the build is green, the server starts, and only the route
that touches the module answers 500.

1. output.importAttributesKey is never set

Rollup 4 re-prints the import attributes of external modules with its default key, assert:

// source
() => import("@iconify-json/mdi/icons.json", { with: { type: "json" } })
// .output/server/chunks/routes/index.mjs
() => import('@iconify-json/mdi/icons.json', { assert: { type: 'json' } })

assert was removed from Node.js in 22.0.0 (nodejs/node#52104, "esm: drop support for import
assertions"). In the dynamic form the emitted code isn't a syntax error, it just no longer carries an
attributes bag, so Node refuses the module:

TypeError [ERR_IMPORT_ATTRIBUTE_MISSING]: Module "…/data.json" needs an import attribute of "type: json"
→ HTTP 500

Anything whose attribute is load-bearing (type: "json", wasm) is unloadable this way; attributes a
runtime ignores are of course harmless.

git grep importAttributesKey is empty on both branches. On main this affects the rollup builder
and the rollup branch of src/build/vite/bundler.ts — the default builder is rolldown, which emits
with correctly, so on v3 you have to opt into rollup to hit it. On v2 rollup is the only builder.

Rollup's default flips in v5 (rollup/rollup#6248, merged to the rollup-5 branch on 2026-01-30;
the user-facing report is rollup/rollup#5685, open since 2024-09), and output.importAttributesKey
is the documented workaround until then. No 5.x is published.

On the risk of setting it: the only runtimes that take assert but reject with are Node.js
16.14–18.19 and 20.0–20.9 (with landed in 21.0.0 / 20.10.0 and was backported to 18.20.0) — all
below engines.node (^20.19.0 || >=22.12.0), so the only person who could notice is someone
pinned below the supported floor. Deno 2 turned assert into a hard error; Bun accepts both.

Nitro doesn't create such an import on its own: externals.external doesn't apply to a .json on
either branch (on v2 the externals plugin gates on isValidNodeImport, which returns false for
it), so the import always comes from a module or a user plugin. @nuxt/icon's
serverBundle.externalizeIconsJson is the live case I hit: it generates a correct
with { type: "json" } import and has no way to know it will be rewritten. Note this also means
#4431's docs (import logo from "./logo.png" with { type: "bytes" }) describe the key users write,
not the key that ships for anything left external.

2. esbuild strips the attributes in .ts / .js sources (v2 only)

getRollupConfig runs the esbuild plugin with target: "es2019", and esbuild drops import
attributes, silently, for every esXXXX target — including es2025:

es2019 dropped · es2022 dropped · es2024 dropped · es2025 dropped
esnext kept · node18 kept · node20 kept · chrome120 kept

(esbuild 0.28.1, the version pinned here. The gate is whether the named target shipped attributes,
not how new the year is.)

The plugin's loader map covers .ts, .js, .tsx, .jsx, so on v2 the attributes never reach
Rollup at all when the import is written in TypeScript — which is how server routes are normally
written. .mjs isn't matched, which is the only reason case 2 in the repro (the attribute-key bug)
is observable in isolation.

main transpiles with oxc at target: "esnext" and is unaffected by this second defect.

Suggested fix

  • output.importAttributesKey: "with"src/rollup/config.ts on v2, src/build/rollup/config.ts
    plus src/build/vite/bundler.ts on main. Both sit under the user's rollupConfig in the merge,
    so explicit config still wins.
  • supported: { "import-attributes": true, ...user.supported } on the esbuild plugin options —
    v2 only.

I've pushed both as PRs since they're one line each and easier to judge as a diff — happy to close
them if you'd rather shape the fix differently.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingnuxtv2v3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions