From 036f540bb440b2a54cbaebc09c31afaca27e99b8 Mon Sep 17 00:00:00 2001 From: OJ Kwon <1210596+kwonoj@users.noreply.github.com> Date: Mon, 3 Apr 2023 10:32:13 -0700 Subject: [PATCH] feat(turbopack): support transform options (#47365) ### What? This PR implements a feature for the turbpack to support (partial) transform configuration inherited from ts/jsconfigs, notably for the legacy decorators and jsx runtimes. ### Why? ### How? - Closes WEB-667 --- packages/next-swc/crates/next-core/src/lib.rs | 2 +- .../next-core/src/next_client/context.rs | 18 +- .../next-core/src/next_server/context.rs | 33 ++-- .../crates/next-core/src/transform_options.rs | 154 ++++++++++++++++++ .../crates/next-core/src/typescript.rs | 48 ------ 5 files changed, 181 insertions(+), 74 deletions(-) create mode 100644 packages/next-swc/crates/next-core/src/transform_options.rs delete mode 100644 packages/next-swc/crates/next-core/src/typescript.rs diff --git a/packages/next-swc/crates/next-core/src/lib.rs b/packages/next-swc/crates/next-core/src/lib.rs index 2d790b27d6..a463f0c76f 100644 --- a/packages/next-swc/crates/next-core/src/lib.rs +++ b/packages/next-swc/crates/next-core/src/lib.rs @@ -29,7 +29,7 @@ pub mod react_refresh; pub mod router; pub mod router_source; mod runtime; -mod typescript; +mod transform_options; mod util; mod web_entry_source; diff --git a/packages/next-swc/crates/next-core/src/next_client/context.rs b/packages/next-swc/crates/next-core/src/next_client/context.rs index ddf3df39d1..c60e88ee87 100644 --- a/packages/next-swc/crates/next-core/src/next_client/context.rs +++ b/packages/next-swc/crates/next-core/src/next_client/context.rs @@ -24,7 +24,7 @@ use turbo_binding::{ turbopack::{ module_options::{ module_options_context::{ModuleOptionsContext, ModuleOptionsContextVc}, - JsxTransformOptions, PostCssTransformOptions, WebpackLoadersOptions, + PostCssTransformOptions, WebpackLoadersOptions, }, resolve_options_context::{ResolveOptionsContext, ResolveOptionsContextVc}, transition::TransitionsByNameVc, @@ -46,7 +46,10 @@ use crate::{ get_next_client_resolved_map, }, react_refresh::assert_can_resolve_react_refresh, - typescript::get_typescript_transform_options, + transform_options::{ + get_decorators_transform_options, get_jsx_transform_options, + get_typescript_transform_options, + }, util::foreign_code_context_condition, }; @@ -153,6 +156,8 @@ pub async fn get_client_module_options_context( .is_found(); let tsconfig = get_typescript_transform_options(project_path); + let decorators_options = get_decorators_transform_options(project_path); + let jsx_runtime_options = get_jsx_transform_options(project_path); let enable_webpack_loaders = { let options = &*next_config.webpack_loaders_options().await?; let loaders_options = WebpackLoadersOptions { @@ -179,13 +184,7 @@ pub async fn get_client_module_options_context( // We don't need to resolve React Refresh for each module. Instead, // we try resolve it once at the root and pass down a context to all // the modules. - enable_jsx: Some( - JsxTransformOptions { - import_source: None, - runtime: None, - } - .cell(), - ), + enable_jsx: Some(jsx_runtime_options), enable_emotion: true, enable_react_refresh, enable_styled_components: true, @@ -196,6 +195,7 @@ pub async fn get_client_module_options_context( }), enable_webpack_loaders, enable_typescript_transform: Some(tsconfig), + decorators: Some(decorators_options), rules: vec![( foreign_code_context_condition(next_config).await?, module_options_context.clone().cell(), diff --git a/packages/next-swc/crates/next-core/src/next_server/context.rs b/packages/next-swc/crates/next-core/src/next_server/context.rs index c277b93e95..0eb7bba03d 100644 --- a/packages/next-swc/crates/next-core/src/next_server/context.rs +++ b/packages/next-swc/crates/next-core/src/next_server/context.rs @@ -14,8 +14,8 @@ use turbo_binding::{ node::execution_context::ExecutionContextVc, turbopack::{ module_options::{ - JsxTransformOptions, JsxTransformOptionsVc, ModuleOptionsContext, - ModuleOptionsContextVc, PostCssTransformOptions, WebpackLoadersOptions, + ModuleOptionsContext, ModuleOptionsContextVc, PostCssTransformOptions, + WebpackLoadersOptions, }, resolve_options_context::{ResolveOptionsContext, ResolveOptionsContextVc}, }, @@ -31,7 +31,10 @@ use crate::{ next_build::{get_external_next_compiled_package_mapping, get_postcss_package_mapping}, next_config::NextConfigVc, next_import_map::get_next_server_import_map, - typescript::get_typescript_transform_options, + transform_options::{ + get_decorators_transform_options, get_jsx_transform_options, + get_typescript_transform_options, + }, util::foreign_code_context_condition, }; @@ -230,6 +233,8 @@ pub async fn get_server_module_options_context( }; let tsconfig = get_typescript_transform_options(project_path); + let decorators_options = get_decorators_transform_options(project_path); + let jsx_runtime_options = get_jsx_transform_options(project_path); let module_options_context = match ty.into_value() { ServerContextType::Pages { .. } | ServerContextType::PagesData { .. } => { @@ -238,11 +243,12 @@ pub async fn get_server_module_options_context( ..Default::default() }; ModuleOptionsContext { - enable_jsx: Some(get_jsx_transform_options()), + enable_jsx: Some(jsx_runtime_options), enable_styled_jsx: true, enable_postcss_transform, enable_webpack_loaders, enable_typescript_transform: Some(tsconfig), + decorators: Some(decorators_options), rules: vec![( foreign_code_context_condition, module_options_context.clone().cell(), @@ -257,11 +263,12 @@ pub async fn get_server_module_options_context( ..Default::default() }; ModuleOptionsContext { - enable_jsx: Some(get_jsx_transform_options()), + enable_jsx: Some(jsx_runtime_options), enable_styled_jsx: true, enable_postcss_transform, enable_webpack_loaders, enable_typescript_transform: Some(tsconfig), + decorators: Some(decorators_options), rules: vec![( foreign_code_context_condition, module_options_context.clone().cell(), @@ -279,10 +286,11 @@ pub async fn get_server_module_options_context( ..Default::default() }; ModuleOptionsContext { - enable_jsx: Some(get_jsx_transform_options()), + enable_jsx: Some(jsx_runtime_options), enable_postcss_transform, enable_webpack_loaders, enable_typescript_transform: Some(tsconfig), + decorators: Some(decorators_options), rules: vec![( foreign_code_context_condition, module_options_context.clone().cell(), @@ -300,6 +308,7 @@ pub async fn get_server_module_options_context( enable_postcss_transform, enable_webpack_loaders, enable_typescript_transform: Some(tsconfig), + decorators: Some(decorators_options), rules: vec![( foreign_code_context_condition, module_options_context.clone().cell(), @@ -314,11 +323,12 @@ pub async fn get_server_module_options_context( ..Default::default() }; ModuleOptionsContext { - enable_jsx: Some(get_jsx_transform_options()), + enable_jsx: Some(jsx_runtime_options), enable_styled_jsx: true, enable_postcss_transform, enable_webpack_loaders, enable_typescript_transform: Some(tsconfig), + decorators: Some(decorators_options), rules: vec![( foreign_code_context_condition, module_options_context.clone().cell(), @@ -333,15 +343,6 @@ pub async fn get_server_module_options_context( Ok(module_options_context) } -#[turbo_tasks::function] -pub fn get_jsx_transform_options() -> JsxTransformOptionsVc { - JsxTransformOptions { - import_source: None, - runtime: None, - } - .cell() -} - #[turbo_tasks::function] pub fn get_build_module_options_context() -> ModuleOptionsContextVc { ModuleOptionsContext { diff --git a/packages/next-swc/crates/next-core/src/transform_options.rs b/packages/next-swc/crates/next-core/src/transform_options.rs new file mode 100644 index 0000000000..335112cecc --- /dev/null +++ b/packages/next-swc/crates/next-core/src/transform_options.rs @@ -0,0 +1,154 @@ +use anyhow::Result; +use turbo_binding::turbopack::{ + core::{ + asset::AssetVc, + resolve::{find_context_file, node::node_cjs_resolve_options, FindContextFileResult}, + source_asset::SourceAssetVc, + }, + ecmascript::typescript::resolve::{read_from_tsconfigs, read_tsconfigs, tsconfig}, + turbopack::module_options::{ + DecoratorsKind, DecoratorsOptions, DecoratorsOptionsVc, JsxTransformOptions, + JsxTransformOptionsVc, TypescriptTransformOptions, TypescriptTransformOptionsVc, + }, +}; +use turbo_tasks_fs::{FileJsonContentVc, FileSystemPathVc}; + +async fn get_typescript_options( + project_path: FileSystemPathVc, +) -> Option> { + let tsconfig = find_context_file(project_path, tsconfig()); + match *tsconfig.await.ok()? { + FindContextFileResult::Found(path, _) => Some( + read_tsconfigs( + path.read(), + SourceAssetVc::new(path).into(), + node_cjs_resolve_options(path.root()), + ) + .await + .ok()?, + ), + FindContextFileResult::NotFound(_) => None, + } +} + +/// Build the transform options for specifically for the typescript's runtime +/// outputs +#[turbo_tasks::function] +pub async fn get_typescript_transform_options( + project_path: FileSystemPathVc, +) -> Result { + let tsconfig = get_typescript_options(project_path).await; + + let use_define_for_class_fields = if let Some(tsconfig) = tsconfig { + read_from_tsconfigs(&tsconfig, |json, _| { + json["compilerOptions"]["useDefineForClassFields"].as_bool() + }) + .await? + .unwrap_or(false) + } else { + false + }; + + let ts_transform_options = TypescriptTransformOptions { + use_define_for_class_fields, + }; + + Ok(ts_transform_options.cell()) +} + +/// Build the transform options for the decorators. +/// [TODO]: Currnently only typescript's legacy decorators are supported +#[turbo_tasks::function] +pub async fn get_decorators_transform_options( + project_path: FileSystemPathVc, +) -> Result { + let tsconfig = get_typescript_options(project_path).await; + + let decorators_transform_options = if let Some(tsconfig) = tsconfig { + read_from_tsconfigs(&tsconfig, |json, _| { + let decorators_kind = if json["compilerOptions"]["experimentalDecorators"] + .as_bool() + .unwrap_or(false) + { + Some(DecoratorsKind::Legacy) + } else { + // ref: https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-rc/#differences-with-experimental-legacy-decorators + // `without the flag, decorators will now be valid syntax for all new code. + // Outside of --experimentalDecorators, they will be type-checked and emitted + // differently with ts 5.0, new ecma decorators will be enabled + // if legacy decorators are not enabled + Some(DecoratorsKind::Ecma) + }; + + let emit_decorators_metadata = if let Some(decorators_kind) = &decorators_kind { + match decorators_kind { + DecoratorsKind::Legacy => { + // ref: This new decorators proposal is not compatible with + // --emitDecoratorMetadata, and it does not allow decorating parameters. + // Future ECMAScript proposals may be able to help bridge that gap + json["compilerOptions"]["emitDecoratorMetadata"] + .as_bool() + .unwrap_or(false) + } + DecoratorsKind::Ecma => false, + } + } else { + false + }; + + Some(DecoratorsOptions { + decorators_kind, + emit_decorators_metadata, + use_define_for_class_fields: json["compilerOptions"]["useDefineForClassFields"] + .as_bool() + .unwrap_or(false), + ..Default::default() + }) + }) + .await? + .unwrap_or_default() + } else { + Default::default() + }; + + Ok(decorators_transform_options.cell()) +} + +#[turbo_tasks::function] +pub async fn get_jsx_transform_options( + project_path: FileSystemPathVc, +) -> Result { + let tsconfig = get_typescript_options(project_path).await; + + let react_transform_options = if let Some(tsconfig) = tsconfig { + read_from_tsconfigs(&tsconfig, |json, _| { + let jsx_import_source = json["compilerOptions"]["jsxImportSource"] + .as_str() + .map(|s| s.to_string()); + + // interop between tsconfig's jsx to swc's jsx runtime configuration. Swc's jsx + // runtime is a subset of tsconfig's jsx. + let runtime = if let Some(jsx_runtime) = json["compilerOptions"]["jsx"].as_str() { + match jsx_runtime { + "react" => Some("classic".to_string()), + "react-jsx" => Some("automatic".to_string()), + "react-jsxdev" => Some("automatic".to_string()), + _ => None, + } + } else { + None + }; + + Some(JsxTransformOptions { + import_source: jsx_import_source, + runtime, + }) + }) + .await? + .unwrap_or_default() + } else { + Default::default() + }; + + Ok(react_transform_options.cell()) +} diff --git a/packages/next-swc/crates/next-core/src/typescript.rs b/packages/next-swc/crates/next-core/src/typescript.rs deleted file mode 100644 index e90bc22e5e..0000000000 --- a/packages/next-swc/crates/next-core/src/typescript.rs +++ /dev/null @@ -1,48 +0,0 @@ -use anyhow::Result; -use turbo_binding::{ - turbo::tasks_fs::FileSystemPathVc, - turbopack::{ - core::{ - resolve::{find_context_file, node::node_cjs_resolve_options, FindContextFileResult}, - source_asset::SourceAssetVc, - }, - ecmascript::typescript::resolve::{read_from_tsconfigs, read_tsconfigs, tsconfig}, - turbopack::module_options::{TypescriptTransformOptions, TypescriptTransformOptionsVc}, - }, -}; - -// Get the transform options for specifically for the typescript's runtime -// outputs -#[turbo_tasks::function] -pub async fn get_typescript_transform_options( - project_path: FileSystemPathVc, -) -> Result { - let tsconfig = find_context_file(project_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, - }; - - let use_define_for_class_fields = if let Some(tsconfig) = tsconfig { - read_from_tsconfigs(&tsconfig, |json, _| { - json["compilerOptions"]["useDefineForClassFields"].as_bool() - }) - .await? - .unwrap_or(false) - } else { - false - }; - - let ts_transform_options = TypescriptTransformOptions { - use_define_for_class_fields, - }; - - Ok(ts_transform_options.cell()) -}