1#![cfg_attr(
5 any(not(any(feature = "plugin")), target_arch = "wasm32"),
6 allow(unused)
7)]
8
9use std::{path::PathBuf, sync::Arc};
10
11use anyhow::{Context, Result};
12use atoms::Atom;
13use common::FileName;
14use serde::{Deserialize, Serialize};
15use swc_common::errors::{DiagnosticId, HANDLER};
16use swc_ecma_ast::Pass;
17#[cfg(feature = "plugin")]
18use swc_ecma_ast::*;
19use swc_ecma_loader::{
20 resolve::Resolve,
21 resolvers::{lru::CachingResolver, node::NodeModulesResolver},
22};
23use swc_ecma_visit::{fold_pass, noop_fold_type, Fold};
24#[cfg(feature = "plugin")]
25use swc_plugin_runner::runtime::Runtime as PluginRuntime;
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
35#[serde(deny_unknown_fields, rename_all = "camelCase")]
36pub struct PluginConfig(pub String, pub serde_json::Value);
37
38#[cfg(feature = "plugin")]
39pub(crate) fn plugins(
40 configured_plugins: Option<Vec<PluginConfig>>,
41 plugin_env_vars: Option<Vec<Atom>>,
42 metadata_context: std::sync::Arc<swc_common::plugin::metadata::TransformPluginMetadataContext>,
43 comments: Option<swc_common::comments::SingleThreadedComments>,
44 source_map: std::sync::Arc<swc_common::SourceMap>,
45 unresolved_mark: swc_common::Mark,
46 plugin_runtime: Arc<dyn PluginRuntime>,
47) -> impl Pass {
48 fold_pass(RustPlugins {
49 plugins: configured_plugins,
50 plugin_env_vars: plugin_env_vars.map(std::sync::Arc::new),
51 metadata_context,
52 comments,
53 source_map,
54 unresolved_mark,
55 plugin_runtime,
56 })
57}
58
59#[cfg(feature = "plugin")]
60struct RustPlugins {
61 plugins: Option<Vec<PluginConfig>>,
62 plugin_env_vars: Option<std::sync::Arc<Vec<Atom>>>,
63 metadata_context: std::sync::Arc<swc_common::plugin::metadata::TransformPluginMetadataContext>,
64 comments: Option<swc_common::comments::SingleThreadedComments>,
65 source_map: std::sync::Arc<swc_common::SourceMap>,
66 unresolved_mark: swc_common::Mark,
67 plugin_runtime: Arc<dyn PluginRuntime>,
68}
69
70#[cfg(feature = "plugin")]
71impl RustPlugins {
72 #[cfg(feature = "plugin")]
73 fn apply(&mut self, n: Program) -> Result<Program, anyhow::Error> {
74 use anyhow::Context;
75 if self.plugins.is_none() || self.plugins.as_ref().unwrap().is_empty() {
76 return Ok(n);
77 }
78
79 let filename = self.metadata_context.filename.clone();
80
81 if cfg!(feature = "manual-tokio-runtime") {
82 self.apply_inner(n)
83 } else {
84 let fut = async move { self.apply_inner(n) };
85 if let Ok(handle) = tokio::runtime::Handle::try_current() {
86 handle.block_on(fut)
87 } else {
88 tokio::runtime::Runtime::new().unwrap().block_on(fut)
89 }
90 }
91 .with_context(|| format!("failed to invoke plugin on '{filename:?}'"))
92 }
93
94 #[tracing::instrument(level = "info", skip_all, name = "apply_plugins")]
95 #[cfg(all(feature = "plugin", not(target_arch = "wasm32")))]
96 fn apply_inner(&mut self, n: Program) -> Result<Program, anyhow::Error> {
97 use anyhow::Context;
98 use swc_common::plugin::serialized::PluginSerializedBytes;
99
100 let should_enable_comments_proxy = self.comments.is_some();
102
103 swc_plugin_proxy::COMMENTS.set(
105 &swc_plugin_proxy::HostCommentsStorage {
106 inner: self.comments.clone(),
107 },
108 || {
109 let span = tracing::span!(tracing::Level::INFO, "serialize_program").entered();
110 let program = swc_common::plugin::serialized::VersionedSerializable::new(n);
111 let mut serialized = PluginSerializedBytes::try_serialize(&program)?;
112 drop(span);
113
114 if let Some(plugins) = &mut self.plugins {
121 for p in plugins.drain(..) {
122 let plugin_module_bytes = crate::config::PLUGIN_MODULE_CACHE
123 .inner
124 .get()
125 .unwrap()
126 .lock()
127 .get(&*self.plugin_runtime, &p.0)
128 .expect("plugin module should be loaded");
129
130 let plugin_name = plugin_module_bytes.get_module_name().to_string();
131
132 let mut transform_plugin_executor =
133 swc_plugin_runner::create_plugin_transform_executor(
134 &self.source_map,
135 &self.unresolved_mark,
136 &self.metadata_context,
137 self.plugin_env_vars.clone(),
138 plugin_module_bytes,
139 Some(p.1),
140 self.plugin_runtime.clone(),
141 );
142
143 let span = tracing::span!(
144 tracing::Level::INFO,
145 "execute_plugin_runner",
146 plugin_module = p.0.as_str()
147 )
148 .entered();
149
150 serialized = transform_plugin_executor
151 .transform(&serialized, Some(should_enable_comments_proxy))
152 .with_context(|| {
153 format!(
154 "failed to invoke `{}` as js transform plugin at {}",
155 &p.0, plugin_name
156 )
157 })?;
158 drop(span);
159 }
160 }
161
162 serialized.deserialize().map(|v| v.into_inner())
165 },
166 )
167 }
168
169 #[cfg(all(feature = "plugin", target_arch = "wasm32"))]
170 #[tracing::instrument(level = "info", skip_all)]
171 fn apply_inner(&mut self, n: Program) -> Result<Program, anyhow::Error> {
172 n
174 }
175}
176
177#[cfg(feature = "plugin")]
178impl Fold for RustPlugins {
179 noop_fold_type!();
180
181 fn fold_module(&mut self, n: Module) -> Module {
182 match self.apply(Program::Module(n)) {
183 Ok(program) => program.expect_module(),
184 Err(err) => {
185 HANDLER.with(|handler| {
186 handler.err_with_code(&err.to_string(), DiagnosticId::Error("plugin".into()));
187 });
188 Module::default()
189 }
190 }
191 }
192
193 fn fold_script(&mut self, n: Script) -> Script {
194 match self.apply(Program::Script(n)) {
195 Ok(program) => program.expect_script(),
196 Err(err) => {
197 HANDLER.with(|handler| {
198 handler.err_with_code(&err.to_string(), DiagnosticId::Error("plugin".into()));
199 });
200 Script::default()
201 }
202 }
203 }
204}
205
206#[cfg(feature = "plugin")]
207pub(crate) fn compile_wasm_plugins(
208 cache_root: Option<&str>,
209 plugins: &[PluginConfig],
210 #[cfg(feature = "plugin")] plugin_runtime: &dyn PluginRuntime,
211) -> Result<()> {
212 let plugin_resolver = CachingResolver::new(
213 40,
214 NodeModulesResolver::new(swc_ecma_loader::TargetEnv::Node, Default::default(), true),
215 );
216
217 crate::config::init_plugin_module_cache_once(true, cache_root);
220
221 let mut inner_cache = crate::config::PLUGIN_MODULE_CACHE
222 .inner
223 .get()
224 .expect("Cache should be available")
225 .lock();
226
227 for plugin_config in plugins.iter() {
229 let plugin_name = &plugin_config.0;
230
231 if !inner_cache.contains(plugin_runtime, plugin_name) {
232 let resolved_path = plugin_resolver
233 .resolve(&FileName::Real(PathBuf::from(plugin_name)), plugin_name)
234 .with_context(|| format!("failed to resolve plugin path: {plugin_name}"))?;
235
236 let path = if let FileName::Real(value) = resolved_path.filename {
237 value
238 } else {
239 anyhow::bail!("Failed to resolve plugin path: {:?}", resolved_path);
240 };
241
242 inner_cache.store_bytes_from_path(plugin_runtime, &path, plugin_name)?;
243 tracing::debug!("Initialized WASM plugin {plugin_name}");
244 }
245 }
246
247 Ok(())
248}