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 <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: Niklas Mischkulnig <[email protected]>
This commit is contained in:
Tobias Koppers
2026-04-16 08:17:22 +02:00
committed by GitHub
co-authored by Tobias Koppers Claude Niklas Mischkulnig
parent a584bb8794
commit f49722285b
28 changed files with 698 additions and 317 deletions
+24 -22
View File
@@ -30,7 +30,7 @@ pub async fn get_next_client_transforms_rules(
) -> Result<Vec<ModuleRule>> {
let mut rules = vec![];
let modularize_imports_config = &next_config.modularize_imports().await?;
let modularize_imports_config = next_config.modularize_imports();
let enable_mdx_rs = next_config.mdx_rs().await?.is_some();
let page_extensions: Vec<String> = next_config
.page_extensions()
@@ -40,14 +40,13 @@ pub async fn get_next_client_transforms_rules(
.collect();
if !foreign_code {
rules.push(get_next_lint_transform_rule(enable_mdx_rs));
rules.push(get_next_lint_transform_rule(enable_mdx_rs).await?);
}
if !modularize_imports_config.is_empty() {
rules.push(get_next_modularize_imports_rule(
modularize_imports_config,
enable_mdx_rs,
));
if !modularize_imports_config.await?.is_empty() {
rules.push(
get_next_modularize_imports_rule(modularize_imports_config, enable_mdx_rs).await?,
);
}
// This is purely a performance optimization:
@@ -64,11 +63,11 @@ pub async fn get_next_client_transforms_rules(
))],
));
rules.push(get_next_font_transform_rule(enable_mdx_rs));
rules.push(get_next_font_transform_rule(enable_mdx_rs).await?);
let is_development = mode.await?.is_development();
if is_development {
rules.push(get_debug_fn_name_rule(enable_mdx_rs));
rules.push(get_debug_fn_name_rule(enable_mdx_rs).await?);
}
let use_cache_enabled = *next_config.enable_use_cache().await?;
@@ -78,17 +77,20 @@ pub async fn get_next_client_transforms_rules(
match &context_ty {
ClientContextType::Pages { pages_dir } => {
if !foreign_code {
rules.push(get_next_pages_transforms_rule(
pages_dir.clone(),
ExportFilter::StripDataExports,
enable_mdx_rs,
vec![],
&page_extensions,
)?);
rules.push(get_next_disallow_export_all_in_page_rule(
enable_mdx_rs,
pages_dir.clone(),
));
rules.push(
get_next_pages_transforms_rule(
pages_dir.clone(),
ExportFilter::StripDataExports,
enable_mdx_rs,
vec![],
&page_extensions,
)
.await?,
);
rules.push(
get_next_disallow_export_all_in_page_rule(enable_mdx_rs, pages_dir.clone())
.await?,
);
}
}
ClientContextType::App { .. } => {
@@ -109,8 +111,8 @@ pub async fn get_next_client_transforms_rules(
};
if !foreign_code {
rules.push(get_next_cjs_optimizer_rule(enable_mdx_rs));
rules.push(get_next_pure_rule(enable_mdx_rs));
rules.push(get_next_cjs_optimizer_rule(enable_mdx_rs).await?);
rules.push(get_next_pure_rule(enable_mdx_rs).await?);
rules.push(
get_next_dynamic_transform_rule(false, false, is_app_dir, mode, enable_mdx_rs).await?,
+41 -24
View File
@@ -27,7 +27,8 @@ use turbopack_core::{
};
use turbopack_css::chunk::CssChunkType;
use turbopack_ecmascript::{
AnalyzeMode, TypeofWindow, chunk::EcmascriptChunkType, references::esm::UrlRewriteBehavior,
AnalyzeMode, CustomTransformer, TransformPlugin, TypeofWindow, chunk::EcmascriptChunkType,
references::esm::UrlRewriteBehavior,
};
use turbopack_ecmascript_plugins::transform::directives::{
client::ClientDirectiveTransformer, client_disallowed::ClientDisallowedDirectiveTransformer,
@@ -752,17 +753,18 @@ pub async fn get_server_module_options_context(
ecmascript_client_reference_transition_name,
..
} => {
let client_directive_transformer = ecmascript_client_reference_transition_name.map(
|ecmascript_client_reference_transition_name| {
get_ecma_transform_rule(
Box::new(ClientDirectiveTransformer::new(
ecmascript_client_reference_transition_name,
)),
let client_directive_transformer =
if let Some(name) = ecmascript_client_reference_transition_name {
Some(get_ecma_transform_rule(
client_directive_transform_plugin(name)
.to_resolved()
.await?,
enable_mdx_rs.is_some(),
EcmascriptTransformStage::Preprocess,
)
},
);
))
} else {
None
};
foreign_next_server_rules.extend(internal_custom_rules);
foreign_next_server_rules.extend(client_directive_transformer.clone());
@@ -839,9 +841,9 @@ pub async fn get_server_module_options_context(
ecmascript_client_reference_transition_name
{
common_next_server_rules.push(get_ecma_transform_rule(
Box::new(ClientDirectiveTransformer::new(
ecmascript_client_reference_transition_name,
)),
client_directive_transform_plugin(ecmascript_client_reference_transition_name)
.to_resolved()
.await?,
enable_mdx_rs.is_some(),
EcmascriptTransformStage::Preprocess,
));
@@ -910,26 +912,28 @@ pub async fn get_server_module_options_context(
app_dir,
ecmascript_client_reference_transition_name,
} => {
let custom_source_transform_rules: Vec<ModuleRule> = vec![
if let Some(ecmascript_client_reference_transition_name) =
ecmascript_client_reference_transition_name
{
let directive_transform_rule =
if let Some(name) = ecmascript_client_reference_transition_name {
get_ecma_transform_rule(
Box::new(ClientDirectiveTransformer::new(
ecmascript_client_reference_transition_name,
)),
client_directive_transform_plugin(name)
.to_resolved()
.await?,
enable_mdx_rs.is_some(),
EcmascriptTransformStage::Preprocess,
)
} else {
get_ecma_transform_rule(
Box::new(ClientDisallowedDirectiveTransformer::new(
"next/dist/client/use-client-disallowed.js".to_string(),
)),
client_disallowed_directive_transform_plugin(rcstr!(
"next/dist/client/use-client-disallowed.js"
))
.to_resolved()
.await?,
enable_mdx_rs.is_some(),
EcmascriptTransformStage::Preprocess,
)
},
};
let custom_source_transform_rules: Vec<ModuleRule> = vec![
directive_transform_rule,
get_next_react_server_components_transform_rule(next_config, true, app_dir).await?,
];
@@ -993,6 +997,19 @@ pub async fn get_server_module_options_context(
Ok(module_options_context)
}
#[turbo_tasks::function]
fn client_directive_transform_plugin(transition_name: RcStr) -> Vc<TransformPlugin> {
Vc::cell(Box::new(ClientDirectiveTransformer::new(transition_name))
as Box<dyn CustomTransformer + Send + Sync>)
}
#[turbo_tasks::function]
fn client_disallowed_directive_transform_plugin(error_proxy_module: RcStr) -> Vc<TransformPlugin> {
Vc::cell(Box::new(ClientDisallowedDirectiveTransformer::new(
error_proxy_module.to_string(),
)) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Encode, Decode)]
pub struct ServerChunkingContextOptions {
pub mode: Vc<NextMode>,
+37 -38
View File
@@ -37,7 +37,7 @@ pub async fn get_next_server_transforms_rules(
) -> Result<Vec<ModuleRule>> {
let mut rules = vec![];
let modularize_imports_config = &next_config.modularize_imports().await?;
let modularize_imports_config = next_config.modularize_imports();
let mdx_rs = next_config.mdx_rs().await?.is_some();
let page_extensions: Vec<String> = next_config
.page_extensions()
@@ -47,16 +47,13 @@ pub async fn get_next_server_transforms_rules(
.collect();
if !foreign_code {
rules.push(get_next_lint_transform_rule(mdx_rs));
rules.push(get_next_lint_transform_rule(mdx_rs).await?);
}
if !modularize_imports_config.is_empty() {
rules.push(get_next_modularize_imports_rule(
modularize_imports_config,
mdx_rs,
));
if !modularize_imports_config.await?.is_empty() {
rules.push(get_next_modularize_imports_rule(modularize_imports_config, mdx_rs).await?);
}
rules.push(get_next_font_transform_rule(mdx_rs));
rules.push(get_next_font_transform_rule(mdx_rs).await?);
if !matches!(context_ty, ServerContextType::AppRSC { .. }) {
rules.extend([
@@ -84,19 +81,21 @@ pub async fn get_next_server_transforms_rules(
let is_server_components = match &context_ty {
ServerContextType::Pages { pages_dir } | ServerContextType::PagesApi { pages_dir } => {
if !foreign_code {
rules.push(get_next_disallow_export_all_in_page_rule(
mdx_rs,
pages_dir.clone(),
));
rules.push(get_next_pages_transforms_rule(
pages_dir.clone(),
ExportFilter::StripDefaultExport,
mdx_rs,
vec![RuleCondition::ReferenceType(ReferenceTypeCondition::Entry(
Some(EntryReferenceSubType::PageData),
))],
&page_extensions,
)?);
rules.push(
get_next_disallow_export_all_in_page_rule(mdx_rs, pages_dir.clone()).await?,
);
rules.push(
get_next_pages_transforms_rule(
pages_dir.clone(),
ExportFilter::StripDefaultExport,
mdx_rs,
vec![RuleCondition::ReferenceType(ReferenceTypeCondition::Entry(
Some(EntryReferenceSubType::PageData),
))],
&page_extensions,
)
.await?,
);
}
false
}
@@ -158,10 +157,7 @@ pub async fn get_next_server_transforms_rules(
};
if is_app_dir {
rules.push(get_next_debug_instant_stack_rule(
mdx_rs,
page_extensions.clone(),
));
rules.push(get_next_debug_instant_stack_rule(mdx_rs, page_extensions.clone()).await?);
}
if is_app_dir &&
@@ -170,7 +166,7 @@ pub async fn get_next_server_transforms_rules(
next_runtime != NextRuntime::Edge &&
*next_config.enable_cache_components().await?
{
rules.push(get_next_track_dynamic_imports_transform_rule(mdx_rs));
rules.push(get_next_track_dynamic_imports_transform_rule(mdx_rs).await?);
}
if !foreign_code {
@@ -179,8 +175,8 @@ pub async fn get_next_server_transforms_rules(
.await?,
);
rules.push(get_next_cjs_optimizer_rule(mdx_rs));
rules.push(get_next_pure_rule(mdx_rs));
rules.push(get_next_cjs_optimizer_rule(mdx_rs).await?);
rules.push(get_next_pure_rule(mdx_rs).await?);
// [NOTE]: this rule only works in prod config
// https://github.com/vercel/next.js/blob/a1d0259ea06592c5ca6df882e9b1d0d0121c5083/packages/next/src/build/swc/options.ts#L409
@@ -194,16 +190,19 @@ pub async fn get_next_server_transforms_rules(
let mode = *mode.await?;
if mode == NextMode::Development {
rules.push(get_middleware_dynamic_assert_rule(mdx_rs));
rules.push(get_middleware_dynamic_assert_rule(mdx_rs).await?);
}
if !foreign_code {
rules.push(next_edge_node_api_assert(
mdx_rs,
matches!(context_ty, ServerContextType::Middleware { .. })
&& mode == NextMode::Build,
mode == NextMode::Build,
));
rules.push(
next_edge_node_api_assert(
mdx_rs,
matches!(context_ty, ServerContextType::Middleware { .. })
&& mode == NextMode::Build,
mode == NextMode::Build,
)
.await?,
);
}
if matches!(context_ty, ServerContextType::AppRoute { .. }) {
@@ -246,14 +245,14 @@ pub async fn get_next_server_internal_transforms_rules(
match context_ty {
ServerContextType::Pages { .. } => {
// Apply next/font transforms to foreign code
rules.push(get_next_font_transform_rule(mdx_rs));
rules.push(get_next_font_transform_rule(mdx_rs).await?);
}
ServerContextType::PagesApi { .. } => {}
ServerContextType::AppSSR { .. } => {
rules.push(get_next_font_transform_rule(mdx_rs));
rules.push(get_next_font_transform_rule(mdx_rs).await?);
}
ServerContextType::AppRSC { .. } => {
rules.push(get_next_font_transform_rule(mdx_rs));
rules.push(get_next_font_transform_rule(mdx_rs).await?);
}
ServerContextType::AppRoute { .. } => {}
ServerContextType::Middleware { .. } => {}
@@ -2,25 +2,32 @@ use anyhow::Result;
use async_trait::async_trait;
use next_custom_transforms::transforms::debug_fn_name::debug_fn_name;
use swc_core::ecma::{ast::Program, visit::VisitMutWith};
use turbo_tasks::ResolvedVc;
use turbo_tasks::{ResolvedVc, Vc};
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_js_no_url;
pub fn get_debug_fn_name_rule(enable_mdx_rs: bool) -> ModuleRule {
let debug_fn_name_transform = EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(
DebugFnNameTransformer {},
) as _));
pub async fn get_debug_fn_name_rule(enable_mdx_rs: bool) -> Result<ModuleRule> {
let debug_fn_name_transform =
EcmascriptInputTransform::Plugin(debug_fn_name_transform_plugin().to_resolved().await?);
ModuleRule::new(
// TODO: use get_ecma_transform_rule instead
Ok(ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![debug_fn_name_transform]),
}],
)
))
}
#[turbo_tasks::function]
fn debug_fn_name_transform_plugin() -> Vc<TransformPlugin> {
Vc::cell(Box::new(DebugFnNameTransformer {}) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
@@ -1,6 +1,7 @@
use anyhow::Result;
use turbo_tasks::Vc;
use turbopack::module_options::ModuleRule;
use turbopack_ecmascript::{CustomTransformer, TransformPlugin};
use turbopack_ecmascript_plugins::transform::emotion::EmotionTransformer;
use super::get_ecma_transform_rule;
@@ -11,9 +12,37 @@ use crate::{
pub async fn get_emotion_transform_rule(next_config: Vc<NextConfig>) -> Result<Option<ModuleRule>> {
let enable_mdx_rs = next_config.mdx_rs().await?.is_some();
let module_rule = next_config
let has_config = next_config
.compiler()
.await?
.emotion
.as_ref()
.is_some_and(|config| {
matches!(
config,
EmotionTransformOptionsOrBoolean::Boolean(true)
| EmotionTransformOptionsOrBoolean::Options(_)
)
});
if has_config {
let plugin = emotion_transform_plugin(next_config).to_resolved().await?;
Ok(Some(get_ecma_transform_rule(
plugin,
enable_mdx_rs,
EcmascriptTransformStage::Main,
)))
} else {
Ok(None)
}
}
#[turbo_tasks::function]
async fn emotion_transform_plugin(next_config: Vc<NextConfig>) -> Result<Vc<TransformPlugin>> {
use anyhow::Context as _;
let compiler = next_config.compiler().await?;
let transformer = compiler
.emotion
.as_ref()
.and_then(|config| match config {
@@ -23,13 +52,8 @@ pub async fn get_emotion_transform_rule(next_config: Vc<NextConfig>) -> Result<O
EmotionTransformOptionsOrBoolean::Options(value) => EmotionTransformer::new(value),
_ => None,
})
.map(|transformer| {
get_ecma_transform_rule(
Box::new(transformer),
enable_mdx_rs,
EcmascriptTransformStage::Main,
)
});
Ok(module_rule)
.context("emotion config must exist")?;
Ok(Vc::cell(
Box::new(transformer) as Box<dyn CustomTransformer + Send + Sync>
))
}
@@ -35,7 +35,7 @@ use turbo_tasks::ResolvedVc;
use turbo_tasks_fs::FileSystemPath;
use turbopack::module_options::{ModuleRule, ModuleRuleEffect, ModuleType, RuleCondition};
use turbopack_core::reference_type::ReferenceTypeCondition;
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform};
use turbopack_ecmascript::{EcmascriptInputTransform, TransformPlugin};
use crate::next_image::{StructuredImageModuleType, module::BlurPlaceholderMode};
@@ -134,11 +134,11 @@ pub(crate) enum EcmascriptTransformStage {
/// Create a new module rule for the given ecmatransform, runs against
/// any ecmascript (with mdx if enabled) except url reference type
pub(crate) fn get_ecma_transform_rule(
transformer: Box<dyn CustomTransformer + Send + Sync>,
transformer: ResolvedVc<TransformPlugin>,
enable_mdx_rs: bool,
stage: EcmascriptTransformStage,
) -> ModuleRule {
let transformer = EcmascriptInputTransform::Plugin(ResolvedVc::cell(transformer as _));
let transformer = EcmascriptInputTransform::Plugin(transformer);
let (preprocess, main, postprocess) = match stage {
EcmascriptTransformStage::Preprocess => (vec![transformer], vec![], vec![]),
EcmascriptTransformStage::Main => (vec![], vec![transformer], vec![]),
@@ -6,11 +6,13 @@ use bincode::{Decode, Encode};
use modularize_imports::{Config, PackageConfig, modularize_imports};
use serde::{Deserialize, Serialize};
use swc_core::ecma::ast::Program;
use turbo_tasks::{FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, trace::TraceRawVcs};
use turbo_tasks::{FxIndexMap, NonLocalValue, OperationValue, ResolvedVc, Vc, trace::TraceRawVcs};
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use crate::next_shared::transforms::module_rule_match_js_no_url;
use crate::{next_config::ModularizeImports, next_shared::transforms::module_rule_match_js_no_url};
#[derive(
Clone,
@@ -58,21 +60,35 @@ pub enum Transform {
}
/// Returns a rule which applies the Next.js modularize imports transform.
pub fn get_next_modularize_imports_rule(
modularize_imports_config: &FxIndexMap<String, ModularizeImportPackageConfig>,
pub async fn get_next_modularize_imports_rule(
modularize_imports_config: Vc<ModularizeImports>,
enable_mdx_rs: bool,
) -> ModuleRule {
let transformer = EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(
ModularizeImportsTransformer::new(modularize_imports_config),
) as _));
ModuleRule::new(
) -> Result<ModuleRule> {
let transformer = EcmascriptInputTransform::Plugin(
modularize_imports_transform_plugin(modularize_imports_config)
.to_resolved()
.await?,
);
// TODO: use get_ecma_transform_rule instead
Ok(ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![transformer]),
}],
)
))
}
#[turbo_tasks::function]
async fn modularize_imports_transform_plugin(
config: Vc<ModularizeImports>,
) -> Result<Vc<TransformPlugin>> {
let config = config.await?;
Ok(Vc::cell(
Box::new(ModularizeImportsTransformer::new(&config))
as Box<dyn CustomTransformer + Send + Sync>,
))
}
#[derive(Debug)]
@@ -7,13 +7,31 @@ use swc_core::{
common::SyntaxContext,
ecma::{ast::*, visit::VisitMutWith},
};
use turbo_tasks::ResolvedVc;
use turbo_tasks::{ResolvedVc, Vc};
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_js_no_url;
pub fn get_next_cjs_optimizer_rule(enable_mdx_rs: bool) -> ModuleRule {
pub async fn get_next_cjs_optimizer_rule(enable_mdx_rs: bool) -> Result<ModuleRule> {
let transformer = EcmascriptInputTransform::Plugin(
next_cjs_optimizer_transform_plugin().to_resolved().await?,
);
// TODO: use get_ecma_transform_rule instead
Ok(ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![transformer]),
}],
))
}
#[turbo_tasks::function]
fn next_cjs_optimizer_transform_plugin() -> Vc<TransformPlugin> {
// [NOTE]: This isn't user configurable config
// (https://github.com/vercel/next.js/blob/a1d0259ea06592c5ca6df882e9b1d0d0121c5083/packages/next/src/build/swc/options.ts#L395)
// build it internally without accepting customization.
@@ -47,18 +65,7 @@ pub fn get_next_cjs_optimizer_rule(enable_mdx_rs: bool) -> ModuleRule {
},
)]),
};
let transformer = EcmascriptInputTransform::Plugin(ResolvedVc::cell(
Box::new(NextCjsOptimizer { config }) as _,
));
ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![transformer]),
}],
)
Vc::cell(Box::new(NextCjsOptimizer { config }) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
@@ -2,29 +2,39 @@ use anyhow::Result;
use async_trait::async_trait;
use next_custom_transforms::transforms::debug_instant_stack::debug_instant_stack;
use swc_core::ecma::ast::Program;
use turbo_tasks::ResolvedVc;
use turbo_tasks::{ResolvedVc, Vc};
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_js_no_url;
pub fn get_next_debug_instant_stack_rule(
pub async fn get_next_debug_instant_stack_rule(
enable_mdx_rs: bool,
page_extensions: Vec<String>,
) -> ModuleRule {
let transform =
EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(NextDebugInstantStack {
page_extensions,
}) as _));
) -> Result<ModuleRule> {
let transform = EcmascriptInputTransform::Plugin(
next_debug_instant_stack_transform_plugin(page_extensions)
.to_resolved()
.await?,
);
ModuleRule::new(
// TODO: use get_ecma_transform_rule instead
Ok(ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![transform]),
}],
)
))
}
#[turbo_tasks::function]
fn next_debug_instant_stack_transform_plugin(page_extensions: Vec<String>) -> Vc<TransformPlugin> {
Vc::cell(Box::new(NextDebugInstantStack { page_extensions })
as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
@@ -2,28 +2,37 @@ use anyhow::Result;
use async_trait::async_trait;
use next_custom_transforms::transforms::disallow_re_export_all_in_page::disallow_re_export_all_in_page;
use swc_core::ecma::ast::*;
use turbo_tasks::ResolvedVc;
use turbo_tasks::{ResolvedVc, Vc};
use turbo_tasks_fs::FileSystemPath;
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_pages_page_file;
pub fn get_next_disallow_export_all_in_page_rule(
pub async fn get_next_disallow_export_all_in_page_rule(
enable_mdx_rs: bool,
pages_dir: FileSystemPath,
) -> ModuleRule {
let transformer = EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(
NextDisallowReExportAllInPage,
) as _));
ModuleRule::new(
) -> Result<ModuleRule> {
let transformer = EcmascriptInputTransform::Plugin(
next_disallow_re_export_all_in_page_transform_plugin()
.to_resolved()
.await?,
);
Ok(ModuleRule::new(
module_rule_match_pages_page_file(enable_mdx_rs, pages_dir),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![transformer]),
}],
)
))
}
#[turbo_tasks::function]
fn next_disallow_re_export_all_in_page_transform_plugin() -> Vc<TransformPlugin> {
Vc::cell(Box::new(NextDisallowReExportAllInPage) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
@@ -4,7 +4,9 @@ use next_custom_transforms::transforms::dynamic::{NextDynamicMode, next_dynamic}
use swc_core::{atoms::atom, common::FileName, ecma::ast::Program};
use turbo_tasks::{ResolvedVc, Vc};
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_js_no_url;
use crate::mode::NextMode;
@@ -17,13 +19,12 @@ pub async fn get_next_dynamic_transform_rule(
mode: Vc<NextMode>,
enable_mdx_rs: bool,
) -> Result<ModuleRule> {
let dynamic_transform =
EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(NextJsDynamic {
is_server_compiler,
is_react_server_layer,
is_app_dir,
mode: *mode.await?,
}) as _));
let dynamic_transform = EcmascriptInputTransform::Plugin(
next_dynamic_transform_plugin(is_server_compiler, is_react_server_layer, is_app_dir, mode)
.to_resolved()
.await?,
);
// TODO: use get_ecma_transform_rule instead
Ok(ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
@@ -34,6 +35,21 @@ pub async fn get_next_dynamic_transform_rule(
))
}
#[turbo_tasks::function]
async fn next_dynamic_transform_plugin(
is_server_compiler: bool,
is_react_server_layer: bool,
is_app_dir: bool,
mode: Vc<NextMode>,
) -> Result<Vc<TransformPlugin>> {
Ok(Vc::cell(Box::new(NextJsDynamic {
is_server_compiler,
is_react_server_layer,
is_app_dir,
mode: *mode.await?,
}) as Box<dyn CustomTransformer + Send + Sync>))
}
#[derive(Debug)]
struct NextJsDynamic {
is_server_compiler: bool,
@@ -58,7 +74,6 @@ impl CustomTransformer for NextJsDynamic {
FileName::Real(ctx.file_path_str.into()).into(),
None,
));
Ok(())
}
}
@@ -5,30 +5,44 @@ use swc_core::{
common::SyntaxContext,
ecma::{ast::*, utils::ExprCtx, visit::VisitWith},
};
use turbo_tasks::ResolvedVc;
use turbo_tasks::{ResolvedVc, Vc};
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_js_no_url;
pub fn next_edge_node_api_assert(
pub async fn next_edge_node_api_assert(
enable_mdx_rs: bool,
should_error_for_node_apis: bool,
is_production: bool,
) -> ModuleRule {
let transformer =
EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(NextEdgeNodeApiAssert {
should_error_for_node_apis,
is_production,
}) as _));
ModuleRule::new(
) -> Result<ModuleRule> {
let transformer = EcmascriptInputTransform::Plugin(
next_edge_node_api_assert_transform_plugin(should_error_for_node_apis, is_production)
.to_resolved()
.await?,
);
// TODO: use get_ecma_transform_rule instead
Ok(ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![transformer]),
}],
)
))
}
#[turbo_tasks::function]
fn next_edge_node_api_assert_transform_plugin(
should_error_for_node_apis: bool,
is_production: bool,
) -> Vc<TransformPlugin> {
Vc::cell(Box::new(NextEdgeNodeApiAssert {
should_error_for_node_apis,
is_production,
}) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
@@ -5,26 +5,20 @@ use swc_core::{
atoms::{Wtf8Atom, atom},
ecma::{ast::Program, visit::VisitMutWith},
};
use turbo_tasks::ResolvedVc;
use turbo_tasks::{ResolvedVc, Vc};
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_js_no_url;
/// Returns a rule which applies the Next.js font transform.
pub fn get_next_font_transform_rule(enable_mdx_rs: bool) -> ModuleRule {
let font_loaders = vec![
atom!("next/font/google").into(),
atom!("@next/font/google").into(),
atom!("next/font/local").into(),
atom!("@next/font/local").into(),
];
pub async fn get_next_font_transform_rule(enable_mdx_rs: bool) -> Result<ModuleRule> {
let transformer =
EcmascriptInputTransform::Plugin(ResolvedVc::cell(
Box::new(NextJsFont { font_loaders }) as _
));
ModuleRule::new(
EcmascriptInputTransform::Plugin(next_font_transform_plugin().to_resolved().await?);
// TODO: use get_ecma_transform_rule instead
Ok(ModuleRule::new(
// TODO: Only match in pages (not pages/api), app/, etc.
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
@@ -32,7 +26,18 @@ pub fn get_next_font_transform_rule(enable_mdx_rs: bool) -> ModuleRule {
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![transformer]),
}],
)
))
}
#[turbo_tasks::function]
fn next_font_transform_plugin() -> Vc<TransformPlugin> {
let font_loaders = vec![
atom!("next/font/google").into(),
atom!("@next/font/google").into(),
atom!("next/font/local").into(),
atom!("@next/font/local").into(),
];
Vc::cell(Box::new(NextJsFont { font_loaders }) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
@@ -2,18 +2,24 @@ use anyhow::Result;
use async_trait::async_trait;
use next_custom_transforms::transforms::lint_codemod_comments::lint_codemod_comments;
use swc_core::ecma::{ast::Program, visit::VisitWith};
use turbo_tasks::Vc;
use turbopack::module_options::ModuleRule;
use turbopack_ecmascript::{CustomTransformer, TransformContext};
use turbopack_ecmascript::{CustomTransformer, TransformContext, TransformPlugin};
use super::get_ecma_transform_rule;
use crate::next_shared::transforms::EcmascriptTransformStage;
pub fn get_next_lint_transform_rule(enable_mdx_rs: bool) -> ModuleRule {
get_ecma_transform_rule(
Box::new(LintTransformer {}),
pub async fn get_next_lint_transform_rule(enable_mdx_rs: bool) -> Result<ModuleRule> {
Ok(get_ecma_transform_rule(
lint_transform_plugin().to_resolved().await?,
enable_mdx_rs,
EcmascriptTransformStage::Preprocess,
)
))
}
#[turbo_tasks::function]
fn lint_transform_plugin() -> Vc<TransformPlugin> {
Vc::cell(Box::new(LintTransformer {}) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
@@ -24,7 +30,6 @@ impl CustomTransformer for LintTransformer {
#[tracing::instrument(level = tracing::Level::TRACE, name = "next_custom_lint", skip_all)]
async fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()> {
program.visit_with(&mut lint_codemod_comments(ctx.comments));
Ok(())
}
}
@@ -2,24 +2,34 @@ use anyhow::Result;
use async_trait::async_trait;
use next_custom_transforms::transforms::middleware_dynamic::next_middleware_dynamic;
use swc_core::ecma::{ast::*, visit::VisitMutWith};
use turbo_tasks::ResolvedVc;
use turbo_tasks::{ResolvedVc, Vc};
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_js_no_url;
pub fn get_middleware_dynamic_assert_rule(enable_mdx_rs: bool) -> ModuleRule {
let transformer = EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(
NextMiddlewareDynamicAssert {},
) as _));
ModuleRule::new(
pub async fn get_middleware_dynamic_assert_rule(enable_mdx_rs: bool) -> Result<ModuleRule> {
let transformer = EcmascriptInputTransform::Plugin(
next_middleware_dynamic_assert_transform_plugin()
.to_resolved()
.await?,
);
// TODO: use get_ecma_transform_rule instead
Ok(ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![transformer]),
}],
)
))
}
#[turbo_tasks::function]
fn next_middleware_dynamic_assert_transform_plugin() -> Vc<TransformPlugin> {
Vc::cell(Box::new(NextMiddlewareDynamicAssert {}) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
@@ -2,29 +2,39 @@ use anyhow::Result;
use async_trait::async_trait;
use next_custom_transforms::transforms::optimize_server_react::{Config, optimize_server_react};
use swc_core::ecma::ast::*;
use turbo_tasks::ResolvedVc;
use turbo_tasks::{ResolvedVc, Vc};
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_js_no_url;
#[allow(dead_code)]
pub fn get_next_optimize_server_react_rule(
pub async fn get_next_optimize_server_react_rule(
enable_mdx_rs: bool,
optimize_use_state: bool,
) -> ModuleRule {
let transformer =
EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(NextOptimizeServerReact {
optimize_use_state,
}) as _));
ModuleRule::new(
) -> Result<ModuleRule> {
let transformer = EcmascriptInputTransform::Plugin(
next_optimize_server_react_transform_plugin(optimize_use_state)
.to_resolved()
.await?,
);
// TODO: use get_ecma_transform_rule instead
Ok(ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![transformer]),
}],
)
))
}
#[turbo_tasks::function]
fn next_optimize_server_react_transform_plugin(optimize_use_state: bool) -> Vc<TransformPlugin> {
Vc::cell(Box::new(NextOptimizeServerReact { optimize_use_state })
as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
@@ -2,23 +2,31 @@ use anyhow::Result;
use async_trait::async_trait;
use next_custom_transforms::transforms::pure::pure_magic;
use swc_core::ecma::{ast::*, visit::VisitMutWith};
use turbo_tasks::ResolvedVc;
use turbo_tasks::{ResolvedVc, Vc};
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_js_no_url;
pub fn get_next_pure_rule(enable_mdx_rs: bool) -> ModuleRule {
pub async fn get_next_pure_rule(enable_mdx_rs: bool) -> Result<ModuleRule> {
let transformer =
EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(NextPure {}) as _));
ModuleRule::new(
EcmascriptInputTransform::Plugin(next_pure_transform_plugin().to_resolved().await?);
// TODO: use get_ecma_transform_rule instead
Ok(ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![]),
postprocess: ResolvedVc::cell(vec![transformer]),
}],
)
))
}
#[turbo_tasks::function]
fn next_pure_transform_plugin() -> Vc<TransformPlugin> {
Vc::cell(Box::new(NextPure {}) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
@@ -8,7 +8,7 @@ use swc_core::{
use turbo_tasks::Vc;
use turbo_tasks_fs::FileSystemPath;
use turbopack::module_options::ModuleRule;
use turbopack_ecmascript::{CustomTransformer, TransformContext};
use turbopack_ecmascript::{CustomTransformer, TransformContext, TransformPlugin};
use super::get_ecma_transform_rule;
use crate::{next_config::NextConfig, next_shared::transforms::EcmascriptTransformStage};
@@ -44,19 +44,40 @@ pub async fn get_next_react_server_components_transform_rule(
.map(|s| s.to_string())
.collect::<Vec<_>>();
Ok(get_ecma_transform_rule(
Box::new(NextJsReactServerComponents::new(
next_react_server_components_transform_plugin(
is_react_server_layer,
cache_components_enabled,
use_cache_enabled,
taint_enabled,
app_dir,
page_extensions,
)),
)
.to_resolved()
.await?,
enable_mdx_rs,
EcmascriptTransformStage::Preprocess,
))
}
#[turbo_tasks::function]
fn next_react_server_components_transform_plugin(
is_react_server_layer: bool,
cache_components_enabled: bool,
use_cache_enabled: bool,
taint_enabled: bool,
app_dir: Option<FileSystemPath>,
page_extensions: Vec<String>,
) -> Vc<TransformPlugin> {
Vc::cell(Box::new(NextJsReactServerComponents {
is_react_server_layer,
cache_components_enabled,
use_cache_enabled,
taint_enabled,
app_dir,
page_extensions,
}) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
struct NextJsReactServerComponents {
is_react_server_layer: bool,
@@ -67,26 +88,6 @@ struct NextJsReactServerComponents {
page_extensions: Vec<String>,
}
impl NextJsReactServerComponents {
fn new(
is_react_server_layer: bool,
cache_components_enabled: bool,
use_cache_enabled: bool,
taint_enabled: bool,
app_dir: Option<FileSystemPath>,
page_extensions: Vec<String>,
) -> Self {
Self {
is_react_server_layer,
cache_components_enabled,
use_cache_enabled,
taint_enabled,
app_dir,
page_extensions,
}
}
}
#[async_trait]
impl CustomTransformer for NextJsReactServerComponents {
#[tracing::instrument(level = tracing::Level::TRACE, name = "next_react_server_components", skip_all)]
@@ -1,18 +1,46 @@
use anyhow::Result;
use async_trait::async_trait;
use bincode::{Decode, Encode};
use next_custom_transforms::transforms::strip_page_exports::{
ExportFilter, next_transform_strip_page_exports,
};
use swc_core::ecma::ast::Program;
use turbo_tasks::ResolvedVc;
use turbo_tasks::{ResolvedVc, TaskInput, Vc, trace::TraceRawVcs};
use turbo_tasks_fs::FileSystemPath;
use turbopack::module_options::{ModuleRule, ModuleRuleEffect, RuleCondition};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_js_no_url;
/// A [`TaskInput`]-compatible mirror of [`ExportFilter`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Encode, Decode)]
enum ExportFilterInput {
StripDataExports,
StripDefaultExport,
}
impl From<ExportFilter> for ExportFilterInput {
fn from(filter: ExportFilter) -> Self {
match filter {
ExportFilter::StripDataExports => ExportFilterInput::StripDataExports,
ExportFilter::StripDefaultExport => ExportFilterInput::StripDefaultExport,
}
}
}
impl From<ExportFilterInput> for ExportFilter {
fn from(filter: ExportFilterInput) -> Self {
match filter {
ExportFilterInput::StripDataExports => ExportFilter::StripDataExports,
ExportFilterInput::StripDefaultExport => ExportFilter::StripDefaultExport,
}
}
}
/// Returns a rule which applies the Next.js page export stripping transform.
pub fn get_next_pages_transforms_rule(
pub async fn get_next_pages_transforms_rule(
pages_dir: FileSystemPath,
export_filter: ExportFilter,
enable_mdx_rs: bool,
@@ -20,10 +48,11 @@ pub fn get_next_pages_transforms_rule(
page_extensions: &[String],
) -> Result<ModuleRule> {
// Apply the Next SSG transform to all pages.
let strip_transform =
EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(NextJsStripPageExports {
export_filter,
}) as _));
let strip_transform = EcmascriptInputTransform::Plugin(
next_strip_page_exports_transform_plugin(export_filter.into())
.to_resolved()
.await?,
);
let document_exclusions: Vec<RuleCondition> = page_extensions
.iter()
.map(|ext| {
@@ -53,6 +82,15 @@ pub fn get_next_pages_transforms_rule(
))
}
#[turbo_tasks::function]
fn next_strip_page_exports_transform_plugin(
export_filter: ExportFilterInput,
) -> Vc<TransformPlugin> {
Vc::cell(Box::new(NextJsStripPageExports {
export_filter: export_filter.into(),
}) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
struct NextJsStripPageExports {
export_filter: ExportFilter,
@@ -2,18 +2,26 @@ use anyhow::Result;
use async_trait::async_trait;
use next_custom_transforms::transforms::track_dynamic_imports::*;
use swc_core::ecma::ast::Program;
use turbo_tasks::Vc;
use turbopack::module_options::ModuleRule;
use turbopack_ecmascript::{CustomTransformer, TransformContext};
use turbopack_ecmascript::{CustomTransformer, TransformContext, TransformPlugin};
use super::get_ecma_transform_rule;
use crate::next_shared::transforms::EcmascriptTransformStage;
pub fn get_next_track_dynamic_imports_transform_rule(mdx_rs: bool) -> ModuleRule {
get_ecma_transform_rule(
Box::new(NextTrackDynamicImports {}),
pub async fn get_next_track_dynamic_imports_transform_rule(mdx_rs: bool) -> Result<ModuleRule> {
Ok(get_ecma_transform_rule(
next_track_dynamic_imports_transform_plugin()
.to_resolved()
.await?,
mdx_rs,
EcmascriptTransformStage::Postprocess,
)
))
}
#[turbo_tasks::function]
fn next_track_dynamic_imports_transform_plugin() -> Vc<TransformPlugin> {
Vc::cell(Box::new(NextTrackDynamicImports {}) as Box<dyn CustomTransformer + Send + Sync>)
}
#[derive(Debug)]
@@ -3,7 +3,7 @@ use async_trait::async_trait;
use swc_core::ecma::ast::Program;
use turbo_tasks::Vc;
use turbopack::module_options::ModuleRule;
use turbopack_ecmascript::{CustomTransformer, TransformContext};
use turbopack_ecmascript::{CustomTransformer, TransformContext, TransformPlugin};
use super::get_ecma_transform_rule;
use crate::{
@@ -17,7 +17,33 @@ pub async fn get_react_remove_properties_transform_rule(
) -> Result<Option<ModuleRule>> {
let enable_mdx_rs = next_config.mdx_rs().await?.is_some();
let module_rule = next_config
let has_config = next_config
.compiler()
.await?
.react_remove_properties
.as_ref()
.is_some_and(|config| !matches!(config, ReactRemoveProperties::Boolean(false)));
if has_config {
let plugin = react_remove_properties_transform_plugin(next_config)
.to_resolved()
.await?;
Ok(Some(get_ecma_transform_rule(
plugin,
enable_mdx_rs,
EcmascriptTransformStage::Preprocess,
)))
} else {
Ok(None)
}
}
#[turbo_tasks::function]
async fn react_remove_properties_transform_plugin(
next_config: Vc<NextConfig>,
) -> Result<Vc<TransformPlugin>> {
use anyhow::Context as _;
let config = next_config
.compiler()
.await?
.react_remove_properties
@@ -33,15 +59,11 @@ pub async fn get_react_remove_properties_transform_rule(
}),
),
})
.map(|config| {
get_ecma_transform_rule(
Box::new(ReactRemovePropertiesTransformer { config }),
enable_mdx_rs,
EcmascriptTransformStage::Preprocess,
)
});
Ok(module_rule)
.context("react_remove_properties config must exist")?;
Ok(Vc::cell(
Box::new(ReactRemovePropertiesTransformer { config })
as Box<dyn CustomTransformer + Send + Sync>,
))
}
#[derive(Debug)]
@@ -2,6 +2,7 @@ use anyhow::Result;
use turbo_tasks::Vc;
use turbo_tasks_fs::FileSystemPath;
use turbopack::module_options::ModuleRule;
use turbopack_ecmascript::{CustomTransformer, TransformPlugin};
use turbopack_ecmascript_plugins::transform::relay::RelayTransformer;
use crate::{
@@ -15,13 +16,30 @@ pub async fn get_relay_transform_rule(
project_path: FileSystemPath,
) -> Result<Option<ModuleRule>> {
let enable_mdx_rs = next_config.mdx_rs().await?.is_some();
let module_rule = next_config.compiler().await?.relay.as_ref().map(|config| {
get_ecma_transform_rule(
Box::new(RelayTransformer::new(config, &project_path)),
if next_config.compiler().await?.relay.is_some() {
let plugin = relay_transform_plugin(next_config, project_path)
.to_resolved()
.await?;
Ok(Some(get_ecma_transform_rule(
plugin,
enable_mdx_rs,
EcmascriptTransformStage::Preprocess,
)
});
)))
} else {
Ok(None)
}
}
Ok(module_rule)
#[turbo_tasks::function]
async fn relay_transform_plugin(
next_config: Vc<NextConfig>,
project_path: FileSystemPath,
) -> Result<Vc<TransformPlugin>> {
use anyhow::Context as _;
let compiler = next_config.compiler().await?;
let config = compiler.relay.as_ref().context("relay config must exist")?;
Ok(Vc::cell(
Box::new(RelayTransformer::new(config, &project_path))
as Box<dyn CustomTransformer + Send + Sync>,
))
}
@@ -3,7 +3,7 @@ use async_trait::async_trait;
use swc_core::{common::SyntaxContext, ecma::ast::Program};
use turbo_tasks::Vc;
use turbopack::module_options::ModuleRule;
use turbopack_ecmascript::{CustomTransformer, TransformContext};
use turbopack_ecmascript::{CustomTransformer, TransformContext, TransformPlugin};
use super::get_ecma_transform_rule;
use crate::{
@@ -17,7 +17,33 @@ pub async fn get_remove_console_transform_rule(
) -> Result<Option<ModuleRule>> {
let enable_mdx_rs = next_config.mdx_rs().await?.is_some();
let module_rule = next_config
let has_config = next_config
.compiler()
.await?
.remove_console
.as_ref()
.is_some_and(|config| !matches!(config, RemoveConsoleConfig::Boolean(false)));
if has_config {
let plugin = remove_console_transform_plugin(next_config)
.to_resolved()
.await?;
Ok(Some(get_ecma_transform_rule(
plugin,
enable_mdx_rs,
EcmascriptTransformStage::Preprocess,
)))
} else {
Ok(None)
}
}
#[turbo_tasks::function]
async fn remove_console_transform_plugin(
next_config: Vc<NextConfig>,
) -> Result<Vc<TransformPlugin>> {
use anyhow::Context as _;
let config = next_config
.compiler()
.await?
.remove_console
@@ -36,15 +62,10 @@ pub async fn get_remove_console_transform_rule(
},
)),
})
.map(|config| {
get_ecma_transform_rule(
Box::new(RemoveConsoleTransformer { config }),
enable_mdx_rs,
EcmascriptTransformStage::Preprocess,
)
});
Ok(module_rule)
.context("remove_console config must exist")?;
Ok(Vc::cell(
Box::new(RemoveConsoleTransformer { config }) as Box<dyn CustomTransformer + Send + Sync>
))
}
#[derive(Debug)]
@@ -1,18 +1,21 @@
use anyhow::Result;
use async_trait::async_trait;
use bincode::{Decode, Encode};
use next_custom_transforms::transforms::server_actions::{
Config, ServerActionsMode, server_actions,
};
use swc_core::{common::FileName, ecma::ast::Program};
use turbo_rcstr::RcStr;
use turbo_tasks::{ResolvedVc, Vc};
use turbo_tasks::{ResolvedVc, TaskInput, Vc, trace::TraceRawVcs};
use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};
use turbopack_ecmascript::{
CustomTransformer, EcmascriptInputTransform, TransformContext, TransformPlugin,
};
use super::module_rule_match_js_no_url;
use crate::{mode::NextMode, next_config::CacheKinds};
#[derive(Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Encode, Decode)]
pub enum ActionsTransform {
/// Browser and SSR
Client,
@@ -29,14 +32,18 @@ pub async fn get_server_actions_transform_rule(
use_cache_enabled: bool,
cache_kinds: ResolvedVc<CacheKinds>,
) -> Result<ModuleRule> {
let transformer =
EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(NextServerActions {
mode: *mode.await?,
let transformer = EcmascriptInputTransform::Plugin(
next_server_actions_transform_plugin(
mode,
transform,
encryption_key,
*encryption_key,
use_cache_enabled,
cache_kinds,
}) as _));
*cache_kinds,
)
.to_resolved()
.await?,
);
// TODO: use get_ecma_transform_rule instead
Ok(ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
@@ -47,9 +54,26 @@ pub async fn get_server_actions_transform_rule(
))
}
#[turbo_tasks::function]
async fn next_server_actions_transform_plugin(
mode: Vc<NextMode>,
transform: ActionsTransform,
encryption_key: ResolvedVc<RcStr>,
use_cache_enabled: bool,
cache_kinds: ResolvedVc<CacheKinds>,
) -> Result<Vc<TransformPlugin>> {
Ok(Vc::cell(Box::new(NextServerActions {
mode: *mode.await?,
is_react_server_layer: matches!(transform, ActionsTransform::Server),
encryption_key,
use_cache_enabled,
cache_kinds,
}) as Box<dyn CustomTransformer + Send + Sync>))
}
#[derive(Debug)]
struct NextServerActions {
transform: ActionsTransform,
is_react_server_layer: bool,
encryption_key: ResolvedVc<RcStr>,
use_cache_enabled: bool,
cache_kinds: ResolvedVc<CacheKinds>,
@@ -64,7 +88,7 @@ impl CustomTransformer for NextServerActions {
&FileName::Real(ctx.file_path_str.into()),
Some(ctx.query_str.clone()),
Config {
is_react_server_layer: matches!(self.transform, ActionsTransform::Server),
is_react_server_layer: self.is_react_server_layer,
is_development: self.mode.is_development(),
use_cache_enabled: self.use_cache_enabled,
hash_salt: self.encryption_key.await?.to_string(),
@@ -1,6 +1,7 @@
use anyhow::Result;
use turbo_tasks::Vc;
use turbopack::module_options::ModuleRule;
use turbopack_ecmascript::{CustomTransformer, TransformPlugin};
use turbopack_ecmascript_plugins::transform::styled_components::StyledComponentsTransformer;
use crate::{
@@ -13,9 +14,39 @@ pub async fn get_styled_components_transform_rule(
) -> Result<Option<ModuleRule>> {
let enable_mdx_rs = next_config.mdx_rs().await?.is_some();
let module_rule = next_config
let has_config = next_config
.compiler()
.await?
.styled_components
.as_ref()
.is_some_and(|config| {
!matches!(
config,
StyledComponentsTransformOptionsOrBoolean::Boolean(false)
)
});
if has_config {
let plugin = styled_components_transform_plugin(next_config)
.to_resolved()
.await?;
Ok(Some(get_ecma_transform_rule(
plugin,
enable_mdx_rs,
EcmascriptTransformStage::Main,
)))
} else {
Ok(None)
}
}
#[turbo_tasks::function]
async fn styled_components_transform_plugin(
next_config: Vc<NextConfig>,
) -> Result<Vc<TransformPlugin>> {
use anyhow::Context as _;
let compiler = next_config.compiler().await?;
let transformer = compiler
.styled_components
.as_ref()
.and_then(|config| match config {
@@ -27,13 +58,8 @@ pub async fn get_styled_components_transform_rule(
}
_ => None,
})
.map(|transformer| {
get_ecma_transform_rule(
Box::new(transformer),
enable_mdx_rs,
EcmascriptTransformStage::Main,
)
});
Ok(module_rule)
.context("styled_components config must exist")?;
Ok(Vc::cell(
Box::new(transformer) as Box<dyn CustomTransformer + Send + Sync>
))
}
@@ -2,6 +2,7 @@ use anyhow::Result;
use turbo_tasks::Vc;
use turbopack::module_options::ModuleRule;
use turbopack_core::environment::RuntimeVersions;
use turbopack_ecmascript::{CustomTransformer, TransformPlugin};
use turbopack_ecmascript_plugins::transform::styled_jsx::StyledJsxTransformer;
use super::get_ecma_transform_rule;
@@ -13,12 +14,22 @@ pub async fn get_styled_jsx_transform_rule(
target_browsers: Vc<RuntimeVersions>,
) -> Result<Option<ModuleRule>> {
let enable_mdx_rs = next_config.mdx_rs().await?.is_some();
let versions = *target_browsers.await?;
let transformer = StyledJsxTransformer::new(versions);
let plugin = styled_jsx_transform_plugin(target_browsers)
.to_resolved()
.await?;
Ok(Some(get_ecma_transform_rule(
Box::new(transformer),
plugin,
enable_mdx_rs,
EcmascriptTransformStage::Main,
)))
}
#[turbo_tasks::function]
async fn styled_jsx_transform_plugin(
target_browsers: Vc<RuntimeVersions>,
) -> Result<Vc<TransformPlugin>> {
let versions = *target_browsers.await?;
Ok(Vc::cell(
Box::new(StyledJsxTransformer::new(versions)) as Box<dyn CustomTransformer + Send + Sync>
))
}
@@ -1,12 +1,38 @@
use anyhow::Result;
use bincode::{Decode, Encode};
use serde::{Deserialize, Serialize};
use turbo_rcstr::RcStr;
use turbo_tasks::Vc;
use turbo_tasks::{ResolvedVc, Vc, trace::TraceRawVcs};
use turbo_tasks_fs::FileSystemPath;
use turbopack::module_options::ModuleRule;
use turbopack_core::{context::AssetContext, resolve::origin::ResolveOrigin};
use turbopack_ecmascript::{CustomTransformer, TransformPlugin};
use crate::next_config::NextConfig;
/// A wrapper around [`serde_json::Value`] that implements [`turbo_tasks::TaskInput`].
///
/// [`serde_json::Value`] does not implement [`std::hash::Hash`], so we implement it manually by
/// hashing the serialized JSON string.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, Encode, Decode)]
pub struct JsonValue(#[bincode(with = "turbo_bincode::serde_self_describing")] serde_json::Value);
impl std::hash::Hash for JsonValue {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
serde_json::to_string(&self.0)
.expect("JSON serialization should never fail")
.hash(state);
}
}
// Manual impl because `serde_json::Value` doesn't implement `TaskInput`, but `JsonValue` can
// never contain any `Vc` types, so `is_transient` is always `false`.
impl turbo_tasks::TaskInput for JsonValue {
fn is_transient(&self) -> bool {
false
}
}
pub async fn get_swc_ecma_transform_plugin_rule(
next_config: Vc<NextConfig>,
project_path: FileSystemPath,
@@ -61,9 +87,7 @@ pub async fn get_swc_ecma_transform_rule_impl(
reference_type::{CommonJsReferenceSubType, ReferenceType},
resolve::{ResolveErrorMode, error::handle_resolve_error, parse::Request, resolve},
};
use turbopack_ecmascript_plugins::transform::swc_ecma_transform_plugins::{
SwcEcmaTransformPluginsTransformer, SwcPluginModule,
};
use turbopack_ecmascript_plugins::transform::swc_ecma_transform_plugins::SwcPluginModule;
use turbopack_resolve::{
resolve::resolve_options, resolve_options_context::ResolveOptionsContext,
};
@@ -134,7 +158,7 @@ pub async fn get_swc_ecma_transform_rule_impl(
Ok(Some((
SwcPluginModule::new(name.clone(), file.content().to_bytes().to_vec())
.resolved_cell(),
config.clone(),
JsonValue(config.clone()),
)))
}
})
@@ -142,8 +166,25 @@ pub async fn get_swc_ecma_transform_rule_impl(
.await?;
Ok(Some(get_ecma_transform_rule(
Box::new(SwcEcmaTransformPluginsTransformer::new(plugins)),
swc_ecma_transform_plugins_transform_plugin(plugins)
.to_resolved()
.await?,
enable_mdx_rs,
EcmascriptTransformStage::Main,
)))
}
#[turbo_tasks::function]
fn swc_ecma_transform_plugins_transform_plugin(
plugins: Vec<(
ResolvedVc<
turbopack_ecmascript_plugins::transform::swc_ecma_transform_plugins::SwcPluginModule,
>,
JsonValue,
)>,
) -> Vc<TransformPlugin> {
use turbopack_ecmascript_plugins::transform::swc_ecma_transform_plugins::SwcEcmaTransformPluginsTransformer;
Vc::cell(Box::new(SwcEcmaTransformPluginsTransformer::new(
plugins.into_iter().map(|(m, v)| (m, v.0)).collect(),
)) as Box<dyn CustomTransformer + Send + Sync>)
}
@@ -53,7 +53,8 @@ use turbopack_core::{
reference_type::{EntryReferenceSubType, ReferenceType, ReferenceTypeCondition},
};
use turbopack_ecmascript::{
AnalyzeMode, EcmascriptInputTransform, TreeShakingMode, chunk::EcmascriptChunkType,
AnalyzeMode, CustomTransformer, EcmascriptInputTransform, TransformPlugin, TreeShakingMode,
chunk::EcmascriptChunkType,
};
use turbopack_ecmascript_plugins::transform::{
emotion::{EmotionTransformConfig, EmotionTransformer},
@@ -374,13 +375,10 @@ async fn run_test_operation(resource: RcStr) -> Result<Vc<FileSystemPath>> {
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
preprocess: ResolvedVc::cell(vec![]),
main: ResolvedVc::cell(vec![
EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(
EmotionTransformer::new(&EmotionTransformConfig::default())
.expect("Should be able to create emotion transformer"),
) as _)),
EcmascriptInputTransform::Plugin(ResolvedVc::cell(Box::new(
StyledComponentsTransformer::new(&StyledComponentsTransformConfig::default()),
) as _)),
EcmascriptInputTransform::Plugin(emotion_transform_plugin().to_resolved().await?),
EcmascriptInputTransform::Plugin(
styled_components_transform_plugin().to_resolved().await?,
),
]),
postprocess: ResolvedVc::cell(vec![]),
}],
@@ -677,3 +675,18 @@ async fn maybe_load_env(
Ok(Some(asset))
}
#[turbo_tasks::function]
fn emotion_transform_plugin() -> Vc<TransformPlugin> {
Vc::cell(Box::new(
EmotionTransformer::new(&EmotionTransformConfig::default())
.expect("Should be able to create emotion transformer"),
) as Box<dyn CustomTransformer + Send + Sync>)
}
#[turbo_tasks::function]
fn styled_components_transform_plugin() -> Vc<TransformPlugin> {
Vc::cell(Box::new(StyledComponentsTransformer::new(
&StyledComponentsTransformConfig::default(),
)) as Box<dyn CustomTransformer + Send + Sync>)
}