next.js
f4972228 - turbopack: cache TransformPlugin in narrow-scoped turbo-tasks functions (#92842)

Commit
15 hours ago
turbopack: cache TransformPlugin in narrow-scoped turbo-tasks functions (#92842) ### What? Refactor all usages of `EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(...) as _))` across `next-core` and `turbopack-tests` so that `TransformPlugin` cells are created inside dedicated `#[turbo_tasks::function]` functions rather than inline at call sites. Additionally: - Introduce a `JsonValue` newtype wrapping `serde_json::Value` that implements `TaskInput`, enabling the SWC wasm plugin list to be passed through a turbo-tasks function boundary and properly cached. - Replace the `bool` roundtrip in `next_strip_page_exports` with an `ExportFilterInput` enum that derives `TaskInput`, mirroring `ExportFilter` exhaustively (so a new upstream variant is a compile error, not a silent fallback). - Derive `TaskInput` on `ActionsTransform` and pass it directly to the cached function instead of converting to `is_server: bool` at the call site. - Replace all `.expect("... config must exist")` panics in option-gated plugin functions (`emotion`, `styled_components`, `react_remove_properties`, `remove_console`, `relay`) with `.context(...)?` for proper error propagation. - Add `// TODO: use get_ecma_transform_rule instead` comments to the ~10 transform functions that manually inline the same `ModuleRule::new` + `ExtendEcmascriptTransforms` pattern that `get_ecma_transform_rule` abstracts. ### Why? `TransformPlugin` is not serializable and not comparable. When a `TransformPlugin` is `cell`ed inline (i.e. `ResolvedVc::cell(Box::new(...) as _)`) inside a turbo-tasks function, a new cell is created on every invocation of the enclosing function, because the framework has no way to detect that the value is the same as before. This causes every task that depends on the `TransformPlugin` cell to be invalidated unnecessarily. By moving the `Vc::cell(...)` call into its own narrow-scoped `#[turbo_tasks::function]`, turbo-tasks can cache the cell by the function's inputs. If the inputs haven't changed, the function won't re-run, the existing cell is reused, and downstream tasks are not invalidated. ### How? Each inline `ResolvedVc::cell(Box::new(SomeTransformer { ... }) as _)` is replaced with: ```rust some_transform_plugin(args).to_resolved().await? #[turbo_tasks::function] fn some_transform_plugin(args: ...) -> Vc<TransformPlugin> { Vc::cell(Box::new(SomeTransformer { ... }) as Box<dyn CustomTransformer + Send + Sync>) } ``` Where a cached function needs to store a `ResolvedVc` in the resulting transformer struct, the parameter is declared as `ResolvedVc<T>` directly in the `#[turbo_tasks::function]` signature. The turbo_tasks macro rewrites `ResolvedVc<T>` → `Vc<T>` in the external call-site signature, and the call site passes a dereferenced `*resolved_vc`. This avoids a redundant `.to_resolved().await?` inside the function body. For the SWC wasm plugin case, `serde_json::Value` (used for per-plugin config) doesn't implement `Hash` or `TaskInput`. A `JsonValue` newtype is introduced with: - `#[bincode(with = "turbo_bincode::serde_self_describing")]` for serialization - Manual `Hash` impl that hashes the JSON string representation - Manual `TaskInput` impl (`is_transient = false` since the type contains no `Vc`s) <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tobias Koppers <sokra@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Niklas Mischkulnig <mischnic@users.noreply.github.com>
Author
Parents
Loading