diff --git a/Cargo.lock b/Cargo.lock index 6aca81d44ae1d..0e947af7852da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8869,11 +8869,14 @@ name = "turbopack-ecmascript-plugins" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "serde", "swc_core", "swc_emotion", + "swc_relay", "turbo-tasks", "turbo-tasks-build", + "turbo-tasks-fs", "turbopack-ecmascript", ] diff --git a/crates/turbopack-ecmascript-plugins/Cargo.toml b/crates/turbopack-ecmascript-plugins/Cargo.toml index 399d9bae5d4cb..b9552a5796813 100644 --- a/crates/turbopack-ecmascript-plugins/Cargo.toml +++ b/crates/turbopack-ecmascript-plugins/Cargo.toml @@ -14,13 +14,16 @@ transform_emotion = [] [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } serde = { workspace = true } turbo-tasks = { workspace = true } +turbo-tasks-fs = { workspace = true } turbopack-ecmascript = { workspace = true } swc_core = { workspace = true, features = ["ecma_ast", "ecma_visit", "common"] } swc_emotion = { workspace = true } +swc_relay = { workspace = true } [build-dependencies] turbo-tasks-build = { workspace = true } diff --git a/crates/turbopack-ecmascript-plugins/src/transform/emotion.rs b/crates/turbopack-ecmascript-plugins/src/transform/emotion.rs index 63c1eb184759a..be0e0b8038f22 100644 --- a/crates/turbopack-ecmascript-plugins/src/transform/emotion.rs +++ b/crates/turbopack-ecmascript-plugins/src/transform/emotion.rs @@ -5,6 +5,7 @@ use std::{ }; use anyhow::Result; +use async_trait::async_trait; use serde::{Deserialize, Serialize}; use swc_core::{ common::util::take::Take, @@ -91,8 +92,13 @@ impl EmotionTransformer { } } +#[async_trait] impl CustomTransformer for EmotionTransformer { - fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Option { + async fn transform( + &self, + program: &mut Program, + ctx: &TransformContext<'_>, + ) -> Result> { #[cfg(feature = "transform_emotion")] { let p = std::mem::replace(program, Program::Module(Module::dummy())); @@ -111,7 +117,7 @@ impl CustomTransformer for EmotionTransformer { )); } - None + Ok(None) } } diff --git a/crates/turbopack-ecmascript-plugins/src/transform/mod.rs b/crates/turbopack-ecmascript-plugins/src/transform/mod.rs index 3697907f45254..ba33ce94d8bd6 100644 --- a/crates/turbopack-ecmascript-plugins/src/transform/mod.rs +++ b/crates/turbopack-ecmascript-plugins/src/transform/mod.rs @@ -1 +1,2 @@ pub mod emotion; +pub mod relay; diff --git a/crates/turbopack-ecmascript-plugins/src/transform/relay.rs b/crates/turbopack-ecmascript-plugins/src/transform/relay.rs new file mode 100644 index 0000000000000..4e13c7537f0c2 --- /dev/null +++ b/crates/turbopack-ecmascript-plugins/src/transform/relay.rs @@ -0,0 +1,57 @@ +use std::path::PathBuf; + +use anyhow::Result; +use async_trait::async_trait; +use swc_core::{ + common::{util::take::Take, FileName}, + ecma::{ + ast::{Module, Program}, + visit::FoldWith, + }, +}; +use turbopack_ecmascript::{CustomTransformer, TransformContext}; + +#[derive(Debug)] +pub struct RelayTransformer { + config: swc_relay::Config, +} + +impl RelayTransformer { + pub fn new(config: swc_relay::Config) -> Self { + Self { config } + } +} + +#[async_trait] +impl CustomTransformer for RelayTransformer { + async fn transform( + &self, + program: &mut Program, + ctx: &TransformContext<'_>, + ) -> Result> { + // If user supplied artifact_directory, it should be resolvable already. + // Otherwise, supply default relative path (./__generated__) + let (root, config) = if self.config.artifact_directory.is_some() { + (PathBuf::new(), None) + } else { + let config = swc_relay::Config { + artifact_directory: Some(PathBuf::from("__generated__")), + ..self.config + }; + (PathBuf::from("."), Some(config)) + }; + + let p = std::mem::replace(program, Program::Module(Module::dummy())); + *program = p.fold_with(&mut swc_relay::relay( + config.as_ref().unwrap_or_else(|| &self.config), + FileName::Real(PathBuf::from(ctx.file_name_str)), + root, + // [TODO]: pages_dir comes through next-swc-loader + // https://github.com/vercel/next.js/blob/ea472e8058faea8ebdab2ef6d3aab257a1f0d11c/packages/next/src/build/webpack-config.ts#L792 + None, + Some(ctx.unresolved_mark), + )); + + Ok(None) + } +} diff --git a/crates/turbopack-ecmascript/src/lib.rs b/crates/turbopack-ecmascript/src/lib.rs index ad92d35af8d4b..a83f07615efb3 100644 --- a/crates/turbopack-ecmascript/src/lib.rs +++ b/crates/turbopack-ecmascript/src/lib.rs @@ -42,8 +42,9 @@ use swc_core::{ }, }; pub use transform::{ - CustomTransformer, EcmascriptInputTransform, EcmascriptInputTransformsVc, TransformContext, - TransformPlugin, TransformPluginVc, + CustomTransformer, EcmascriptInputTransform, EcmascriptInputTransformsVc, + OptionTransformPlugin, OptionTransformPluginVc, TransformContext, TransformPlugin, + TransformPluginVc, }; use turbo_tasks::{ primitives::StringVc, trace::TraceRawVcs, RawVc, ReadRef, TryJoinIterExt, Value, ValueToString, diff --git a/crates/turbopack-ecmascript/src/transform/mod.rs b/crates/turbopack-ecmascript/src/transform/mod.rs index f58f51c9500bf..aca096b2e0675 100644 --- a/crates/turbopack-ecmascript/src/transform/mod.rs +++ b/crates/turbopack-ecmascript/src/transform/mod.rs @@ -4,6 +4,7 @@ mod util; use std::{fmt::Debug, hash::Hash, path::PathBuf, sync::Arc}; use anyhow::Result; +use async_trait::async_trait; use swc_core::{ base::SwcComments, common::{chain, util::take::Take, FileName, Mark, SourceMap}, @@ -77,8 +78,13 @@ pub enum EcmascriptInputTransform { /// The CustomTransformer trait allows you to implement your own custom SWC /// transformer to run over all ECMAScript files imported in the graph. +#[async_trait] pub trait CustomTransformer: Debug { - fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Option; + async fn transform( + &self, + program: &mut Program, + ctx: &TransformContext<'_>, + ) -> Result>; } /// A wrapper around a TransformPlugin instance, allowing it to operate with @@ -93,9 +99,23 @@ pub trait CustomTransformer: Debug { #[derive(Debug)] pub struct TransformPlugin(#[turbo_tasks(trace_ignore)] Box); +#[turbo_tasks::value(transparent)] +pub struct OptionTransformPlugin(Option); + +impl Default for OptionTransformPluginVc { + fn default() -> Self { + OptionTransformPluginVc::cell(None) + } +} + +#[async_trait] impl CustomTransformer for TransformPlugin { - fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Option { - self.0.transform(program, ctx) + async fn transform( + &self, + program: &mut Program, + ctx: &TransformContext<'_>, + ) -> Result> { + self.0.transform(program, ctx).await } } @@ -323,7 +343,7 @@ impl EcmascriptInputTransform { } } EcmascriptInputTransform::Plugin(transform) => { - if let Some(output) = transform.await?.transform(program, ctx) { + if let Some(output) = transform.await?.transform(program, ctx).await? { *program = output; } } diff --git a/crates/turbopack/src/module_options/mod.rs b/crates/turbopack/src/module_options/mod.rs index f5e6a0afd2289..9876d5659ba6e 100644 --- a/crates/turbopack/src/module_options/mod.rs +++ b/crates/turbopack/src/module_options/mod.rs @@ -79,6 +79,7 @@ impl ModuleOptionsVc { preset_env_versions, ref custom_ecmascript_app_transforms, ref custom_ecmascript_transforms, + ref custom_ecma_transform_plugins, ref custom_rules, execution_context, ref rules, @@ -93,7 +94,30 @@ impl ModuleOptionsVc { } } } - let mut transforms = custom_ecmascript_app_transforms.clone(); + + let (before_transform_plugins, after_transform_plugins) = + if let Some(transform_plugins) = custom_ecma_transform_plugins { + let transform_plugins = transform_plugins.await?; + ( + transform_plugins + .source_transforms + .iter() + .cloned() + .map(EcmascriptInputTransform::Plugin) + .collect(), + transform_plugins + .output_transforms + .iter() + .cloned() + .map(|plugin| EcmascriptInputTransform::Plugin(plugin)) + .collect(), + ) + } else { + (vec![], vec![]) + }; + + let mut transforms = before_transform_plugins; + transforms.extend(custom_ecmascript_app_transforms.iter().cloned()); transforms.extend(custom_ecmascript_transforms.iter().cloned()); // Order of transforms is important. e.g. if the React transform occurs before @@ -182,6 +206,7 @@ impl ModuleOptionsVc { .iter() .cloned() .chain(transforms.iter().cloned()) + .chain(after_transform_plugins.iter().cloned()) .collect(), ) } else { @@ -202,6 +227,7 @@ impl ModuleOptionsVc { .iter() .cloned() .chain(transforms.iter().cloned()) + .chain(after_transform_plugins.iter().cloned()) .collect(), ); @@ -222,6 +248,7 @@ impl ModuleOptionsVc { .iter() .cloned() .chain(transforms.iter().cloned()) + .chain(after_transform_plugins.iter().cloned()) .collect(), ); diff --git a/crates/turbopack/src/module_options/module_options_context.rs b/crates/turbopack/src/module_options/module_options_context.rs index a3451616007d1..b361f5ebace46 100644 --- a/crates/turbopack/src/module_options/module_options_context.rs +++ b/crates/turbopack/src/module_options/module_options_context.rs @@ -2,7 +2,7 @@ use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use turbo_tasks::trace::TraceRawVcs; use turbopack_core::{environment::EnvironmentVc, resolve::options::ImportMappingVc}; -use turbopack_ecmascript::EcmascriptInputTransform; +use turbopack_ecmascript::{EcmascriptInputTransform, TransformPluginVc}; use turbopack_ecmascript_plugins::transform::emotion::EmotionTransformConfigVc; use turbopack_node::{ execution_context::ExecutionContextVc, transforms::webpack::WebpackLoaderConfigItemsVc, @@ -156,6 +156,18 @@ impl Default for StyledComponentsTransformConfigVc { } } +/// Configuration options for the custom ecma transform to be applied. +#[turbo_tasks::value(shared)] +#[derive(Default, Clone)] +pub struct CustomEcmascriptTransformPlugins { + /// List of plugins to be applied before the main transform. + /// Transform will be applied in the order of the list. + pub source_transforms: Vec, + /// List of plugins to be applied after the main transform. + /// Transform will be applied in the order of the list. + pub output_transforms: Vec, +} + #[turbo_tasks::value(shared)] #[derive(Default, Clone)] pub struct ModuleOptionsContext { @@ -187,11 +199,15 @@ pub struct ModuleOptionsContext { pub enable_mdx_rs: bool, #[serde(default)] pub preset_env_versions: Option, + #[deprecated(note = "use custom_ecma_transform_plugins instead")] #[serde(default)] pub custom_ecmascript_app_transforms: Vec, + #[deprecated(note = "use custom_ecma_transform_plugins instead")] #[serde(default)] pub custom_ecmascript_transforms: Vec, #[serde(default)] + pub custom_ecma_transform_plugins: Option, + #[serde(default)] /// Custom rules to be applied after all default rules. pub custom_rules: Vec, #[serde(default)]