-
Notifications
You must be signed in to change notification settings - Fork 29.9k
Description
Link to the code that reproduces this issue
https://github.com/jimrandomh/turbopack-issue-repro
To Reproduce
Start a server using the linked reduced repro and yarn dev or yarn build && yarn start, and make a request to http://localhost:3000/api/test.
Current vs. Expected behavior
If a project contains an import cycle, has an import path from the cycle to an async module, and has a certain import-graph topology, then some function calls will fail at runtime with an error like:
(0 , w.exampleFunction) is not a function
This will always be a function call from a file which does not participate in the import cycle, which has imported a function that is in a file which is in the import cycle. This happens because compute_async_module_info_single marked the imported file as an async module, but didn't propagated this to the file that imports it.
compute_async_module_info_single determines which modules are async by (1) propagating async-ness backwards across import relations with a depth-first traversal, then (2) propagating async-ness through import cycles. This is incorrect in that it stops too early; after propagating async-ness through an import cycle, it needs to return to (1), since there may be files that imported something in the cycle that weren't marked.
(Repeating-until-no-changes is definitely correct, here, but might be noticeably slow. There is a fancy graph-alpgorithm solution to this that would be faster, I think.)
In the linked test case, the import graph has the following topology:
api/test/route.ts
| \
v v
A<- D
| \ |
v \v
B--->C
|
v
async
In this test case, D is not marked as an async module, and the function call from D to C fails.
This impacted our project (ForumMagnum aka LessWrong/EA Forum) despite us not intentionally using any async modules, because some libraries, if listed in serverExternalPackages in NextConfig, are treated as async modules even if there's nothing async inside them (presumably because turbopack is making conservative assumptions without looking inside the library).
I set up a local turbopack build and patched compute_async_module_info_single to do a second pass of graph.traverse_edges_from_entries_dfs, confirmed that this was marking additional modules as async, and confirmed that it fixed the issue in the places we were seeing it.
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 24.5.0: Tue Apr 22 19:54:49 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6000
Available memory (MB): 65536
Available CPU cores: 10
Binaries:
Node: 22.21.1
npm: 10.9.4
Yarn: N/A
pnpm: 9.6.0
Relevant Packages:
next: 16.0.2-canary.13
eslint-config-next: N/A
react: 19.3.0-canary-fa50caf5-20251107
react-dom: 19.3.0-canary-fa50caf5-20251107
typescript: 5.9.2
Next.js Config:
output: N/AWhich area(s) are affected? (Select all that apply)
Turbopack
Which stage(s) are affected? (Select all that apply)
next build (local)
Additional context
No response