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)]
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 Self({
32 use swc_core::plugin_runner::plugin_module_bytes::{
33 CompiledPluginModuleBytes, RawPluginModuleBytes,
34 };
35 CompiledPluginModuleBytes::from(RawPluginModuleBytes::new(
36 plugin_name.to_string(),
37 plugin_bytes,
38 ))
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 plugin_runner::plugin_module_bytes::PluginModuleBytes,
136 };
137
138 let mut plugins = vec![];
139 for (plugin_module, config) in &self.plugins {
140 let plugin_module = &plugin_module.await?.0;
141
142 plugins.push((
143 plugin_module.get_module_name().to_string(),
144 config.clone(),
145 Box::new(plugin_module.clone()),
146 ));
147 }
148
149 let should_enable_comments_proxy =
150 !ctx.comments.leading.is_empty() && !ctx.comments.trailing.is_empty();
151
152 let comments = if should_enable_comments_proxy {
156 let mut leading =
159 swc_core::common::comments::SingleThreadedCommentsMapInner::default();
160 ctx.comments.leading.as_ref().into_iter().for_each(|c| {
161 leading.insert(*c.key(), c.value().clone());
162 });
163
164 let mut trailing =
165 swc_core::common::comments::SingleThreadedCommentsMapInner::default();
166 ctx.comments.trailing.as_ref().into_iter().for_each(|c| {
167 trailing.insert(*c.key(), c.value().clone());
168 });
169
170 Some(SingleThreadedComments::from_leading_and_trailing(
171 Rc::new(RefCell::new(leading)),
172 Rc::new(RefCell::new(trailing)),
173 ))
174 } else {
175 None
176 };
177
178 let transformed_program =
179 COMMENTS.set(&HostCommentsStorage { inner: comments }, || {
180 let module_program =
181 std::mem::replace(program, Program::Module(Module::dummy()));
182 let module_program =
183 swc_core::common::plugin::serialized::VersionedSerializable::new(
184 module_program,
185 );
186 let mut serialized_program =
187 PluginSerializedBytes::try_serialize(&module_program)?;
188
189 let transform_metadata_context = Arc::new(TransformPluginMetadataContext::new(
190 Some(ctx.file_path_str.to_string()),
191 "development".to_string(),
193 None,
194 ));
195
196 for (_plugin_name, plugin_config, plugin_module) in plugins.drain(..) {
203 let runtime =
204 swc_core::plugin_runner::wasix_runtime::build_wasi_runtime(None);
205 let mut transform_plugin_executor =
206 swc_core::plugin_runner::create_plugin_transform_executor(
207 ctx.source_map,
208 &ctx.unresolved_mark,
209 &transform_metadata_context,
210 None,
211 plugin_module,
212 Some(plugin_config),
213 runtime,
214 );
215
216 serialized_program = transform_plugin_executor
217 .transform(&serialized_program, Some(should_enable_comments_proxy))?;
218 }
219
220 serialized_program.deserialize().map(|v| v.into_inner())
221 })?;
222
223 *program = transformed_program;
224 }
225
226 #[cfg(not(feature = "swc_ecma_transform_plugin"))]
227 {
228 use turbopack_core::issue::IssueExt;
229
230 UnsupportedSwcEcmaTransformPluginsIssue {
231 file_path: ctx.file_path.clone(),
232 }
233 .resolved_cell()
234 .emit();
235 }
236
237 Ok(())
238 }
239}