diff --git a/native/src/lib.rs b/native/src/lib.rs index 3d4405f8fbf..90382ab90d1 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -9,24 +9,19 @@ extern crate path_clean; extern crate serde; extern crate swc; -use anyhow::{Context as _, Error}; +use anyhow::Error; use backtrace::Backtrace; use neon::prelude::*; -use path_clean::clean; -use std::{ - env, - panic::set_hook, - path::{Path, PathBuf}, - sync::Arc, -}; +use std::{env, panic::set_hook, sync::Arc}; use swc::{ - common::{self, errors::Handler, FileName, FilePathMapping, SourceFile, SourceMap}, - config::{Options, ParseOptions, SourceMapsConfig}, - ecmascript::ast::Program, + common::{self, errors::Handler, FilePathMapping, SourceMap}, Compiler, TransformOutput, }; mod bundle; +mod parse; +mod print; +mod transform; fn init(_cx: MethodContext) -> NeonResult { if cfg!(debug_assertions) || env::var("SWC_DEBUG").unwrap_or_else(|_| String::new()) == "1" { @@ -50,24 +45,7 @@ fn init(_cx: MethodContext) -> NeonResult { Ok(Arc::new(c)) } -/// Input to transform -#[derive(Debug)] -enum Input { - /// json string - Program(String), - /// Raw source code. - Source(Arc), - /// File - File(PathBuf), -} - -struct TransformTask { - c: Arc, - input: Input, - options: Options, -} - -fn complete_output<'a>( +pub fn complete_output<'a>( mut cx: impl Context<'a>, result: Result, ) -> JsResult<'a, JsValue> { @@ -77,439 +55,6 @@ fn complete_output<'a>( } } -impl Task for TransformTask { - type Output = TransformOutput; - type Error = Error; - type JsEvent = JsValue; - - fn perform(&self) -> Result { - self.c.run(|| match self.input { - Input::Program(ref s) => { - let program: Program = - serde_json::from_str(&s).expect("failed to deserialize Program"); - // TODO: Source map - self.c.process_js(program, &self.options) - } - - Input::File(ref path) => { - let fm = self.c.cm.load_file(path).context("failed to read module")?; - self.c.process_js_file(fm, &self.options) - } - - Input::Source(ref s) => self.c.process_js_file(s.clone(), &self.options), - }) - } - - fn complete( - self, - cx: TaskContext, - result: Result, - ) -> JsResult { - complete_output(cx, result) - } -} - -/// returns `compiler, (src / path), options, plugin, callback` -fn schedule_transform(mut cx: MethodContext, op: F) -> JsResult -where - F: FnOnce(&Arc, String, bool, Options) -> TransformTask, -{ - let c; - let this = cx.this(); - { - let guard = cx.lock(); - c = this.borrow(&guard).clone(); - }; - - let s = cx.argument::(0)?.value(); - let is_module = cx.argument::(1)?; - let options_arg = cx.argument::(2)?; - - let options: Options = neon_serde::from_value(&mut cx, options_arg)?; - let callback = cx.argument::(3)?; - - let task = op(&c, s, is_module.value(), options); - task.schedule(callback); - - Ok(cx.undefined().upcast()) -} - -fn exec_transform(mut cx: MethodContext, op: F) -> JsResult -where - F: FnOnce(&Compiler, String, &Options) -> Result, Error>, -{ - let s = cx.argument::(0)?; - let is_module = cx.argument::(1)?; - let options: Options = match cx.argument_opt(2) { - Some(v) => neon_serde::from_value(&mut cx, v)?, - None => { - let obj = cx.empty_object().upcast(); - neon_serde::from_value(&mut cx, obj)? - } - }; - - let this = cx.this(); - let output = { - let guard = cx.lock(); - let c = this.borrow(&guard); - c.run(|| { - if is_module.value() { - let program: Program = - serde_json::from_str(&s.value()).expect("failed to deserialize Program"); - c.process_js(program, &options) - } else { - let fm = op(&c, s.value(), &options).expect("failed to create fm"); - c.process_js_file(fm, &options) - } - }) - }; - - complete_output(cx, output) -} - -fn transform(cx: MethodContext) -> JsResult { - schedule_transform(cx, |c, src, is_module, options| { - let input = if is_module { - Input::Program(src) - } else { - Input::Source(c.cm.new_source_file( - if options.filename.is_empty() { - FileName::Anon - } else { - FileName::Real(options.filename.clone().into()) - }, - src, - )) - }; - - TransformTask { - c: c.clone(), - input, - options, - } - }) -} - -fn transform_sync(cx: MethodContext) -> JsResult { - exec_transform(cx, |c, src, options| { - Ok(c.cm.new_source_file( - if options.filename.is_empty() { - FileName::Anon - } else { - FileName::Real(options.filename.clone().into()) - }, - src, - )) - }) -} - -fn transform_file(cx: MethodContext) -> JsResult { - schedule_transform(cx, |c, path, _, options| { - let path = clean(&path); - - TransformTask { - c: c.clone(), - input: Input::File(path.into()), - options, - } - }) -} - -fn transform_file_sync(cx: MethodContext) -> JsResult { - exec_transform(cx, |c, path, _| { - Ok(c.cm - .load_file(Path::new(&path)) - .expect("failed to load file")) - }) -} - -// ----- Parsing ----- - -struct ParseTask { - c: Arc, - fm: Arc, - options: ParseOptions, -} - -struct ParseFileTask { - c: Arc, - path: PathBuf, - options: ParseOptions, -} - -fn complete_parse<'a>( - mut cx: impl Context<'a>, - result: Result, - c: &Compiler, -) -> JsResult<'a, JsValue> { - c.run(|| match result { - Ok(program) => Ok(cx - .string(serde_json::to_string(&program).expect("failed to serialize Program")) - .upcast()), - Err(err) => cx.throw_error(format!("{:?}", err)), - }) -} - -impl Task for ParseTask { - type Output = Program; - type Error = Error; - type JsEvent = JsValue; - - fn perform(&self) -> Result { - self.c.run(|| { - self.c.parse_js( - self.fm.clone(), - self.options.target, - self.options.syntax, - self.options.is_module, - self.options.comments, - ) - }) - } - - fn complete( - self, - cx: TaskContext, - result: Result, - ) -> JsResult { - complete_parse(cx, result, &self.c) - } -} - -impl Task for ParseFileTask { - type Output = Program; - type Error = Error; - type JsEvent = JsValue; - - fn perform(&self) -> Result { - self.c.run(|| { - let fm = self - .c - .cm - .load_file(&self.path) - .context("failed to read module")?; - - self.c.parse_js( - fm, - self.options.target, - self.options.syntax, - self.options.is_module, - self.options.comments, - ) - }) - } - - fn complete( - self, - cx: TaskContext, - result: Result, - ) -> JsResult { - complete_parse(cx, result, &self.c) - } -} - -fn parse(mut cx: MethodContext) -> JsResult { - let src = cx.argument::(0)?; - let options_arg = cx.argument::(1)?; - let options: ParseOptions = neon_serde::from_value(&mut cx, options_arg)?; - let callback = cx.argument::(2)?; - - let this = cx.this(); - { - let guard = cx.lock(); - let c = this.borrow(&guard); - - let fm = c.cm.new_source_file(FileName::Anon, src.value()); - - ParseTask { - c: c.clone(), - fm, - options, - } - .schedule(callback); - }; - - Ok(cx.undefined().upcast()) -} - -fn parse_sync(mut cx: MethodContext) -> JsResult { - let c; - let this = cx.this(); - { - let guard = cx.lock(); - let compiler = this.borrow(&guard); - c = compiler.clone(); - } - c.run(|| { - let src = cx.argument::(0)?; - let options_arg = cx.argument::(1)?; - let options: ParseOptions = neon_serde::from_value(&mut cx, options_arg)?; - - let program = { - let fm = c.cm.new_source_file(FileName::Anon, src.value()); - c.parse_js( - fm, - options.target, - options.syntax, - options.is_module, - options.comments, - ) - }; - - complete_parse(cx, program, &c) - }) -} - -fn parse_file_sync(mut cx: MethodContext) -> JsResult { - let c; - let this = cx.this(); - { - let guard = cx.lock(); - let compiler = this.borrow(&guard); - c = compiler.clone(); - } - c.run(|| { - let path = cx.argument::(0)?; - let options_arg = cx.argument::(1)?; - let options: ParseOptions = neon_serde::from_value(&mut cx, options_arg)?; - - let program = { - let fm = - c.cm.load_file(Path::new(&path.value())) - .expect("failed to read program file"); - - c.parse_js( - fm, - options.target, - options.syntax, - options.is_module, - options.comments, - ) - }; - - complete_parse(cx, program, &c) - }) -} - -fn parse_file(mut cx: MethodContext) -> JsResult { - let path = cx.argument::(0)?; - let options_arg = cx.argument::(1)?; - let options: ParseOptions = neon_serde::from_value(&mut cx, options_arg)?; - let callback = cx.argument::(2)?; - - let this = cx.this(); - { - let guard = cx.lock(); - let c = this.borrow(&guard); - - ParseFileTask { - c: c.clone(), - path: path.value().into(), - options, - } - .schedule(callback); - }; - - Ok(cx.undefined().upcast()) -} - -// ----- Printing ----- - -struct PrintTask { - c: Arc, - program: Program, - options: Options, -} - -impl Task for PrintTask { - type Output = TransformOutput; - type Error = Error; - type JsEvent = JsValue; - fn perform(&self) -> Result { - self.c.run(|| { - self.c.print( - &self.program, - self.options - .source_maps - .clone() - .unwrap_or(SourceMapsConfig::Bool(false)), - None, - self.options - .config - .clone() - .unwrap_or_default() - .minify - .unwrap_or(false), - ) - }) - } - - fn complete( - self, - cx: TaskContext, - result: Result, - ) -> JsResult { - complete_output(cx, result) - } -} - -fn print(mut cx: MethodContext) -> JsResult { - let program = cx.argument::(0)?; - let program: Program = - serde_json::from_str(&program.value()).expect("failed to deserialize Program"); - - let options = cx.argument::(1)?; - let options: Options = neon_serde::from_value(&mut cx, options)?; - - let callback = cx.argument::(2)?; - - let this = cx.this(); - { - let guard = cx.lock(); - let c = this.borrow(&guard); - - PrintTask { - c: c.clone(), - program, - options, - } - .schedule(callback) - } - - Ok(cx.undefined().upcast()) -} - -fn print_sync(mut cx: MethodContext) -> JsResult { - let c; - let this = cx.this(); - { - let guard = cx.lock(); - let compiler = this.borrow(&guard); - c = compiler.clone(); - } - c.run(|| { - let program = cx.argument::(0)?; - let program: Program = - serde_json::from_str(&program.value()).expect("failed to deserialize Program"); - - let options = cx.argument::(1)?; - let options: Options = neon_serde::from_value(&mut cx, options)?; - - let result = { - c.print( - &program, - options - .source_maps - .clone() - .unwrap_or(SourceMapsConfig::Bool(false)), - None, - options.config.unwrap_or_default().minify.unwrap_or(false), - ) - }; - complete_output(cx, result) - }) -} - pub type ArcCompiler = Arc; declare_types! { @@ -519,43 +64,43 @@ declare_types! { } method transform(cx) { - transform(cx) + transform::transform(cx) } method transformSync(cx) { - transform_sync(cx) + transform::transform_sync(cx) } method transformFile(cx) { - transform_file(cx) + transform::transform_file(cx) } method transformFileSync(cx) { - transform_file_sync(cx) + transform::transform_file_sync(cx) } method parse(cx) { - parse(cx) + parse::parse(cx) } method parseSync(cx) { - parse_sync(cx) + parse::parse_sync(cx) } method parseFile(cx) { - parse_file(cx) + parse::parse_file(cx) } method parseFileSync(cx) { - parse_file_sync(cx) + parse::parse_file_sync(cx) } method print(cx) { - print(cx) + print::print(cx) } method printSync(cx) { - print_sync(cx) + print::print_sync(cx) } method bundle(cx) { diff --git a/native/src/parse.rs b/native/src/parse.rs new file mode 100644 index 00000000000..81d14635f6c --- /dev/null +++ b/native/src/parse.rs @@ -0,0 +1,203 @@ +use crate::JsCompiler; +use anyhow::{Context as _, Error}; +use neon::prelude::*; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; +use swc::{ + common::{FileName, SourceFile}, + config::ParseOptions, + ecmascript::ast::Program, + Compiler, +}; + +// ----- Parsing ----- + +pub struct ParseTask { + pub c: Arc, + pub fm: Arc, + pub options: ParseOptions, +} + +pub struct ParseFileTask { + pub c: Arc, + pub path: PathBuf, + pub options: ParseOptions, +} + +pub fn complete_parse<'a>( + mut cx: impl Context<'a>, + result: Result, + c: &Compiler, +) -> JsResult<'a, JsValue> { + c.run(|| match result { + Ok(program) => Ok(cx + .string(serde_json::to_string(&program).expect("failed to serialize Program")) + .upcast()), + Err(err) => cx.throw_error(format!("{:?}", err)), + }) +} + +impl Task for ParseTask { + type Output = Program; + type Error = Error; + type JsEvent = JsValue; + + fn perform(&self) -> Result { + self.c.run(|| { + self.c.parse_js( + self.fm.clone(), + self.options.target, + self.options.syntax, + self.options.is_module, + self.options.comments, + ) + }) + } + + fn complete( + self, + cx: TaskContext, + result: Result, + ) -> JsResult { + complete_parse(cx, result, &self.c) + } +} + +impl Task for ParseFileTask { + type Output = Program; + type Error = Error; + type JsEvent = JsValue; + + fn perform(&self) -> Result { + self.c.run(|| { + let fm = self + .c + .cm + .load_file(&self.path) + .context("failed to read module")?; + + self.c.parse_js( + fm, + self.options.target, + self.options.syntax, + self.options.is_module, + self.options.comments, + ) + }) + } + + fn complete( + self, + cx: TaskContext, + result: Result, + ) -> JsResult { + complete_parse(cx, result, &self.c) + } +} + +pub fn parse(mut cx: MethodContext) -> JsResult { + let src = cx.argument::(0)?; + let options_arg = cx.argument::(1)?; + let options: ParseOptions = neon_serde::from_value(&mut cx, options_arg)?; + let callback = cx.argument::(2)?; + + let this = cx.this(); + { + let guard = cx.lock(); + let c = this.borrow(&guard); + + let fm = c.cm.new_source_file(FileName::Anon, src.value()); + + ParseTask { + c: c.clone(), + fm, + options, + } + .schedule(callback); + }; + + Ok(cx.undefined().upcast()) +} + +pub fn parse_sync(mut cx: MethodContext) -> JsResult { + let c; + let this = cx.this(); + { + let guard = cx.lock(); + let compiler = this.borrow(&guard); + c = compiler.clone(); + } + c.run(|| { + let src = cx.argument::(0)?; + let options_arg = cx.argument::(1)?; + let options: ParseOptions = neon_serde::from_value(&mut cx, options_arg)?; + + let program = { + let fm = c.cm.new_source_file(FileName::Anon, src.value()); + c.parse_js( + fm, + options.target, + options.syntax, + options.is_module, + options.comments, + ) + }; + + complete_parse(cx, program, &c) + }) +} + +pub fn parse_file_sync(mut cx: MethodContext) -> JsResult { + let c; + let this = cx.this(); + { + let guard = cx.lock(); + let compiler = this.borrow(&guard); + c = compiler.clone(); + } + c.run(|| { + let path = cx.argument::(0)?; + let options_arg = cx.argument::(1)?; + let options: ParseOptions = neon_serde::from_value(&mut cx, options_arg)?; + + let program = { + let fm = + c.cm.load_file(Path::new(&path.value())) + .expect("failed to read program file"); + + c.parse_js( + fm, + options.target, + options.syntax, + options.is_module, + options.comments, + ) + }; + + complete_parse(cx, program, &c) + }) +} + +pub fn parse_file(mut cx: MethodContext) -> JsResult { + let path = cx.argument::(0)?; + let options_arg = cx.argument::(1)?; + let options: ParseOptions = neon_serde::from_value(&mut cx, options_arg)?; + let callback = cx.argument::(2)?; + + let this = cx.this(); + { + let guard = cx.lock(); + let c = this.borrow(&guard); + + ParseFileTask { + c: c.clone(), + path: path.value().into(), + options, + } + .schedule(callback); + }; + + Ok(cx.undefined().upcast()) +} diff --git a/native/src/print.rs b/native/src/print.rs new file mode 100644 index 00000000000..67d6e5c2892 --- /dev/null +++ b/native/src/print.rs @@ -0,0 +1,106 @@ +use crate::{complete_output, JsCompiler}; +use anyhow::Error; +use neon::prelude::*; +use std::sync::Arc; +use swc::{ + config::{Options, SourceMapsConfig}, + ecmascript::ast::Program, + Compiler, TransformOutput, +}; + +// ----- Printing ----- + +pub struct PrintTask { + pub c: Arc, + pub program: Program, + pub options: Options, +} + +impl Task for PrintTask { + type Output = TransformOutput; + type Error = Error; + type JsEvent = JsValue; + fn perform(&self) -> Result { + self.c.run(|| { + self.c.print( + &self.program, + self.options + .source_maps + .clone() + .unwrap_or(SourceMapsConfig::Bool(false)), + None, + self.options + .config + .clone() + .unwrap_or_default() + .minify + .unwrap_or(false), + ) + }) + } + + fn complete( + self, + cx: TaskContext, + result: Result, + ) -> JsResult { + complete_output(cx, result) + } +} + +pub fn print(mut cx: MethodContext) -> JsResult { + let program = cx.argument::(0)?; + let program: Program = + serde_json::from_str(&program.value()).expect("failed to deserialize Program"); + + let options = cx.argument::(1)?; + let options: Options = neon_serde::from_value(&mut cx, options)?; + + let callback = cx.argument::(2)?; + + let this = cx.this(); + { + let guard = cx.lock(); + let c = this.borrow(&guard); + + PrintTask { + c: c.clone(), + program, + options, + } + .schedule(callback) + } + + Ok(cx.undefined().upcast()) +} + +pub fn print_sync(mut cx: MethodContext) -> JsResult { + let c; + let this = cx.this(); + { + let guard = cx.lock(); + let compiler = this.borrow(&guard); + c = compiler.clone(); + } + c.run(|| { + let program = cx.argument::(0)?; + let program: Program = + serde_json::from_str(&program.value()).expect("failed to deserialize Program"); + + let options = cx.argument::(1)?; + let options: Options = neon_serde::from_value(&mut cx, options)?; + + let result = { + c.print( + &program, + options + .source_maps + .clone() + .unwrap_or(SourceMapsConfig::Bool(false)), + None, + options.config.unwrap_or_default().minify.unwrap_or(false), + ) + }; + complete_output(cx, result) + }) +} diff --git a/native/src/transform.rs b/native/src/transform.rs new file mode 100644 index 00000000000..356e2b5faac --- /dev/null +++ b/native/src/transform.rs @@ -0,0 +1,176 @@ +use crate::{complete_output, JsCompiler}; +use anyhow::{Context as _, Error}; +use neon::prelude::*; +use path_clean::clean; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; +use swc::{ + common::{FileName, SourceFile}, + config::Options, + ecmascript::ast::Program, + Compiler, TransformOutput, +}; +/// Input to transform +#[derive(Debug)] +pub enum Input { + /// json string + Program(String), + /// Raw source code. + Source(Arc), + /// File + File(PathBuf), +} + +pub struct TransformTask { + pub c: Arc, + pub input: Input, + pub options: Options, +} + +impl Task for TransformTask { + type Output = TransformOutput; + type Error = Error; + type JsEvent = JsValue; + + fn perform(&self) -> Result { + self.c.run(|| match self.input { + Input::Program(ref s) => { + let program: Program = + serde_json::from_str(&s).expect("failed to deserialize Program"); + // TODO: Source map + self.c.process_js(program, &self.options) + } + + Input::File(ref path) => { + let fm = self.c.cm.load_file(path).context("failed to read module")?; + self.c.process_js_file(fm, &self.options) + } + + Input::Source(ref s) => self.c.process_js_file(s.clone(), &self.options), + }) + } + + fn complete( + self, + cx: TaskContext, + result: Result, + ) -> JsResult { + complete_output(cx, result) + } +} + +/// returns `compiler, (src / path), options, plugin, callback` +pub fn schedule_transform(mut cx: MethodContext, op: F) -> JsResult +where + F: FnOnce(&Arc, String, bool, Options) -> TransformTask, +{ + let c; + let this = cx.this(); + { + let guard = cx.lock(); + c = this.borrow(&guard).clone(); + }; + + let s = cx.argument::(0)?.value(); + let is_module = cx.argument::(1)?; + let options_arg = cx.argument::(2)?; + + let options: Options = neon_serde::from_value(&mut cx, options_arg)?; + let callback = cx.argument::(3)?; + + let task = op(&c, s, is_module.value(), options); + task.schedule(callback); + + Ok(cx.undefined().upcast()) +} + +pub fn exec_transform(mut cx: MethodContext, op: F) -> JsResult +where + F: FnOnce(&Compiler, String, &Options) -> Result, Error>, +{ + let s = cx.argument::(0)?; + let is_module = cx.argument::(1)?; + let options: Options = match cx.argument_opt(2) { + Some(v) => neon_serde::from_value(&mut cx, v)?, + None => { + let obj = cx.empty_object().upcast(); + neon_serde::from_value(&mut cx, obj)? + } + }; + + let this = cx.this(); + let output = { + let guard = cx.lock(); + let c = this.borrow(&guard); + c.run(|| { + if is_module.value() { + let program: Program = + serde_json::from_str(&s.value()).expect("failed to deserialize Program"); + c.process_js(program, &options) + } else { + let fm = op(&c, s.value(), &options).expect("failed to create fm"); + c.process_js_file(fm, &options) + } + }) + }; + + complete_output(cx, output) +} + +pub fn transform(cx: MethodContext) -> JsResult { + schedule_transform(cx, |c, src, is_module, options| { + let input = if is_module { + Input::Program(src) + } else { + Input::Source(c.cm.new_source_file( + if options.filename.is_empty() { + FileName::Anon + } else { + FileName::Real(options.filename.clone().into()) + }, + src, + )) + }; + + TransformTask { + c: c.clone(), + input, + options, + } + }) +} + +pub fn transform_sync(cx: MethodContext) -> JsResult { + exec_transform(cx, |c, src, options| { + Ok(c.cm.new_source_file( + if options.filename.is_empty() { + FileName::Anon + } else { + FileName::Real(options.filename.clone().into()) + }, + src, + )) + }) +} + +pub fn transform_file(cx: MethodContext) -> JsResult { + schedule_transform(cx, |c, path, _, options| { + let path = clean(&path); + + TransformTask { + c: c.clone(), + input: Input::File(path.into()), + options, + } + }) +} + +pub fn transform_file_sync(cx: MethodContext) -> JsResult { + exec_transform(cx, |c, path, _| { + Ok(c.cm + .load_file(Path::new(&path)) + .expect("failed to load file")) + }) +}