diff --git a/Cargo.lock b/Cargo.lock index 1f7917909ba63..eb6e31c24980e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5817,6 +5817,7 @@ dependencies = [ "swc_ecma_transforms_base", "swc_ecma_transforms_module", "swc_ecma_transforms_optimization", + "swc_ecma_transforms_proposal", "swc_ecma_transforms_react", "swc_ecma_transforms_testing", "swc_ecma_transforms_typescript", diff --git a/crates/turbopack-ecmascript/Cargo.toml b/crates/turbopack-ecmascript/Cargo.toml index 724e3af85c299..e6bc843d353ab 100644 --- a/crates/turbopack-ecmascript/Cargo.toml +++ b/crates/turbopack-ecmascript/Cargo.toml @@ -52,6 +52,7 @@ swc_core = { workspace = true, features = [ "ecma_transforms_module", "ecma_transforms_react", "ecma_transforms_typescript", + "ecma_transforms_proposal", "ecma_quote", "ecma_visit", "ecma_visit_path", diff --git a/crates/turbopack-ecmascript/src/parse.rs b/crates/turbopack-ecmascript/src/parse.rs index 84c1aa9170618..6fbb0f472e5f9 100644 --- a/crates/turbopack-ecmascript/src/parse.rs +++ b/crates/turbopack-ecmascript/src/parse.rs @@ -23,10 +23,12 @@ use turbo_tasks::{ primitives::{StringVc, U64Vc}, Value, ValueToString, }; -use turbo_tasks_fs::{FileContent, FileSystemPath}; +use turbo_tasks_fs::{FileContent, FileJsonContentVc, FileSystemPath}; use turbo_tasks_hash::hash_xxh3_hash64; use turbopack_core::{ asset::{Asset, AssetContent, AssetVc}, + resolve::{find_context_file, node::node_cjs_resolve_options, FindContextFileResult}, + source_asset::SourceAssetVc, source_map::{GenerateSourceMap, GenerateSourceMapVc, SourceMapVc}, }; use turbopack_swc_utils::emitter::IssueEmitter; @@ -35,6 +37,7 @@ use super::EcmascriptModuleAssetType; use crate::{ analyzer::graph::EvalContext, transform::{EcmascriptInputTransformsVc, TransformContext}, + typescript::resolve::{read_tsconfigs, tsconfig}, utils::WrapFuture, EcmascriptInputTransform, }; @@ -138,6 +141,19 @@ pub async fn parse( let fs_path = &*source.ident().path().await?; let file_path_hash = *hash_ident(source.ident().to_string()).await? as u128; let ty = ty.into_value(); + let tsconfig = find_context_file(source.ident().path(), tsconfig()); + let tsconfig = match *tsconfig.await? { + FindContextFileResult::Found(path, _) => Some( + read_tsconfigs( + path.read(), + SourceAssetVc::new(path).into(), + node_cjs_resolve_options(path.root()), + ) + .await?, + ), + FindContextFileResult::NotFound(_) => None, + }; + Ok(match &*content.await? { AssetContent::File(file) => match &*file.await? { FileContent::NotFound => ParseResult::NotFound.cell(), @@ -151,6 +167,7 @@ pub async fn parse( source, ty, transforms, + tsconfig, ) .await { @@ -178,6 +195,7 @@ async fn parse_content( source: AssetVc, ty: EcmascriptModuleAssetType, transforms: &[EcmascriptInputTransform], + tsconfig: Option>, ) -> Result { let source_map: Arc = Default::default(); let handler = Handler::with_emitter( @@ -284,6 +302,7 @@ async fn parse_content( file_path_str: &fs_path.path, file_name_str: fs_path.file_name(), file_name_hash: file_path_hash, + tsconfig: &tsconfig, }; for transform in transforms.iter() { transform.apply(&mut parsed_program, &context).await?; diff --git a/crates/turbopack-ecmascript/src/transform/mod.rs b/crates/turbopack-ecmascript/src/transform/mod.rs index c741015761002..28b3fce964c86 100644 --- a/crates/turbopack-ecmascript/src/transform/mod.rs +++ b/crates/turbopack-ecmascript/src/transform/mod.rs @@ -15,6 +15,7 @@ use swc_core::{ preset_env::{self, Targets}, transforms::{ base::{feature::FeatureFlag, helpers::inject_helpers, resolver, Assumptions}, + proposal::decorators, react::react, }, visit::{FoldWith, VisitMutWith}, @@ -24,8 +25,10 @@ use turbo_tasks::{ primitives::{StringVc, StringsVc}, trace::TraceRawVcs, }; -use turbo_tasks_fs::{json::parse_json_with_source_context, FileSystemPathVc}; -use turbopack_core::environment::EnvironmentVc; +use turbo_tasks_fs::{ + json::parse_json_with_source_context, FileJsonContent, FileJsonContentVc, FileSystemPathVc, +}; +use turbopack_core::{asset::AssetVc, environment::EnvironmentVc}; use self::server_to_client_proxy::{create_proxy_module, is_client_module}; @@ -78,6 +81,9 @@ pub enum EcmascriptInputTransform { StyledComponents, StyledJsx, TypeScript, + // Apply ecma decorators transform. This is not part of Typescript transform, even though + // decorators can be ts-specific (legacy decorartors) since there's ecma decorators for js. + Decorators, } #[turbo_tasks::value(transparent, serialization = "auto_for_input")] @@ -102,6 +108,7 @@ pub struct TransformContext<'a> { pub file_path_str: &'a str, pub file_name_str: &'a str, pub file_name_hash: u128, + pub tsconfig: &'a Option>, } impl EcmascriptInputTransform { @@ -116,6 +123,7 @@ impl EcmascriptInputTransform { file_path_str, file_name_str, file_name_hash, + tsconfig, }: &TransformContext<'_>, ) -> Result<()> { match *self { @@ -198,9 +206,63 @@ impl EcmascriptInputTransform { FileName::Anon, )); } + EcmascriptInputTransform::Decorators => { + // TODO: Currently this only supports legacy decorators from tsconfig / jsconfig + // options. + if let Some(tsconfig) = tsconfig { + // Selectively picks up tsconfig.json values to construct + // swc transform's stripconfig. It doesn't account .swcrc config currently. + for (value, _) in tsconfig { + let value = &*value.await?; + if let FileJsonContent::Content(value) = value { + let legacy_decorators = value["compilerOptions"] + ["experimentalDecorators"] + .as_bool() + .unwrap_or(false); + + if legacy_decorators { + // TODO: `fn decorators` does not support visitMut yet + let p = + std::mem::replace(program, Program::Module(Module::dummy())); + *program = p.fold_with(&mut chain!( + decorators(decorators::Config { + legacy: true, + emit_metadata: true, + use_define_for_class_fields: value["compilerOptions"] + ["useDefineForClassFields"] + .as_bool() + .unwrap_or(false), + }), + inject_helpers(unresolved_mark), + )); + } + } + } + }; + } EcmascriptInputTransform::TypeScript => { - use swc_core::ecma::transforms::typescript::strip; - program.visit_mut_with(&mut strip(top_level_mark)); + use swc_core::ecma::transforms::typescript::{strip_with_config, Config}; + + let config = if let Some(tsconfig) = tsconfig { + let mut config = Config { + ..Default::default() + }; + + for (value, _) in tsconfig { + let value = &*value.await?; + if let FileJsonContent::Content(value) = value { + let use_define_for_class_fields = + &value["compilerOptions"]["useDefineForClassFields"]; + config.use_define_for_class_fields = + use_define_for_class_fields.as_bool().unwrap_or(false); + } + } + config + } else { + Default::default() + }; + + program.visit_mut_with(&mut strip_with_config(config, top_level_mark)); } EcmascriptInputTransform::ClientDirective(transition_name) => { let transition_name = &*transition_name.await?; diff --git a/crates/turbopack/src/module_options/mod.rs b/crates/turbopack/src/module_options/mod.rs index 012f734296570..9892476dad517 100644 --- a/crates/turbopack/src/module_options/mod.rs +++ b/crates/turbopack/src/module_options/mod.rs @@ -83,7 +83,10 @@ impl ModuleOptionsVc { } } } - let mut transforms = custom_ecmascript_app_transforms.clone(); + // We apply decorators _before_ any ts transforms, as some of decorator requires + // type information. + let mut transforms = vec![EcmascriptInputTransform::Decorators]; + 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