diff --git a/src/lib.rs b/src/lib.rs
index 293e8b0b0..e018feef7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,6 +14,7 @@ use swc::{config::Options, Compiler, TransformOutput};
use swc_common::{self, errors::Handler, FileName, FilePathMapping, SourceMap};
mod jest;
+mod plugin;
#[cfg(all(unix, not(target_env = "musl")))]
#[global_allocator]
@@ -104,6 +105,13 @@ fn init(module: &mut Module) -> Result<()> {
module.create_named_method("transform", transform)?;
module.create_named_method("transformJest", jest_transform)?;
+
+ module.create_named_method("transformFactory", plugin::transform_factory)?;
+
+ module.create_named_method(
+ "transformSyncWithPlugins",
+ plugin::transform_sync_with_plugins,
+ )?;
Ok(())
}
diff --git a/src/plugin.rs b/src/plugin.rs
new file mode 100644
index 000000000..106291afc
--- /dev/null
+++ b/src/plugin.rs
@@ -0,0 +1,104 @@
+use std::path::PathBuf;
+use std::str::FromStr;
+use std::sync::Arc;
+
+use napi::{CallContext, Error, JsExternal, JsObject, JsString, Result, Status};
+use serde_json;
+use swc::config::Options;
+use swc::Compiler;
+use swc::TransformOutput;
+use swc_common::FileName;
+use swc_common::SourceFile;
+use swc_ecma_visit::Fold;
+
+use crate::get_compiler;
+
+pub trait Plugin: Fold + 'static {
+ fn with_options(&mut self, options: &Options) -> Result<()>;
+}
+
+#[inline(always)]
+pub fn create_plugin
(ctx: &CallContext, plugin: P) -> Result
+where
+ P: Plugin,
+{
+ ctx.env.create_external(Box::into_raw(Box::new(plugin)))
+}
+
+struct Factory(Vec>, Options);
+
+impl Factory {
+ #[inline(always)]
+ fn transform(
+ &mut self,
+ filename: String,
+ source: Arc,
+ compiler: &Compiler,
+ ) -> Result {
+ let config = self.1.config.as_ref().unwrap();
+ self.1.filename = filename;
+ let mut program = compiler
+ .parse_js(
+ source,
+ config.jsc.target,
+ config.jsc.syntax.unwrap(),
+ true,
+ false,
+ )
+ .map_err(|err| Error::new(Status::InvalidArg, format!("{}", err)))?;
+ for plugin in &mut self.0 {
+ program = compiler.transform(program, false, plugin);
+ }
+ compiler
+ .process_js(program, &self.1)
+ .map_err(|e| Error::new(Status::InvalidArg, format!("{}", e)))
+ }
+}
+
+#[js_function(2)]
+pub fn transform_factory(ctx: CallContext) -> Result {
+ let plugins = ctx.get::(0)?;
+ let options = ctx.get::(1)?;
+ let swc_options: Options = serde_json::from_str(options.as_str()?)
+ .map_err(|e| Error::new(Status::InvalidArg, format!("{}", e)))?;
+
+ let len = plugins.get_array_length_unchecked()?;
+ let native_plugins = Vec::with_capacity(len as _);
+ for index in 0..len {
+ let js_external = plugins.get_element::(index)?;
+ let plugin_ptr = ctx.env.get_value_external::<*mut _>(&js_external)?;
+ let mut plugin_native: Box = unsafe { Box::from_raw(*plugin_ptr) };
+ Plugin::with_options(plugin_native.as_mut(), &swc_options)?;
+ }
+ ctx
+ .env
+ .create_external(Factory(native_plugins, swc_options))
+}
+
+#[js_function(3)]
+pub fn transform_sync_with_plugins(ctx: CallContext) -> Result {
+ let factory_external = ctx.get::(0)?;
+ let filename = ctx.get::(1)?;
+ let source = ctx.get::(2)?;
+ let factory: &mut Factory = ctx.env.get_value_external(&factory_external)?;
+ let c = get_compiler();
+ let fm = c.cm.new_source_file(
+ FileName::Real(
+ PathBuf::from_str(filename.as_str()?)
+ .map_err(|e| Error::new(Status::InvalidArg, format!("Invalid path {}", e)))?,
+ ),
+ source.as_str()?.to_owned(),
+ );
+ let output = factory.transform(filename.as_str()?.to_owned(), fm, c)?;
+
+ let mut result = ctx.env.create_object()?;
+ result.set_named_property("code", ctx.env.create_string_from_std(output.code)?)?;
+ result.set_named_property(
+ "map",
+ match output.map {
+ None => ctx.env.get_null()?.into_unknown()?,
+ Some(sm) => ctx.env.create_string_from_std(sm)?.into_unknown()?,
+ },
+ )?;
+ Ok(result)
+}