turbopack_ecmascript/transform/
mod.rs1use std::{fmt::Debug, hash::Hash, sync::Arc};
2
3use anyhow::Result;
4use async_trait::async_trait;
5use rustc_hash::FxHashMap;
6use swc_core::{
7 atoms::{Atom, atom},
8 base::SwcComments,
9 common::{Mark, SourceMap, comments::Comments, util::take::Take},
10 ecma::{
11 ast::{Module, ModuleItem, Program, Script},
12 preset_env::{self, Targets},
13 transforms::{
14 base::{assumptions::Assumptions, helpers::inject_helpers},
15 optimization::inline_globals,
16 react::react,
17 },
18 },
19 quote,
20};
21use turbo_rcstr::RcStr;
22use turbo_tasks::{ResolvedVc, Vc};
23use turbo_tasks_fs::FileSystemPath;
24use turbopack_core::{
25 environment::Environment,
26 issue::{Issue, IssueSeverity, IssueStage, StyledString},
27};
28
29#[turbo_tasks::value(serialization = "auto_for_input")]
30#[derive(Debug, Clone, Hash)]
31pub enum EcmascriptInputTransform {
32 Plugin(ResolvedVc<TransformPlugin>),
33 PresetEnv(ResolvedVc<Environment>),
34 React {
35 #[serde(default)]
36 development: bool,
37 #[serde(default)]
38 refresh: bool,
39 import_source: ResolvedVc<Option<RcStr>>,
41 runtime: ResolvedVc<Option<RcStr>>,
43 },
44 GlobalTypeofs {
45 window_value: String,
46 },
47 TypeScript {
50 #[serde(default)]
51 use_define_for_class_fields: bool,
52 },
53 Decorators {
54 #[serde(default)]
55 is_legacy: bool,
56 #[serde(default)]
57 is_ecma: bool,
58 #[serde(default)]
59 emit_decorators_metadata: bool,
60 #[serde(default)]
61 use_define_for_class_fields: bool,
62 },
63}
64
65#[async_trait]
68pub trait CustomTransformer: Debug {
69 async fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()>;
70}
71
72#[turbo_tasks::value(
75 transparent,
76 serialization = "none",
77 eq = "manual",
78 into = "new",
79 cell = "new"
80)]
81#[derive(Debug)]
82pub struct TransformPlugin(#[turbo_tasks(trace_ignore)] Box<dyn CustomTransformer + Send + Sync>);
83
84#[async_trait]
85impl CustomTransformer for TransformPlugin {
86 async fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()> {
87 self.0.transform(program, ctx).await
88 }
89}
90
91#[turbo_tasks::value(transparent, serialization = "auto_for_input")]
92#[derive(Debug, Clone, Hash)]
93pub struct EcmascriptInputTransforms(Vec<EcmascriptInputTransform>);
94
95#[turbo_tasks::value_impl]
96impl EcmascriptInputTransforms {
97 #[turbo_tasks::function]
98 pub fn empty() -> Vc<Self> {
99 Vc::cell(Vec::new())
100 }
101
102 #[turbo_tasks::function]
103 pub async fn extend(self: Vc<Self>, other: Vc<EcmascriptInputTransforms>) -> Result<Vc<Self>> {
104 let mut transforms = self.owned().await?;
105 transforms.extend(other.owned().await?);
106 Ok(Vc::cell(transforms))
107 }
108}
109
110pub struct TransformContext<'a> {
111 pub comments: &'a SwcComments,
112 pub top_level_mark: Mark,
113 pub unresolved_mark: Mark,
114 pub source_map: &'a Arc<SourceMap>,
115 pub file_path_str: &'a str,
116 pub file_name_str: &'a str,
117 pub file_name_hash: u128,
118 pub query_str: RcStr,
119 pub file_path: ResolvedVc<FileSystemPath>,
120}
121
122impl EcmascriptInputTransform {
123 pub async fn apply(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()> {
124 let &TransformContext {
125 comments,
126 source_map,
127 top_level_mark,
128 unresolved_mark,
129 ..
130 } = ctx;
131 match self {
132 EcmascriptInputTransform::GlobalTypeofs { window_value } => {
133 let mut typeofs: FxHashMap<Atom, Atom> = Default::default();
134 typeofs.insert(Atom::from("window"), Atom::from(&**window_value));
135
136 program.mutate(inline_globals(
137 unresolved_mark,
138 Default::default(),
139 Default::default(),
140 Default::default(),
141 Arc::new(typeofs),
142 ));
143 }
144 EcmascriptInputTransform::React {
145 development,
146 refresh,
147 import_source,
148 runtime,
149 } => {
150 use swc_core::ecma::transforms::react::{Options, Runtime};
151 let runtime = if let Some(runtime) = &*runtime.await? {
152 match runtime.as_str() {
153 "classic" => Runtime::Classic,
154 "automatic" => Runtime::Automatic,
155 _ => {
156 return Err(anyhow::anyhow!(
157 "Invalid value for swc.jsc.transform.react.runtime: {}",
158 runtime
159 ));
160 }
161 }
162 } else {
163 Runtime::Automatic
164 };
165
166 let config = Options {
167 runtime: Some(runtime),
168 development: Some(*development),
169 import_source: import_source.await?.as_deref().map(Atom::from),
170 refresh: if *refresh {
171 Some(swc_core::ecma::transforms::react::RefreshOptions {
172 refresh_reg: atom!("__turbopack_context__.k.register"),
174 refresh_sig: atom!("__turbopack_context__.k.signature"),
175 ..Default::default()
176 })
177 } else {
178 None
179 },
180 ..Default::default()
181 };
182
183 program.mutate(react::<&dyn Comments>(
186 source_map.clone(),
187 Some(&comments),
188 config,
189 top_level_mark,
190 unresolved_mark,
191 ));
192
193 if *refresh {
194 let stmt = quote!(
195 "\nif (typeof globalThis.$RefreshHelpers$ === 'object' && \
197 globalThis.$RefreshHelpers !== null) { \
198 __turbopack_context__.k.registerExports(module, \
199 globalThis.$RefreshHelpers$); }\n" as Stmt
200 );
201
202 match program {
203 Program::Module(module) => {
204 module.body.push(ModuleItem::Stmt(stmt));
205 }
206 Program::Script(script) => {
207 script.body.push(stmt);
208 }
209 }
210 }
211 }
212 EcmascriptInputTransform::PresetEnv(env) => {
213 let versions = env.runtime_versions().await?;
214 let config = swc_core::ecma::preset_env::EnvConfig::from(
215 swc_core::ecma::preset_env::Config {
216 targets: Some(Targets::Versions(*versions)),
217 mode: None, ..Default::default()
219 },
220 );
221
222 let module_program = std::mem::replace(program, Program::Module(Module::dummy()));
223
224 let module_program = if let Program::Script(Script {
225 span,
226 mut body,
227 shebang,
228 }) = module_program
229 {
230 Program::Module(Module {
231 span,
232 body: body.drain(..).map(ModuleItem::Stmt).collect(),
233 shebang,
234 })
235 } else {
236 module_program
237 };
238
239 *program = module_program.apply((
242 preset_env::transform_from_env::<&'_ dyn Comments>(
243 top_level_mark,
244 Some(&comments),
245 config,
246 Assumptions::default(),
247 ),
248 inject_helpers(unresolved_mark),
249 ));
250 }
251 EcmascriptInputTransform::TypeScript {
252 use_define_for_class_fields: _use_define_for_class_fields,
254 } => {
255 use swc_core::ecma::transforms::typescript::typescript;
256 let config = Default::default();
257 program.mutate(typescript(config, unresolved_mark, top_level_mark));
258 }
259 EcmascriptInputTransform::Decorators {
260 is_legacy,
261 is_ecma: _,
262 emit_decorators_metadata,
263 use_define_for_class_fields: _use_define_for_class_fields,
265 } => {
266 use swc_core::ecma::transforms::proposal::decorators::{Config, decorators};
267 let config = Config {
268 legacy: *is_legacy,
269 emit_metadata: *emit_decorators_metadata,
270 ..Default::default()
271 };
272
273 program.mutate((decorators(config), inject_helpers(unresolved_mark)));
274 }
275 EcmascriptInputTransform::Plugin(transform) => {
276 transform.await?.transform(program, ctx).await?
277 }
278 }
279 Ok(())
280 }
281}
282
283pub fn remove_shebang(program: &mut Program) {
284 match program {
285 Program::Module(m) => {
286 m.shebang = None;
287 }
288 Program::Script(s) => {
289 s.shebang = None;
290 }
291 }
292}
293
294#[turbo_tasks::value(shared)]
295pub struct UnsupportedServerActionIssue {
296 pub file_path: ResolvedVc<FileSystemPath>,
297}
298
299#[turbo_tasks::value_impl]
300impl Issue for UnsupportedServerActionIssue {
301 #[turbo_tasks::function]
302 fn severity(&self) -> Vc<IssueSeverity> {
303 IssueSeverity::Error.into()
304 }
305
306 #[turbo_tasks::function]
307 fn title(&self) -> Vc<StyledString> {
308 StyledString::Text(
309 "Server actions (\"use server\") are not yet supported in Turbopack".into(),
310 )
311 .cell()
312 }
313
314 #[turbo_tasks::function]
315 fn file_path(&self) -> Vc<FileSystemPath> {
316 *self.file_path
317 }
318
319 #[turbo_tasks::function]
320 fn stage(&self) -> Vc<IssueStage> {
321 IssueStage::Transform.cell()
322 }
323}