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::sync::Arc;
106
107 use anyhow::Context;
108 use swc_core::{
109 common::{
110 plugin::{
111 metadata::TransformPluginMetadataContext, serialized::PluginSerializedBytes,
112 },
113 util::take::Take,
114 },
115 ecma::ast::Module,
116 plugin::proxies::{COMMENTS, HostCommentsStorage},
117 plugin_runner::plugin_module_bytes::CompiledPluginModuleBytes,
118 };
119 use swc_plugin_backend_wasmtime::WasmtimeRuntime;
120 use turbo_tasks::TryJoinIterExt;
121
122 let plugins = self
123 .plugins
124 .iter()
125 .map(async |(plugin_module, config)| {
126 let plugin_module = plugin_module.await?;
127 Ok((
128 plugin_module.name.clone(),
129 config.clone(),
130 Box::new(plugin_module.plugin.clone_module(&WasmtimeRuntime)),
131 ))
132 })
133 .try_join()
134 .await?;
135
136 let should_enable_comments_proxy =
137 !ctx.comments.leading.is_empty() && !ctx.comments.trailing.is_empty();
138
139 let comments = if should_enable_comments_proxy {
143 Some(turbopack_ecmascript::swc_comments_to_single_threaded(
144 ctx.comments,
145 ))
146 } else {
147 None
148 };
149
150 fn transform(
151 original_serialized_program: &PluginSerializedBytes,
152 ctx: &TransformContext<'_>,
153 plugins: Vec<(RcStr, serde_json::Value, Box<CompiledPluginModuleBytes>)>,
154 should_enable_comments_proxy: bool,
155 ) -> Result<Program> {
156 use either::Either;
157
158 let transform_metadata_context = Arc::new(TransformPluginMetadataContext::new(
159 Some(ctx.file_path_str.to_string()),
160 ctx.node_env.to_string(),
161 None,
162 ));
163
164 let mut serialized_program = Either::Left(original_serialized_program);
165
166 for (plugin_name, plugin_config, plugin_module) in plugins {
173 let mut transform_plugin_executor =
174 swc_core::plugin_runner::create_plugin_transform_executor(
175 ctx.source_map,
176 &ctx.unresolved_mark,
177 &transform_metadata_context,
178 None,
179 plugin_module,
180 Some(plugin_config),
181 Arc::new(WasmtimeRuntime),
182 );
183
184 serialized_program = Either::Right(
185 transform_plugin_executor
186 .transform(
187 serialized_program.as_ref().either(|p| *p, |p| p),
188 Some(should_enable_comments_proxy),
189 )
190 .with_context(|| format!("Failed to execute {plugin_name}"))?,
191 );
192 }
193
194 serialized_program
195 .as_ref()
196 .either(|p| *p, |p| p)
197 .deserialize()
198 .map(|v| v.into_inner())
199 }
200
201 let transformed_program = COMMENTS.set(&HostCommentsStorage { inner: comments }, || {
202 let module_program = std::mem::replace(program, Program::Module(Module::dummy()));
203 let module_program =
204 swc_core::common::plugin::serialized::VersionedSerializable::new(module_program);
205 let serialized_program = PluginSerializedBytes::try_serialize(&module_program)?;
206
207 match transform(
208 &serialized_program,
209 ctx,
210 plugins,
211 should_enable_comments_proxy,
212 ) {
213 Ok(program) => anyhow::Ok(program),
214 Err(e) => {
215 use turbopack_core::issue::IssueExt;
216
217 let mut description = e.to_string();
222 let mut causes = e.chain().skip(1).peekable();
223 if causes.peek().is_some() {
224 description.push_str("\n\nCaused by:");
225 for (i, cause) in causes.enumerate() {
226 description.push_str(&format!("\n {i}: {cause}"));
227 }
228 }
229
230 SwcEcmaTransformFailureIssue {
231 file_path: ctx.file_path.clone(),
232 description: StyledString::Text(description.into()),
233 }
234 .resolved_cell()
235 .emit();
236
237 Ok(module_program.into_inner())
239 }
240 }
241 })?;
242
243 *program = transformed_program;
244
245 Ok(())
246 }
247}