mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
014b9987fa
### What? Implements dependency-level HMR accept and decline for Turbopack, covering both ESM (`import.meta.turbopackHot`) and CJS (`module.hot`) modules. Previously Turbopack only supported self-accept (`module.hot.accept()` with no arguments) and self-decline. This PR adds the full dependency-targeted API: - `module.hot.accept(dep, cb)` / `import.meta.turbopackHot.accept(dep, cb)` — single dep - `module.hot.accept([depA, depB], cb)` / `import.meta.turbopackHot.accept([depA, depB], cb)` — array of deps - `module.hot.decline(dep)` / `import.meta.turbopackHot.decline(dep)` — single dep - `module.hot.decline([depA, depB])` / `import.meta.turbopackHot.decline([depA, depB])` — array of deps ### Why? Libraries like `react-refresh` and user code often need to accept updates for specific dependencies rather than self-accepting the entire module. Without this, those HMR patterns fall back to full page reloads in Turbopack. ### How? **Runtime** (`turbopack-ecmascript-runtime`): - Extended `HotState` with `acceptedDependencies`, `acceptedErrorHandlers`, and `declinedDependencies` maps (keyed by `ModuleId`) - Updated `hot.accept()` and `hot.decline()` to handle string and array signatures - Updated `getAffectedModuleEffects` to check accepted/declined dependencies when propagating updates through the module graph - Added `'declined'` effect type that throws an `UpdateApplyError` - Track `outdatedDependencies` (map of parent → set of updated deps) alongside `outdatedModules` - In the apply phase, invoke per-dependency accept callbacks with the correct outdated deps - Use `Set` for `outdatedDependencies` dedup (O(1) vs O(n) lookups) **Compiler** (`turbopack-ecmascript`): - Added `ModuleHotReferenceAssetReference` — a single asset reference type for both accept and decline deps, with shared resolve logic for ESM (`esm_resolve`) and CJS (`cjs_resolve`) - Added `ModuleHotReferenceCodeGen` — generates code that replaces dep string literals with resolved module IDs at compile time - ESM binding auto-update: when an ESM module accepts a dependency that it also `import`s, the compiler wraps the accept callback to re-import the namespace variable (`__TURBOPACK__imported__module__<id> = __turbopack_import__(<id>)`) before the user callback runs, so ESM bindings reflect updated values without needing `require()` - Added `import.meta.turbopackHot` as the ESM equivalent of `module.hot`, with TypeScript type declarations in `packages/next/types/global.d.ts` - Static analysis extracts dep strings from `module.hot.accept`/`decline` calls; non-analyzable deps emit a warning with distinct error codes (`TP1204` for accept, `TP1205` for decline) **HMR gating** (`CompileTimeInfo`): - Added `hot_module_replacement_enabled` flag to `CompileTimeInfo` to gate recognition of `module.hot` and `import.meta.turbopackHot` as well-known objects - Without this flag, production builds would recognize `module.hot.accept(...)` and generate HMR-specific code, leading to runtime errors - Flag set to `true` for dev servers and `false` for production builds across Next.js and turbopack-cli entry points - `import.meta.turbopackHot` getter is only emitted when HMR is enabled **Server HMR** (`next-api`, `next-core`): - Server-side `compile_time_info` now also sets `hot_module_replacement_enabled` so that `module.hot` / `import.meta.turbopackHot` are recognized during analysis of server modules - Server HMR requires the `--experimental-server-fast-refresh` CLI flag; the flag is passed through `ProjectOptions` to the Rust side so `server_compile_time_info` only enables HMR when appropriate **Tests** (`test/development/app-dir/hmr-dep-accept/`): - ESM single dep accept — verifies parent module is not re-evaluated, accept callback fires, ESM bindings auto-update - ESM array dep accept — same as above with `accept(['./dep-a', './dep-b'], cb)` - CJS `module.hot.accept` — pure CJS dep observer pattern with `.cjs` files - Single dep decline — verifies full page reload occurs - Array dep decline — verifies full page reload with `decline(['./dep-a', './dep-b'])`
next-build-test
This binary lets you sidestep all of the node bundling and run a turbo build against a raw rust binary. It does not do everything nextjs does, but it is an ok approximation.
Getting started
You will need a project_options file that points to some nextjs repo that has
its dependencies installed. The easiest way to do that is to run a nextjs
build using a modified binary that produces one out for you or to run the
generate command and tweak it manually. We cannot bundle one in the repo,
since it needs fs-specific paths and env vars.
You can run the binary with the generate flag to build one for you.
cargo run -- generate /path/to/project > project_options.json
cargo run -- run
Flags
The run command supports 4 flags:
strategycan be one of sequential, concurrent, or parallel. defines how work is structuredfactordefined how many pages should be built at once. defaults to num_cpuslimitdefines the highest number of pages to build. the pages are shuffled deterministically. defaults to 1 pagepagesa comma separated list of routes to run. queues that precise set in the order specified