nitrojs / nitrojs/nitro

Nitro appears to be downgrading a dependency to CJS rather than using ESM

Open
#3,391 14 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug needs investigation tanstack v3
Dominant language
TypeScript
Stars
11.2k
Forks
899
Avg merge
2d 24m
Merged PRs (30d)
40

Description

Environment

nitro v2.11.12
node v22.14.0

minimal nitro config

//https://nitro.unjs.io/config
export default defineNitroConfig({
  srcDir: 'server',
  preset: 'aws-lambda',
})
Reproduction

https://github.com/mckamyk/nitro-cjs-degrade

I wrote an analyze.ts script that uses depcruise to go through all of the dependencies of .output/server/index.mjs, and ranks them by the number of second-level imports on each top-level import. This helped me track down the cause of the EMFILE error in my lambda. An example of the output at the time of writing below.

../../node_modules/viem/_cjs/index.js depends on 148 files
../../node_modules/viem/_cjs/utils/index.js depends on 102 files
../../node_modules/viem/_cjs/clients/decorators/public.js depends on 52 files
../../node_modules/viem/_cjs/clients/decorators/test.js depends on 30 files
../../node_modules/viem/_cjs/clients/decorators/wallet.js depends on 23 files
../../node_modules/viem/_cjs/actions/public/call.js depends on 21 files
../../node_modules/ws/lib/websocket.js depends on 16 files
../../node_modules/abitype/dist/cjs/exports/index.js depends on 15 files
../../node_modules/viem/_cjs/actions/public/simulateBlocks.js depends on 15 files
../../node_modules/viem/_cjs/actions/public/verifyHash.js depends on 15 files

The code that was bundled to produce this is simply

import { createPublicClient, http } from 'viem'

const client = createPublicClient({
  transport: http('http://localhost:8545'),
})

export default defineEventHandler(async () => {
  return client.getBlockNumber().then((r) => r.toString())
})
Describe the bug

I have a dependency (viem) that has both ESM and CJS exports defined in its package.json when installed. However, when I build with nitro, it always seems to include the CJS bundle, which breaks all tree-shaking.

The problem is worsened as when I build for a aws-lambda target, and my lambda fails with EMFILE (too many open file handles), largely contributed by viem not being tree-shaken.

Additional context

I discovered this building with TanStack Start, which uses nitro via vinxi. I tested it with TanStack Start's alpha release, which removes vinxi, and the issue persisted. I narrowed it down to nitrojs in the reproduction repo.

I've tested this same code in a small React app using Vite, bun build index.ts as well as bun rollup -c with the following config

import { defineConfig } from 'rollup'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'

export default defineConfig({
  input: 'index.js',
  output: {
    dir: 'dist',
  },
  plugins: [nodeResolve(), commonjs()],
})

All of these alternatives correctly use ESM, and nitro seems to be the odd man out using CJS for this dependency.

Logs
❯ bun run build
$ nitro build

 WARN  Please add compatibilityDate: '2025-06-04' to the config file. Using 2024-04-03 as fallback.                                        nitro 11:34:12 AM
       More info: https://nitro.build/deploy#compatibility-date

✔ Generated public .output/public                                                                                                         nitro 11:34:12 AM
ℹ Building Nitro Server (preset: aws-lambda, compatibility date: ``)                                                                      nitro 11:34:12 AM
✔ Nitro Server built                                                                                                                      nitro 11:34:13 AM
  ├─ .output/server/chunks/nitro/nitro.mjs (135 kB) (33.2 kB gzip)
  ├─ .output/server/chunks/nitro/nitro.mjs.map (2.45 kB) (635 B gzip)
  ├─ .output/server/chunks/routes/index.mjs (516 B) (297 B gzip)
  ├─ .output/server/chunks/routes/index.mjs.map (330 B) (197 B gzip)
  ├─ .output/server/index.mjs (334 B) (199 B gzip)
  └─ .output/server/package.json (279 B) (180 B gzip)
Σ Total size: 1.41 MB (401 kB gzip

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked nitro-cjs-degrade reproduction, its minimal Nitro config, and analyze.ts; inspect the generated .output/server/index.mjs and the dependency paths it contains. Compare the aws-lambda build output with the stated ESM behavior from the reproduction, and consider the issue resolved when the generated bundle uses the dependency's ESM exports and preserves tree-shaking.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
backend, build-system
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.