Files
9a0214a12f fix: handle file:// URLs from import.meta.resolve in cache handler path (fixes #73796) (#90370)
## Summary

Fixes #73796

### The Bug

When "type": "module" is in package.json, users cannot use
require.resolve() for the cache handler path. They must use
import.meta.resolve() instead, which returns a file:// URL. Next.js was
concatenating this URL with .next/ producing invalid paths and
ERR_MODULE_NOT_FOUND errors.

### The Fix

1. resolveCacheHandlerPathToFilesystem() - New helper that converts
file:// URLs to proper filesystem paths using Node's fileURLToPath()
2. formatDynamicImportPath() - Now calls
resolveCacheHandlerPathToFilesystem() before any path operations
3. Applied in collect-build-traces.ts, build/index.ts, and
build/swc/index.ts

This ensures ESM import.meta.resolve() cache handler paths work
correctly in all code paths.

---------

Co-authored-by: JJ Kasper <[email protected]>
2026-03-30 23:53:41 +00:00

110 lines
2.7 KiB
TypeScript

import { type NextInstance, nextTestSetup, FileRef } from 'e2e-utils'
import { check } from 'next-test-utils'
import fs from 'fs'
const originalNextConfig = fs.readFileSync(
__dirname + '/next.config.js',
'utf8'
)
const importMetaResolveNextConfig = `export default {
cacheHandler: import.meta.resolve('./cache-handler-esm.js'),
}`
function runTests(
exportType: string,
{ next, isNextDev }: { next: NextInstance; isNextDev: boolean }
) {
describe(exportType, () => {
it('should have logs from cache-handler', async () => {
if (isNextDev) {
await next.fetch('/')
}
await check(() => {
expect(next.cliOutput).toContain('cache handler - ' + exportType)
expect(next.cliOutput).toContain('initialized custom cache-handler')
expect(next.cliOutput).toContain('cache-handler get')
expect(next.cliOutput).toContain('cache-handler set')
return 'success'
}, 'success')
})
})
}
describe('app-dir - custom-cache-handler - cjs', () => {
const { next, isNextDev, skipped } = nextTestSetup({
files: __dirname,
skipDeployment: true,
env: {
CUSTOM_CACHE_HANDLER: 'cache-handler.js',
},
})
if (skipped) {
return
}
runTests('cjs module exports', { next, isNextDev })
})
describe('app-dir - custom-cache-handler - cjs-default-export', () => {
const { next, isNextDev, skipped } = nextTestSetup({
files: __dirname,
skipDeployment: true,
env: {
CUSTOM_CACHE_HANDLER: 'cache-handler-cjs-default-export.js',
},
})
if (skipped) {
return
}
runTests('cjs default export', { next, isNextDev })
})
describe('app-dir - custom-cache-handler - esm', () => {
const { next, isNextDev, skipped } = nextTestSetup({
files: {
app: new FileRef(__dirname + '/app'),
'cache-handler-esm.js': new FileRef(__dirname + '/cache-handler-esm.js'),
'next.config.js': originalNextConfig.replace(
'module.exports = ',
'export default '
),
},
skipDeployment: true,
packageJson: {
type: 'module',
},
env: {
CUSTOM_CACHE_HANDLER: 'cache-handler-esm.js',
},
})
if (skipped) {
return
}
runTests('esm default export', { next, isNextDev })
})
describe('app-dir - custom-cache-handler - esm import.meta.resolve', () => {
const { next, isNextDev, skipped } = nextTestSetup({
files: {
app: new FileRef(__dirname + '/app'),
'cache-handler-esm.js': new FileRef(__dirname + '/cache-handler-esm.js'),
'next.config.js': importMetaResolveNextConfig,
},
skipDeployment: true,
packageJson: {
type: 'module',
},
})
if (skipped) {
return
}
runTests('esm default export', { next, isNextDev })
})