1use std::{env, sync::Arc};
2
3use anyhow::{anyhow, Context, Error};
4use parking_lot::Mutex;
5#[cfg(feature = "__rkyv")]
6use swc_common::plugin::serialized::{PluginError, PluginSerializedBytes};
7#[cfg(any(
8 feature = "plugin_transform_schema_v1",
9 feature = "plugin_transform_schema_vtest"
10))]
11use swc_common::plugin::PLUGIN_TRANSFORM_AST_SCHEMA_VERSION;
12use swc_common::{
13 plugin::{diagnostics::PluginCorePkgDiagnostics, metadata::TransformPluginMetadataContext},
14 SourceMap,
15};
16
17#[cfg(feature = "__rkyv")]
18use crate::{
19 host_environment::BaseHostEnvironment,
20 imported_fn::{
21 build_import_object, comments::CommentHostEnvironment,
22 diagnostics::DiagnosticContextHostEnvironment,
23 metadata_context::MetadataContextHostEnvironment,
24 set_transform_result::TransformResultHostEnvironment, source_map::SourceMapHostEnvironment,
25 },
26 memory_interop::write_into_memory_view,
27};
28use crate::{plugin_module_bytes::PluginModuleBytes, runtime};
29
30struct PluginTransformState {
32 instance: Box<dyn runtime::Instance>,
33 transform_result: Arc<Mutex<Vec<u8>>>,
34 #[allow(unused)]
35 plugin_core_diag: PluginCorePkgDiagnostics,
36}
37
38#[cfg(feature = "__rkyv")]
39impl PluginTransformState {
40 fn run(
41 &mut self,
42 program: &PluginSerializedBytes,
43 unresolved_mark: swc_common::Mark,
44 should_enable_comments_proxy: Option<bool>,
45 ) -> Result<PluginSerializedBytes, Error> {
46 let should_enable_comments_proxy =
47 u32::from(should_enable_comments_proxy.unwrap_or_default());
48
49 let guest_program_ptr = write_into_memory_view(
51 &mut *self.instance.caller()?,
52 program,
53 |caller, serialized_len| {
54 let serialized_len = serialized_len
55 .try_into()
56 .expect("Should able to convert size");
57 caller.alloc(serialized_len).unwrap_or_else(|_| {
58 panic!("Should able to allocate memory for the size of {serialized_len}")
59 })
60 },
61 );
62
63 let returned_ptr_result = self.instance.transform(
64 guest_program_ptr.0,
65 guest_program_ptr.1,
66 unresolved_mark.as_u32(),
67 should_enable_comments_proxy,
68 )?;
69
70 let transformed_result = &(*self.transform_result.lock());
73 let ret = PluginSerializedBytes::from_slice(&transformed_result[..]);
74
75 let ret = if returned_ptr_result == 0 {
76 Ok(ret)
77 } else {
78 let err: PluginError = ret.deserialize()?.into_inner();
79 match err {
80 PluginError::SizeInteropFailure(msg) => Err(anyhow!(
81 "Failed to convert pointer size to calculate: {}",
82 msg
83 )),
84 PluginError::Deserialize(msg) | PluginError::Serialize(msg) => {
85 Err(anyhow!("{}", msg))
86 }
87 _ => Err(anyhow!(
88 "Unexpected error occurred while running plugin transform"
89 )),
90 }
91 };
92
93 self.instance
94 .caller()?
95 .free(guest_program_ptr.0, guest_program_ptr.1)?;
96 self.instance.cleanup()?;
97
98 ret
99 }
100
101 #[allow(unreachable_code)]
109 pub fn is_transform_schema_compatible(&mut self) -> Result<(), Error> {
110 #[cfg(any(
111 feature = "plugin_transform_schema_v1",
112 feature = "plugin_transform_schema_vtest"
113 ))]
114 return {
115 let host_schema_version = PLUGIN_TRANSFORM_AST_SCHEMA_VERSION;
116
117 if host_schema_version >= self.plugin_core_diag.ast_schema_version {
119 Ok(())
120 } else {
121 anyhow::bail!(
122 "Plugin's AST schema version is not compatible with host's. Host: {}, Plugin: \
123 {}",
124 host_schema_version,
125 self.plugin_core_diag.ast_schema_version
126 )
127 }
128 };
129
130 #[cfg(not(all(
131 feature = "plugin_transform_schema_v1",
132 feature = "plugin_transform_schema_vtest"
133 )))]
134 anyhow::bail!(
135 "Plugin runner cannot detect plugin's schema version. Ensure host is compiled with \
136 proper versions"
137 )
138 }
139}
140
141pub struct TransformExecutor {
143 source_map: Arc<SourceMap>,
144 unresolved_mark: swc_common::Mark,
145 metadata_context: Arc<TransformPluginMetadataContext>,
146 plugin_env_vars: Option<Arc<Vec<swc_atoms::Atom>>>,
147 plugin_config: Option<serde_json::Value>,
148 module_bytes: Box<dyn PluginModuleBytes>,
149 runtime: Arc<dyn runtime::Runtime>,
150}
151
152#[cfg(feature = "__rkyv")]
153impl TransformExecutor {
154 #[tracing::instrument(
155 level = "info",
156 skip(source_map, metadata_context, plugin_config, module_bytes, runtime)
157 )]
158 pub fn new(
159 module_bytes: Box<dyn PluginModuleBytes>,
160 source_map: &Arc<SourceMap>,
161 unresolved_mark: &swc_common::Mark,
162 metadata_context: &Arc<TransformPluginMetadataContext>,
163 plugin_env_vars: Option<Arc<Vec<swc_atoms::Atom>>>,
164 plugin_config: Option<serde_json::Value>,
165 runtime: Arc<dyn runtime::Runtime>,
166 ) -> Self {
167 Self {
168 source_map: source_map.clone(),
169 unresolved_mark: *unresolved_mark,
170 metadata_context: metadata_context.clone(),
171 plugin_env_vars,
172 plugin_config,
173 module_bytes,
174 runtime,
175 }
176 }
177
178 fn setup_plugin_env_exports(&mut self) -> Result<PluginTransformState, Error> {
181 let module_name = self.module_bytes.get_module_name();
184 let module = self.module_bytes.compile_module(&*self.runtime);
185
186 let context_key_buffer = Arc::new(Mutex::new(Vec::new()));
187 let metadata_env = Arc::new(MetadataContextHostEnvironment::new(
188 &self.metadata_context,
189 &self.plugin_config,
190 &context_key_buffer,
191 ));
192
193 let transform_result: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
194 let transform_env = Arc::new(TransformResultHostEnvironment::new(&transform_result));
195
196 let base_env = Arc::new(BaseHostEnvironment::new());
197
198 let comment_buffer = Arc::new(Mutex::new(Vec::new()));
199 let comments_env = Arc::new(CommentHostEnvironment::new(&comment_buffer));
200
201 let source_map_buffer = Arc::new(Mutex::new(Vec::new()));
202 let source_map = Arc::new(Mutex::new(self.source_map.clone()));
203 let source_map_host_env = Arc::new(SourceMapHostEnvironment::new(
204 &source_map,
205 &source_map_buffer,
206 ));
207
208 let diagnostics_buffer: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
209 let diagnostics_env = Arc::new(DiagnosticContextHostEnvironment::new(&diagnostics_buffer));
210
211 let import_object = build_import_object(
212 metadata_env,
213 transform_env,
214 base_env,
215 comments_env,
216 source_map_host_env,
217 diagnostics_env,
218 );
219 let envs = self
220 .plugin_env_vars
221 .iter()
222 .flat_map(|list| list.iter())
223 .filter_map(|name| {
224 std::env::var(name.as_str())
225 .ok()
226 .map(|value| (name.as_str().into(), value))
227 })
228 .collect::<Vec<_>>();
229 let instance = self
230 .runtime
231 .init(module_name, import_object, envs, module)?;
232
233 let diag_result: PluginCorePkgDiagnostics =
234 PluginSerializedBytes::from_slice(&(&(*diagnostics_buffer.lock()))[..])
235 .deserialize()?
236 .into_inner();
237
238 Ok(PluginTransformState {
239 instance,
240 transform_result,
241 plugin_core_diag: diag_result,
242 })
243 }
244
245 #[tracing::instrument(level = "info", skip_all)]
246 pub fn transform(
247 &mut self,
248 program: &PluginSerializedBytes,
249 should_enable_comments_proxy: Option<bool>,
250 ) -> Result<PluginSerializedBytes, Error> {
251 let mut transform_state = self.setup_plugin_env_exports()?;
252 transform_state.is_transform_schema_compatible()?;
253 transform_state
254 .run(program, self.unresolved_mark, should_enable_comments_proxy)
255 .with_context(|| {
256 format!(
257 "failed to run Wasm plugin transform. Please ensure the version of `swc_core` \
258 used by the plugin is compatible with the host runtime. See the \
259 documentation for compatibility information. If you are an author of the \
260 plugin, please update `swc_core` to the compatible version.
261
262 Note that if you want to use the os features like filesystem, you need to use \
263 `wasi`. Wasm itself does not have concept of filesystem.
264
265 https://swc.rs/docs/plugin/selecting-swc-core
266
267 See https://plugins.swc.rs/versions/from-plugin-runner/{PKG_VERSION} for the list of the compatible versions.
268
269 Build info:
270 Date: {BUILD_DATE}
271 Timestamp: {BUILD_TIMESTAMP}
272
273 Version info:
274 swc_plugin_runner: {PKG_VERSION}
275 Dependencies: {PKG_DEPS}
276 "
277 )
278 })
279 }
280}
281
282const BUILD_DATE: &str = env!("VERGEN_BUILD_DATE");
283const BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP");
284const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
285const PKG_DEPS: &str = env!("VERGEN_CARGO_DEPENDENCIES");