feat(turbopack): support transform options (#47365)

<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors



-->

### 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
This commit is contained in:
OJ Kwon 2023-04-03 10:32:13 -07:00 committed by GitHub
parent aeec6b5d0f
commit 036f540bb4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 181 additions and 74 deletions

View file

@ -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;

View file

@ -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(),

View file

@ -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 {

View file

@ -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<Vec<(FileJsonContentVc, AssetVc)>> {
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<TypescriptTransformOptionsVc> {
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<DecoratorsOptionsVc> {
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<JsxTransformOptionsVc> {
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())
}

View file

@ -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<TypescriptTransformOptionsVc> {
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())
}