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;
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", into = "new", cell = "new")]
17pub struct SwcPluginModule(
18 #[turbo_tasks(trace_ignore, debug_ignore)]
19 #[cfg(feature = "swc_ecma_transform_plugin")]
20 pub swc_core::plugin_runner::plugin_module_bytes::CompiledPluginModuleBytes,
21 #[cfg(not(feature = "swc_ecma_transform_plugin"))] pub (),
25);
26
27impl SwcPluginModule {
28 pub fn new(plugin_name: &str, plugin_bytes: Vec<u8>) -> Self {
29 #[cfg(feature = "swc_ecma_transform_plugin")]
30 {
31 use swc_core::plugin_runner::plugin_module_bytes::{
32 CompiledPluginModuleBytes, RawPluginModuleBytes,
33 };
34 use swc_plugin_backend_wasmer::WasmerRuntime;
35
36 Self(CompiledPluginModuleBytes::from_raw_module(
37 &WasmerRuntime,
38 RawPluginModuleBytes::new(plugin_name.to_string(), plugin_bytes),
39 ))
40 }
41
42 #[cfg(not(feature = "swc_ecma_transform_plugin"))]
43 {
44 let _ = plugin_name;
45 let _ = plugin_bytes;
46 Self(())
47 }
48 }
49}
50
51#[turbo_tasks::value(shared)]
52struct UnsupportedSwcEcmaTransformPluginsIssue {
53 pub file_path: FileSystemPath,
54}
55
56#[turbo_tasks::value_impl]
57impl Issue for UnsupportedSwcEcmaTransformPluginsIssue {
58 fn severity(&self) -> IssueSeverity {
59 IssueSeverity::Warning
60 }
61
62 #[turbo_tasks::function]
63 fn stage(&self) -> Vc<IssueStage> {
64 IssueStage::Transform.cell()
65 }
66
67 #[turbo_tasks::function]
68 fn title(&self) -> Vc<StyledString> {
69 StyledString::Text(rcstr!(
70 "Unsupported SWC EcmaScript transform plugins on this platform."
71 ))
72 .cell()
73 }
74
75 #[turbo_tasks::function]
76 fn file_path(&self) -> Vc<FileSystemPath> {
77 self.file_path.clone().cell()
78 }
79
80 #[turbo_tasks::function]
81 fn description(&self) -> Vc<OptionStyledString> {
82 Vc::cell(Some(
83 StyledString::Text(rcstr!(
84 "Turbopack does not yet support running SWC EcmaScript transform plugins on this \
85 platform."
86 ))
87 .resolved_cell(),
88 ))
89 }
90}
91
92#[derive(Debug)]
94pub struct SwcEcmaTransformPluginsTransformer {
95 #[cfg(feature = "swc_ecma_transform_plugin")]
96 plugins: Vec<(turbo_tasks::ResolvedVc<SwcPluginModule>, serde_json::Value)>,
97}
98
99impl SwcEcmaTransformPluginsTransformer {
100 #[cfg(feature = "swc_ecma_transform_plugin")]
101 pub fn new(
102 plugins: Vec<(turbo_tasks::ResolvedVc<SwcPluginModule>, serde_json::Value)>,
103 ) -> Self {
104 Self { plugins }
105 }
106
107 #[cfg(not(feature = "swc_ecma_transform_plugin"))]
110 #[allow(clippy::new_without_default)]
111 pub fn new() -> Self {
112 Self {}
113 }
114}
115
116#[async_trait]
117impl CustomTransformer for SwcEcmaTransformPluginsTransformer {
118 #[cfg_attr(not(feature = "swc_ecma_transform_plugin"), allow(unused))]
119 #[tracing::instrument(level = tracing::Level::TRACE, name = "swc_ecma_transform_plugin", skip_all)]
120 async fn transform(&self, program: &mut Program, ctx: &TransformContext<'_>) -> Result<()> {
121 #[cfg(feature = "swc_ecma_transform_plugin")]
122 {
123 use std::{cell::RefCell, rc::Rc, sync::Arc};
124
125 use swc_core::{
126 common::{
127 comments::SingleThreadedComments,
128 plugin::{
129 metadata::TransformPluginMetadataContext, serialized::PluginSerializedBytes,
130 },
131 util::take::Take,
132 },
133 ecma::ast::Module,
134 plugin::proxies::{COMMENTS, HostCommentsStorage},
135 };
136 use swc_plugin_backend_wasmer::WasmerRuntime;
137 use turbo_tasks::TryJoinIterExt;
138
139 let plugins = self
140 .plugins
141 .iter()
142 .map(async |(plugin_module, config)| {
143 let plugin_module = plugin_module.await?;
144 Ok((
145 config.clone(),
146 Box::new(plugin_module.0.clone_module(&WasmerRuntime)),
147 ))
148 })
149 .try_join()
150 .await?;
151
152 let should_enable_comments_proxy =
153 !ctx.comments.leading.is_empty() && !ctx.comments.trailing.is_empty();
154
155 let comments = if should_enable_comments_proxy {
159 let mut leading =
162 swc_core::common::comments::SingleThreadedCommentsMapInner::default();
163 ctx.comments.leading.as_ref().into_iter().for_each(|c| {
164 leading.insert(*c.key(), c.value().clone());
165 });
166
167 let mut trailing =
168 swc_core::common::comments::SingleThreadedCommentsMapInner::default();
169 ctx.comments.trailing.as_ref().into_iter().for_each(|c| {
170 trailing.insert(*c.key(), c.value().clone());
171 });
172
173 Some(SingleThreadedComments::from_leading_and_trailing(
174 Rc::new(RefCell::new(leading)),
175 Rc::new(RefCell::new(trailing)),
176 ))
177 } else {
178 None
179 };
180
181 let transformed_program =
182 COMMENTS.set(&HostCommentsStorage { inner: comments }, || {
183 let module_program =
184 std::mem::replace(program, Program::Module(Module::dummy()));
185 let module_program =
186 swc_core::common::plugin::serialized::VersionedSerializable::new(
187 module_program,
188 );
189 let mut serialized_program =
190 PluginSerializedBytes::try_serialize(&module_program)?;
191
192 let transform_metadata_context = Arc::new(TransformPluginMetadataContext::new(
193 Some(ctx.file_path_str.to_string()),
194 "development".to_string(),
196 None,
197 ));
198
199 for (plugin_config, plugin_module) in plugins {
206 let mut transform_plugin_executor =
207 swc_core::plugin_runner::create_plugin_transform_executor(
208 ctx.source_map,
209 &ctx.unresolved_mark,
210 &transform_metadata_context,
211 None,
212 plugin_module,
213 Some(plugin_config),
214 Arc::new(WasmerRuntime),
215 );
216
217 serialized_program = transform_plugin_executor
218 .transform(&serialized_program, Some(should_enable_comments_proxy))?;
219 }
220
221 serialized_program.deserialize().map(|v| v.into_inner())
222 })?;
223
224 *program = transformed_program;
225 }
226
227 #[cfg(not(feature = "swc_ecma_transform_plugin"))]
228 {
229 use turbopack_core::issue::IssueExt;
230
231 UnsupportedSwcEcmaTransformPluginsIssue {
232 file_path: ctx.file_path.clone(),
233 }
234 .resolved_cell()
235 .emit();
236 }
237
238 Ok(())
239 }
240}