turbopack_ecmascript_plugins/transform/
swc_ecma_transform_plugins.rs1use anyhow::Result;
2use async_trait::async_trait;
3use swc_core::{
4 ecma::ast::Program,
5 plugin_runner::plugin_module_bytes::{CompiledPluginModuleBytes, RawPluginModuleBytes},
6};
7use swc_plugin_backend_wasmtime::WasmtimeRuntime;
8use turbo_rcstr::{RcStr, rcstr};
9use turbo_tasks_fs::FileSystemPath;
10use turbopack_core::issue::{Issue, IssueSeverity, IssueStage, StyledString};
11use turbopack_ecmascript::{CustomTransformer, TransformContext};
12
13#[turbo_tasks::value(
23 serialization = "skip",
24 evict = "last",
25 eq = "manual",
26 cell = "new",
27 shared
28)]
29pub struct SwcPluginModule {
30 pub name: RcStr,
31 #[turbo_tasks(trace_ignore, debug_ignore)]
32 pub plugin: swc_core::plugin_runner::plugin_module_bytes::CompiledPluginModuleBytes,
33}
34
35impl SwcPluginModule {
36 pub fn new(plugin_name: RcStr, plugin_bytes: Vec<u8>) -> Self {
37 Self {
38 plugin: CompiledPluginModuleBytes::from_raw_module(
39 &WasmtimeRuntime,
40 RawPluginModuleBytes::new(plugin_name.to_string(), plugin_bytes),
41 ),
42 name: plugin_name,
43 }
44 }
45}
46
47#[turbo_tasks::value(shared)]
48struct SwcEcmaTransformFailureIssue {
49 pub file_path: FileSystemPath,
50 pub description: StyledString,
51}
52
53#[async_trait]
54#[turbo_tasks::value_impl]
55impl Issue for SwcEcmaTransformFailureIssue {
56 fn severity(&self) -> IssueSeverity {
57 IssueSeverity::Error
58 }
59
60 fn stage(&self) -> IssueStage {
61 IssueStage::Transform
62 }
63
64 async fn title(&self) -> Result<StyledString> {
65 Ok(StyledString::Text(rcstr!("Failed to execute SWC plugin")))
66 }
67
68 async fn file_path(&self) -> Result<FileSystemPath> {
69 Ok(self.file_path.clone())
70 }
71
72 async fn description(&self) -> Result<Option<StyledString>> {
73 Ok(Some(StyledString::Stack(vec![
74 StyledString::Text(rcstr!(
75 "An unexpected error occurred when executing an SWC EcmaScript transform plugin."
76 )),
77 StyledString::Text(rcstr!(
78 "This might be due to a version mismatch between the plugin and Next.js. \
79 https://plugins.swc.rs/ can help you find the correct plugin version to use."
80 )),
81 StyledString::Text(Default::default()),
82 self.description.clone(),
83 ])))
84 }
85}
86
87#[derive(Debug)]
89pub struct SwcEcmaTransformPluginsTransformer {
90 plugins: Vec<(turbo_tasks::ResolvedVc<SwcPluginModule>, serde_json::Value)>,
91}
92
93impl SwcEcmaTransformPluginsTransformer {
94 pub fn new(
95 plugins: Vec<(turbo_tasks::ResolvedVc<SwcPluginModule>, serde_json::Value)>,
96 ) -> Self {
97 Self { plugins }
98 }
99}
100
101#[async_trait]
102impl CustomTransformer for SwcEcmaTransformPluginsTransformer {
103 #[tracing::instrument(level = tracing::Level::TRACE, name = "swc_ecma_transform_plugin", skip_all)]
104 async fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()> {
105 use std::{cell::RefCell, rc::Rc, sync::Arc};
106
107 use anyhow::Context;
108 use swc_core::{
109 common::{
110 comments::SingleThreadedComments,
111 plugin::{
112 metadata::TransformPluginMetadataContext, serialized::PluginSerializedBytes,
113 },
114 util::take::Take,
115 },
116 ecma::ast::Module,
117 plugin::proxies::{COMMENTS, HostCommentsStorage},
118 plugin_runner::plugin_module_bytes::CompiledPluginModuleBytes,
119 };
120 use swc_plugin_backend_wasmtime::WasmtimeRuntime;
121 use turbo_tasks::TryJoinIterExt;
122
123 let plugins = self
124 .plugins
125 .iter()
126 .map(async |(plugin_module, config)| {
127 let plugin_module = plugin_module.await?;
128 Ok((
129 plugin_module.name.clone(),
130 config.clone(),
131 Box::new(plugin_module.plugin.clone_module(&WasmtimeRuntime)),
132 ))
133 })
134 .try_join()
135 .await?;
136
137 let should_enable_comments_proxy =
138 !ctx.comments.leading.is_empty() && !ctx.comments.trailing.is_empty();
139
140 let comments = if should_enable_comments_proxy {
144 let mut leading = swc_core::common::comments::SingleThreadedCommentsMapInner::default();
147 ctx.comments.leading.as_ref().into_iter().for_each(|c| {
148 leading.insert(*c.key(), c.value().clone());
149 });
150
151 let mut trailing =
152 swc_core::common::comments::SingleThreadedCommentsMapInner::default();
153 ctx.comments.trailing.as_ref().into_iter().for_each(|c| {
154 trailing.insert(*c.key(), c.value().clone());
155 });
156
157 Some(SingleThreadedComments::from_leading_and_trailing(
158 Rc::new(RefCell::new(leading)),
159 Rc::new(RefCell::new(trailing)),
160 ))
161 } else {
162 None
163 };
164
165 fn transform(
166 original_serialized_program: &PluginSerializedBytes,
167 ctx: &TransformContext<'_>,
168 plugins: Vec<(RcStr, serde_json::Value, Box<CompiledPluginModuleBytes>)>,
169 should_enable_comments_proxy: bool,
170 ) -> Result<Program> {
171 use either::Either;
172
173 let transform_metadata_context = Arc::new(TransformPluginMetadataContext::new(
174 Some(ctx.file_path_str.to_string()),
175 ctx.node_env.to_string(),
176 None,
177 ));
178
179 let mut serialized_program = Either::Left(original_serialized_program);
180
181 for (plugin_name, plugin_config, plugin_module) in plugins {
188 let mut transform_plugin_executor =
189 swc_core::plugin_runner::create_plugin_transform_executor(
190 ctx.source_map,
191 &ctx.unresolved_mark,
192 &transform_metadata_context,
193 None,
194 plugin_module,
195 Some(plugin_config),
196 Arc::new(WasmtimeRuntime),
197 );
198
199 serialized_program = Either::Right(
200 transform_plugin_executor
201 .transform(
202 serialized_program.as_ref().either(|p| *p, |p| p),
203 Some(should_enable_comments_proxy),
204 )
205 .with_context(|| format!("Failed to execute {plugin_name}"))?,
206 );
207 }
208
209 serialized_program
210 .as_ref()
211 .either(|p| *p, |p| p)
212 .deserialize()
213 .map(|v| v.into_inner())
214 }
215
216 let transformed_program = COMMENTS.set(&HostCommentsStorage { inner: comments }, || {
217 let module_program = std::mem::replace(program, Program::Module(Module::dummy()));
218 let module_program =
219 swc_core::common::plugin::serialized::VersionedSerializable::new(module_program);
220 let serialized_program = PluginSerializedBytes::try_serialize(&module_program)?;
221
222 match transform(
223 &serialized_program,
224 ctx,
225 plugins,
226 should_enable_comments_proxy,
227 ) {
228 Ok(program) => anyhow::Ok(program),
229 Err(e) => {
230 use turbopack_core::issue::IssueExt;
231
232 let mut description = e.to_string();
237 let mut causes = e.chain().skip(1).peekable();
238 if causes.peek().is_some() {
239 description.push_str("\n\nCaused by:");
240 for (i, cause) in causes.enumerate() {
241 description.push_str(&format!("\n {i}: {cause}"));
242 }
243 }
244
245 SwcEcmaTransformFailureIssue {
246 file_path: ctx.file_path.clone(),
247 description: StyledString::Text(description.into()),
248 }
249 .resolved_cell()
250 .emit();
251
252 Ok(module_program.into_inner())
254 }
255 }
256 })?;
257
258 *program = transformed_program;
259
260 Ok(())
261 }
262}