Files
Niklas MischkulnigandGitHub 74bb0d9a6d Turbopack: fixup cache handler tracing (#94477)
https://github.com/vercel/next.js/pull/94197 had a bug in the Turbopack
case, where it didn't properly trace all necessary files referenced from
`cacheHandler` and `cacheHandlers`.

If you imported `lodash` in the cache handler, then
- `node_modules/.pnpm/[email protected].1/node_modules/lodash/lodash.js` was
correctly traced
but missing were
- `node_modules/.pnpm/[email protected].1/node_modules/lodash/package.json`
and
- `node_modules/lodash` (the symlink)

The problem was that it wasn't including affecting sources of these
entrypoints, because it was treating those entrypoints as bundled:

```
SingleModuleGraph::new_inner(
        entries: Vec<ChunkGroupEntry>,
        visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
        include_traced: bool,
        include_binding_usage: bool,
        entries_are_traced: bool, // <------ problematic
    ) -> Result<Vc<Self>>
```

should really have been 

```
SingleModuleGraph::new_inner(
        entries: &GraphEntries, // contains Vec<ChunkGroupEntry> and traced_modules: Vec<Modules
        visited_modules: &FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
        include_traced: bool,
        include_binding_usage: bool,
    ) -> Result<Vc<Self>> {
```
because the single `entries_are_traced: bool` can be different per
entry.
2026-06-05 20:50:32 +02:00

104 lines
3.1 KiB
TypeScript

import { nextTestSetup } from 'e2e-utils'
import fs from 'fs'
import { join } from 'path'
// Uses webpack internals (stats.json via webpack-bundle-analyzer).
;(process.env.IS_TURBOPACK_TEST ? describe.skip : describe)('Chunking', () => {
const { next } = nextTestSetup({
files: __dirname,
dependencies: {
lodash: '4.18.1',
'webpack-bundle-analyzer': 'latest',
},
})
let chunks: string[]
let stats: any
beforeAll(async () => {
const statsRaw = (await next.readFile('.next/stats.json')).replace(
/"static\\(.*?":?)/g,
(match) => match.replace(/\\/g, '\\\\')
)
stats = JSON.parse(statsRaw)
chunks = fs.readdirSync(join(next.testDir, '.next', 'static', 'chunks'))
})
const existsChunkNamed = (name: string) => {
return chunks.some((chunk) => new RegExp(name).test(chunk))
}
it('should use all url friendly names', () => {
expect(chunks).toEqual(chunks.map((name) => encodeURIComponent(name)))
})
it('should create a framework chunk', () => {
expect(existsChunkNamed('framework')).toBe(true)
})
it('should not create a commons chunk', () => {
expect(existsChunkNamed('commons')).toBe(false)
})
it('should not create a lib chunk for react or react-dom', () => {
expect(existsChunkNamed('react|react-dom')).toBe(false)
})
it('should not preload the build manifest', async () => {
const $ = await next.render$('/')
expect(
[].slice
.call($('link[rel="preload"][as="script"]'))
.map((e: any) => e.attribs.href)
.some((entry: string) => entry.includes('_buildManifest'))
).toBe(false)
})
it('should execute the build manifest', async () => {
const $ = await next.render$('/')
expect(
Array.from($('script'))
.map((e: any) => e.attribs.src)
.some((entry: string) => entry && entry.includes('_buildManifest'))
).toBe(true)
})
it('should not include more than one instance of react-dom', () => {
// Match react-dom only when it appears as a directory segment, so pnpm
// peer-dep path suffixes like ".pnpm/[email protected]_react-dom@..." (which
// appear in CI installs) are not treated as react-dom modules.
const reactDomModuleRegex = /[\\/]react-dom[\\/]/
const misplacedReactDom = stats.chunks.some((chunk: any) => {
if (chunk.names.includes('framework')) {
return false
}
return chunk.modules.some((module: any) => {
return reactDomModuleRegex.test(module.name)
})
})
expect(misplacedReactDom).toBe(false)
})
describe('Serving', () => {
it('should hydrate with aggressive chunking', async () => {
const browser = await next.browser('/page2')
const text = await browser.elementByCss('#padded-str').text()
expect(text).toBe('__rad__')
await browser.close()
})
it('should load chunks when navigating', async () => {
const browser = await next.browser('/page3')
const text = await browser
.elementByCss('#page2-link')
.click()
.waitForElementByCss('#padded-str')
.elementByCss('#padded-str')
.text()
expect(text).toBe('__rad__')
await browser.close()
})
})
})