turbopack_ecmascript_plugins/transform/
swc_ecma_transform_plugins.rs1use anyhow::Result;
2use async_trait::async_trait;
3use swc_core::ecma::ast::Program;
4use turbo_rcstr::{RcStr, rcstr};
5use turbo_tasks::Vc;
6use turbo_tasks_fs::FileSystemPath;
7use turbopack_core::issue::{Issue, IssueSeverity, IssueStage, OptionStyledString, StyledString};
8use turbopack_ecmascript::{CustomTransformer, TransformContext};
9
10#[turbo_tasks::value(serialization = "none", eq = "manual", cell = "new", shared)]
17pub struct SwcPluginModule {
18 pub name: RcStr,
19 #[turbo_tasks(trace_ignore, debug_ignore)]
20 #[cfg(feature = "swc_ecma_transform_plugin")]
21 pub plugin: swc_core::plugin_runner::plugin_module_bytes::CompiledPluginModuleBytes,
22}
23
24impl SwcPluginModule {
25 pub fn new(plugin_name: RcStr, plugin_bytes: Vec<u8>) -> Self {
26 #[cfg(feature = "swc_ecma_transform_plugin")]
27 {
28 use swc_core::plugin_runner::plugin_module_bytes::{
29 CompiledPluginModuleBytes, RawPluginModuleBytes,
30 };
31 use swc_plugin_backend_wasmer::WasmerRuntime;
32
33 Self {
34 plugin: CompiledPluginModuleBytes::from_raw_module(
35 &WasmerRuntime,
36 RawPluginModuleBytes::new(plugin_name.to_string(), plugin_bytes),
37 ),
38 name: plugin_name,
39 }
40 }
41
42 #[cfg(not(feature = "swc_ecma_transform_plugin"))]
43 {
44 let _ = plugin_bytes;
45 Self { name: plugin_name }
46 }
47 }
48}
49
50#[turbo_tasks::value(shared)]
51struct UnsupportedSwcEcmaTransformPluginsIssue {
52 pub file_path: FileSystemPath,
53}
54
55#[turbo_tasks::value_impl]
56impl Issue for UnsupportedSwcEcmaTransformPluginsIssue {
57 fn severity(&self) -> IssueSeverity {
58 IssueSeverity::Warning
59 }
60
61 #[turbo_tasks::function]
62 fn stage(&self) -> Vc<IssueStage> {
63 IssueStage::Transform.cell()
64 }
65
66 #[turbo_tasks::function]
67 fn title(&self) -> Vc<StyledString> {
68 StyledString::Text(rcstr!(
69 "Unsupported SWC EcmaScript transform plugins on this platform."
70 ))
71 .cell()
72 }
73
74 #[turbo_tasks::function]
75 fn file_path(&self) -> Vc<FileSystemPath> {
76 self.file_path.clone().cell()
77 }
78
79 #[turbo_tasks::function]
80 fn description(&self) -> Vc<OptionStyledString> {
81 Vc::cell(Some(
82 StyledString::Text(rcstr!(
83 "Turbopack does not yet support running SWC EcmaScript transform plugins on this \
84 platform."
85 ))
86 .resolved_cell(),
87 ))
88 }
89}
90
91#[turbo_tasks::value(shared)]
92struct SwcEcmaTransformFailureIssue {
93 pub file_path: FileSystemPath,
94 pub description: StyledString,
95}
96
97#[turbo_tasks::value_impl]
98impl Issue for SwcEcmaTransformFailureIssue {
99 fn severity(&self) -> IssueSeverity {
100 IssueSeverity::Error
101 }
102
103 #[turbo_tasks::function]
104 fn stage(&self) -> Vc<IssueStage> {
105 IssueStage::Transform.cell()
106 }
107
108 #[turbo_tasks::function]
109 fn title(&self) -> Vc<StyledString> {
110 StyledString::Text(rcstr!("Failed to execute SWC plugin")).cell()
111 }
112
113 #[turbo_tasks::function]
114 fn file_path(&self) -> Vc<FileSystemPath> {
115 self.file_path.clone().cell()
116 }
117
118 #[turbo_tasks::function]
119 fn description(&self) -> Vc<OptionStyledString> {
120 Vc::cell(Some(
121 StyledString::Stack(vec![
122 StyledString::Text(rcstr!(
123 "An unexpected error occurred when executing an SWC EcmaScript transform \
124 plugin."
125 )),
126 StyledString::Text(rcstr!(
127 "This might be due to a version mismatch between the plugin and Next.js. \
128 https://plugins.swc.rs/ can help you find the correct plugin version to use."
129 )),
130 StyledString::Text(Default::default()),
131 self.description.clone(),
132 ])
133 .resolved_cell(),
134 ))
135 }
136}
137
138#[derive(Debug)]
140pub struct SwcEcmaTransformPluginsTransformer {
141 #[cfg(feature = "swc_ecma_transform_plugin")]
142 plugins: Vec<(turbo_tasks::ResolvedVc<SwcPluginModule>, serde_json::Value)>,
143}
144
145impl SwcEcmaTransformPluginsTransformer {
146 #[cfg(feature = "swc_ecma_transform_plugin")]
147 pub fn new(
148 plugins: Vec<(turbo_tasks::ResolvedVc<SwcPluginModule>, serde_json::Value)>,
149 ) -> Self {
150 Self { plugins }
151 }
152
153 #[cfg(not(feature = "swc_ecma_transform_plugin"))]
156 #[allow(clippy::new_without_default)]
157 pub fn new() -> Self {
158 Self {}
159 }
160}
161
162#[async_trait]
163impl CustomTransformer for SwcEcmaTransformPluginsTransformer {
164 #[cfg_attr(not(feature = "swc_ecma_transform_plugin"), allow(unused))]
165 #[tracing::instrument(level = tracing::Level::TRACE, name = "swc_ecma_transform_plugin", skip_all)]
166 async fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()> {
167 #[cfg(feature = "swc_ecma_transform_plugin")]
168 {
169 use std::{cell::RefCell, rc::Rc, sync::Arc};
170
171 use anyhow::Context;
172 use swc_core::{
173 common::{
174 comments::SingleThreadedComments,
175 plugin::{
176 metadata::TransformPluginMetadataContext, serialized::PluginSerializedBytes,
177 },
178 util::take::Take,
179 },
180 ecma::ast::Module,
181 plugin::proxies::{COMMENTS, HostCommentsStorage},
182 plugin_runner::plugin_module_bytes::CompiledPluginModuleBytes,
183 };
184 use swc_plugin_backend_wasmer::WasmerRuntime;
185 use turbo_tasks::TryJoinIterExt;
186
187 let plugins = self
188 .plugins
189 .iter()
190 .map(async |(plugin_module, config)| {
191 let plugin_module = plugin_module.await?;
192 Ok((
193 plugin_module.name.clone(),
194 config.clone(),
195 Box::new(plugin_module.plugin.clone_module(&WasmerRuntime)),
196 ))
197 })
198 .try_join()
199 .await?;
200
201 let should_enable_comments_proxy =
202 !ctx.comments.leading.is_empty() && !ctx.comments.trailing.is_empty();
203
204 let comments = if should_enable_comments_proxy {
208 let mut leading =
211 swc_core::common::comments::SingleThreadedCommentsMapInner::default();
212 ctx.comments.leading.as_ref().into_iter().for_each(|c| {
213 leading.insert(*c.key(), c.value().clone());
214 });
215
216 let mut trailing =
217 swc_core::common::comments::SingleThreadedCommentsMapInner::default();
218 ctx.comments.trailing.as_ref().into_iter().for_each(|c| {
219 trailing.insert(*c.key(), c.value().clone());
220 });
221
222 Some(SingleThreadedComments::from_leading_and_trailing(
223 Rc::new(RefCell::new(leading)),
224 Rc::new(RefCell::new(trailing)),
225 ))
226 } else {
227 None
228 };
229
230 fn transform(
231 original_serialized_program: &PluginSerializedBytes,
232 ctx: &TransformContext<'_>,
233 plugins: Vec<(RcStr, serde_json::Value, Box<CompiledPluginModuleBytes>)>,
234 should_enable_comments_proxy: bool,
235 ) -> Result<Program> {
236 use either::Either;
237
238 let transform_metadata_context = Arc::new(TransformPluginMetadataContext::new(
239 Some(ctx.file_path_str.to_string()),
240 "development".to_string(),
242 None,
243 ));
244
245 let mut serialized_program = Either::Left(original_serialized_program);
246
247 for (plugin_name, plugin_config, plugin_module) in plugins {
254 let mut transform_plugin_executor =
255 swc_core::plugin_runner::create_plugin_transform_executor(
256 ctx.source_map,
257 &ctx.unresolved_mark,
258 &transform_metadata_context,
259 None,
260 plugin_module,
261 Some(plugin_config),
262 Arc::new(WasmerRuntime),
263 );
264
265 serialized_program = Either::Right(
266 transform_plugin_executor
267 .transform(
268 serialized_program.as_ref().either(|p| *p, |p| p),
269 Some(should_enable_comments_proxy),
270 )
271 .with_context(|| format!("Failed to execute {plugin_name}"))?,
272 );
273 }
274
275 serialized_program
276 .as_ref()
277 .either(|p| *p, |p| p)
278 .deserialize()
279 .map(|v| v.into_inner())
280 }
281
282 let transformed_program =
283 COMMENTS.set(&HostCommentsStorage { inner: comments }, || {
284 let module_program =
285 std::mem::replace(program, Program::Module(Module::dummy()));
286 let module_program =
287 swc_core::common::plugin::serialized::VersionedSerializable::new(
288 module_program,
289 );
290 let serialized_program = PluginSerializedBytes::try_serialize(&module_program)?;
291
292 match transform(
293 &serialized_program,
294 ctx,
295 plugins,
296 should_enable_comments_proxy,
297 ) {
298 Ok(program) => anyhow::Ok(program),
299 Err(e) => {
300 use turbopack_core::issue::IssueExt;
301
302 SwcEcmaTransformFailureIssue {
303 file_path: ctx.file_path.clone(),
304 description: StyledString::Text(format!("{:?}", e).into()),
305 }
306 .resolved_cell()
307 .emit();
308
309 Ok(module_program.into_inner())
311 }
312 }
313 })?;
314
315 *program = transformed_program;
316 }
317
318 #[cfg(not(feature = "swc_ecma_transform_plugin"))]
319 {
320 use turbopack_core::issue::IssueExt;
321
322 UnsupportedSwcEcmaTransformPluginsIssue {
323 file_path: ctx.file_path.clone(),
324 }
325 .resolved_cell()
326 .emit();
327 }
328
329 Ok(())
330 }
331}