swc/
plugin.rs

1//! This module always exists because cfg attributes are not stabilized in
2//! expressions at the moment.
3
4#![cfg_attr(
5    any(not(any(feature = "plugin")), target_arch = "wasm32"),
6    allow(unused)
7)]
8
9use serde::{Deserialize, Serialize};
10use swc_common::errors::HANDLER;
11use swc_ecma_ast::Pass;
12#[cfg(feature = "plugin")]
13use swc_ecma_ast::*;
14use swc_ecma_visit::{fold_pass, noop_fold_type, Fold};
15
16/// A tuple represents a plugin.
17///
18/// First element is a resolvable name to the plugin, second is a JSON object
19/// that represents configuration option for those plugin.
20/// Type of plugin's configuration is up to each plugin - swc/core does not have
21/// strong type and it'll be serialized into plain string when it's passed to
22/// plugin's entrypoint function.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(deny_unknown_fields, rename_all = "camelCase")]
25pub struct PluginConfig(pub String, pub serde_json::Value);
26
27pub fn plugins(
28    configured_plugins: Option<Vec<PluginConfig>>,
29    metadata_context: std::sync::Arc<swc_common::plugin::metadata::TransformPluginMetadataContext>,
30    comments: Option<swc_common::comments::SingleThreadedComments>,
31    source_map: std::sync::Arc<swc_common::SourceMap>,
32    unresolved_mark: swc_common::Mark,
33) -> impl Pass {
34    fold_pass(RustPlugins {
35        plugins: configured_plugins,
36        metadata_context,
37        comments,
38        source_map,
39        unresolved_mark,
40    })
41}
42
43struct RustPlugins {
44    plugins: Option<Vec<PluginConfig>>,
45    metadata_context: std::sync::Arc<swc_common::plugin::metadata::TransformPluginMetadataContext>,
46    comments: Option<swc_common::comments::SingleThreadedComments>,
47    source_map: std::sync::Arc<swc_common::SourceMap>,
48    unresolved_mark: swc_common::Mark,
49}
50
51impl RustPlugins {
52    #[cfg(feature = "plugin")]
53    fn apply(&mut self, n: Program) -> Result<Program, anyhow::Error> {
54        use anyhow::Context;
55        if self.plugins.is_none() || self.plugins.as_ref().unwrap().is_empty() {
56            return Ok(n);
57        }
58
59        let filename = self.metadata_context.filename.clone();
60
61        if cfg!(feature = "manual-tokio-runtime") {
62            self.apply_inner(n)
63        } else {
64            let fut = async move { self.apply_inner(n) };
65            if let Ok(handle) = tokio::runtime::Handle::try_current() {
66                handle.block_on(fut)
67            } else {
68                tokio::runtime::Runtime::new().unwrap().block_on(fut)
69            }
70        }
71        .with_context(|| format!("failed to invoke plugin on '{filename:?}'"))
72    }
73
74    #[tracing::instrument(level = "info", skip_all, name = "apply_plugins")]
75    #[cfg(all(feature = "plugin", not(target_arch = "wasm32")))]
76    fn apply_inner(&mut self, n: Program) -> Result<Program, anyhow::Error> {
77        use anyhow::Context;
78        use swc_common::plugin::serialized::PluginSerializedBytes;
79
80        // swc_plugin_macro will not inject proxy to the comments if comments is empty
81        let should_enable_comments_proxy = self.comments.is_some();
82
83        // Set comments once per whole plugin transform execution.
84        swc_plugin_proxy::COMMENTS.set(
85            &swc_plugin_proxy::HostCommentsStorage {
86                inner: self.comments.clone(),
87            },
88            || {
89                let span = tracing::span!(tracing::Level::INFO, "serialize_program").entered();
90                let program = swc_common::plugin::serialized::VersionedSerializable::new(n);
91                let mut serialized = PluginSerializedBytes::try_serialize(&program)?;
92                drop(span);
93
94                // Run plugin transformation against current program.
95                // We do not serialize / deserialize between each plugin execution but
96                // copies raw transformed bytes directly into plugin's memory space.
97                // Note: This doesn't mean plugin won't perform any se/deserialization: it
98                // still have to construct from raw bytes internally to perform actual
99                // transform.
100                if let Some(plugins) = &mut self.plugins {
101                    for p in plugins.drain(..) {
102                        let plugin_module_bytes = crate::config::PLUGIN_MODULE_CACHE
103                            .inner
104                            .get()
105                            .unwrap()
106                            .lock()
107                            .get(&p.0)
108                            .expect("plugin module should be loaded");
109
110                        let plugin_name = plugin_module_bytes.get_module_name().to_string();
111                        let runtime = swc_plugin_runner::wasix_runtime::build_wasi_runtime(
112                            crate::config::PLUGIN_MODULE_CACHE
113                                .inner
114                                .get()
115                                .unwrap()
116                                .lock()
117                                .get_fs_cache_root()
118                                .map(|v| std::path::PathBuf::from(v)),
119                        );
120                        let mut transform_plugin_executor =
121                            swc_plugin_runner::create_plugin_transform_executor(
122                                &self.source_map,
123                                &self.unresolved_mark,
124                                &self.metadata_context,
125                                plugin_module_bytes,
126                                Some(p.1),
127                                runtime,
128                            );
129
130                        let span = tracing::span!(
131                            tracing::Level::INFO,
132                            "execute_plugin_runner",
133                            plugin_module = p.0.as_str()
134                        )
135                        .entered();
136
137                        serialized = transform_plugin_executor
138                            .transform(&serialized, Some(should_enable_comments_proxy))
139                            .with_context(|| {
140                                format!(
141                                    "failed to invoke `{}` as js transform plugin at {}",
142                                    &p.0, plugin_name
143                                )
144                            })?;
145                        drop(span);
146                    }
147                }
148
149                // Plugin transformation is done. Deserialize transformed bytes back
150                // into Program
151                serialized.deserialize().map(|v| v.into_inner())
152            },
153        )
154    }
155
156    #[cfg(all(feature = "plugin", target_arch = "wasm32"))]
157    #[tracing::instrument(level = "info", skip_all)]
158    fn apply_inner(&mut self, n: Program) -> Result<Program, anyhow::Error> {
159        // [TODO]: unimplemented
160        n
161    }
162}
163
164impl Fold for RustPlugins {
165    noop_fold_type!();
166
167    #[cfg(feature = "plugin")]
168    fn fold_module(&mut self, n: Module) -> Module {
169        match self.apply(Program::Module(n)) {
170            Ok(program) => program.expect_module(),
171            Err(err) => {
172                HANDLER.with(|handler| {
173                    handler.err(&err.to_string());
174                });
175                Module::default()
176            }
177        }
178    }
179
180    #[cfg(feature = "plugin")]
181    fn fold_script(&mut self, n: Script) -> Script {
182        match self.apply(Program::Script(n)) {
183            Ok(program) => program.expect_script(),
184            Err(err) => {
185                HANDLER.with(|handler| {
186                    handler.err(&err.to_string());
187                });
188                Script::default()
189            }
190        }
191    }
192}