Nitro appears to be downgrading a dependency to CJS rather than using ESM
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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