use anyhow::Result;
use turbo_rcstr::rcstr;
use turbo_tasks::{ResolvedVc, Vc};
use turbo_tasks_fs::{self, FileJsonContent, FileSystemPath};
use turbopack::module_options::{
DecoratorsKind, DecoratorsOptions, JsxTransformOptions, TypescriptTransformOptions,
};
use turbopack_browser::react_refresh::assert_can_resolve_react_refresh;
use turbopack_core::{
file_source::FileSource,
resolve::{FindContextFileResult, find_context_file, node::node_cjs_resolve_options},
source::Source,
};
use turbopack_ecmascript::typescript::resolve::{read_from_tsconfigs, read_tsconfigs, tsconfig};
use turbopack_resolve::resolve_options_context::ResolveOptionsContext;
use crate::{mode::NextMode, next_config::NextConfig};
async fn get_typescript_options(
project_path: FileSystemPath,
tsconfig_path: Option<FileSystemPath>,
) -> Result<Option<Vec<(Vc<FileJsonContent>, ResolvedVc<Box<dyn Source>>)>>> {
if let Some(tsconfig_path) = tsconfig_path {
let tsconfigs = read_tsconfigs(
tsconfig_path.read(),
ResolvedVc::upcast(FileSource::new(tsconfig_path.clone()).to_resolved().await?),
node_cjs_resolve_options(tsconfig_path.root().owned().await?),
)
.await
.ok();
Ok(tsconfigs)
} else {
let tsconfig = find_context_file(project_path, tsconfig(), false);
Ok(match tsconfig.await.ok().as_deref() {
Some(FindContextFileResult::Found(path, _)) => read_tsconfigs(
path.read(),
ResolvedVc::upcast(FileSource::new(path.clone()).to_resolved().await?),
node_cjs_resolve_options(path.root().owned().await?),
)
.await
.ok(),
Some(FindContextFileResult::NotFound(_)) | None => 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: FileSystemPath,
tsconfig_path: Option<FileSystemPath>,
) -> Result<Vc<TypescriptTransformOptions>> {
let tsconfig = get_typescript_options(project_path, tsconfig_path).await?;
let use_define_for_class_fields = if let Some(ref tsconfig) = tsconfig {
read_from_tsconfigs(tsconfig, |json, _| {
json.get("compilerOptions")
.and_then(|opts| opts.get("useDefineForClassFields"))
.and_then(|v| v.as_bool())
})
.await?
.unwrap_or(false)
} else {
false
};
let verbatim_module_syntax = if let Some(ref tsconfig) = tsconfig {
read_from_tsconfigs(tsconfig, |json, _| {
json.get("compilerOptions")
.and_then(|opts| opts.get("verbatimModuleSyntax"))
.and_then(|v| v.as_bool())
})
.await?
.unwrap_or(false)
} else {
false
};
let ts_transform_options = TypescriptTransformOptions {
use_define_for_class_fields,
verbatim_module_syntax,
};
Ok(ts_transform_options.cell())
}
/// Build the transform options for the decorators.
/// **TODO** Currently only typescript's legacy decorators are supported
#[turbo_tasks::function]
pub async fn get_decorators_transform_options(
project_path: FileSystemPath,
tsconfig_path: Option<FileSystemPath>,
) -> Result<Vc<DecoratorsOptions>> {
let tsconfig = get_typescript_options(project_path, tsconfig_path).await?;
let experimental_decorators = if let Some(ref tsconfig) = tsconfig {
read_from_tsconfigs(tsconfig, |json, _| {
json.get("compilerOptions")
.and_then(|opts| opts.get("experimentalDecorators"))
.and_then(|v| v.as_bool())
})
.await?
.unwrap_or(false)
} else {
false
};
let decorators_kind = if experimental_decorators {
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(ref tsconfig) = tsconfig {
read_from_tsconfigs(tsconfig, |json, _| {
json.get("compilerOptions")
.and_then(|opts| opts.get("emitDecoratorMetadata"))
.and_then(|v| v.as_bool())
})
.await?
.unwrap_or(false)
} else {
false
};
let use_define_for_class_fields = if let Some(ref tsconfig) = tsconfig {
read_from_tsconfigs(tsconfig, |json, _| {
json.get("compilerOptions")
.and_then(|opts| opts.get("useDefineForClassFields"))
.and_then(|v| v.as_bool())
})
.await?
.unwrap_or(false)
} else {
false
};
let decorators_transform_options = DecoratorsOptions {
decorators_kind: decorators_kind.clone(),
emit_decorators_metadata: if let Some(ref decorators_kind) = decorators_kind {
match decorators_kind {
DecoratorsKind::Legacy => emit_decorators_metadata,
// 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
DecoratorsKind::Ecma => false,
}
} else {
false
},
use_define_for_class_fields,
..Default::default()
};
Ok(decorators_transform_options.cell())
}
#[turbo_tasks::function]
pub async fn get_jsx_transform_options(
project_path: FileSystemPath,
mode: Vc<NextMode>,
resolve_options_context: Option<Vc<ResolveOptionsContext>>,
is_rsc_context: bool,
next_config: Vc<NextConfig>,
tsconfig_path: Option<FileSystemPath>,
) -> Result<Vc<JsxTransformOptions>> {
let tsconfig = get_typescript_options(project_path.clone(), tsconfig_path).await?;
let is_react_development = mode.await?.is_react_development();
let enable_react_refresh = if is_react_development {
if let Some(resolve_options_context) = resolve_options_context {
assert_can_resolve_react_refresh(project_path.clone(), resolve_options_context)
.await?
.is_found()
} else {
false
}
} else {
false
};
let is_emotion_enabled = next_config.compiler().await?.emotion.is_some();
// [NOTE]: ref: WEB-901
// next.js does not allow to overriding react runtime config via tsconfig /
// jsconfig, it forces overrides into automatic runtime instead.
// [TODO]: we need to emit / validate config message like next.js devserver does
let react_transform_options = JsxTransformOptions {
development: mode.await?.is_react_development(),
// https://github.com/vercel/next.js/blob/3dc2c1c7f8441cdee31da9f7e0986d654c7fd2e7/packages/next/src/build/swc/options.ts#L112
// This'll be ignored if ts|jsconfig explicitly specifies importSource
import_source: if is_emotion_enabled && !is_rsc_context {
Some(rcstr!("@emotion/react"))
} else {
None
},
runtime: Some(rcstr!("automatic")),
react_refresh: enable_react_refresh,
};
let react_transform_options = if let Some(tsconfig) = tsconfig {
read_from_tsconfigs(&tsconfig, |json, _| {
let jsx_import_source = json
.get("compilerOptions")
.and_then(|opts| opts.get("jsxImportSource"))
.and_then(|v| v.as_str())
.map(|s| s.into());
Some(JsxTransformOptions {
import_source: if jsx_import_source.is_some() {
jsx_import_source
} else {
react_transform_options.import_source.clone()
},
..react_transform_options.clone()
})
})
.await?
.unwrap_or_default()
} else {
react_transform_options
};
Ok(react_transform_options.cell())
}