esbuild_rs/api/
transform.rs

1use std::future::Future;
2use std::os::raw::c_void;
3use std::pin::Pin;
4use std::sync::{Arc, Mutex};
5use std::task::{Context, Poll, Waker};
6
7use libc::size_t;
8
9use crate::bridge::{GoString, GoTransform};
10use crate::wrapper::{Message, SliceContainer, StrContainer, TransformOptions, TransformResult};
11
12struct TransformInvocationData {
13    src_vec_arc_raw: Option<*const Vec<u8>>,
14    opt_arc_raw: Option<*const TransformOptions>,
15    cb_trait_ptr: *mut c_void,
16}
17
18extern "C" fn transform_callback(
19    raw_cb_data: *mut c_void,
20    code: StrContainer,
21    map: StrContainer,
22    raw_errors: *mut Message,
23    errors_len: size_t,
24    raw_warnings: *mut Message,
25    warnings_len: size_t,
26) -> () {
27    unsafe {
28        let cb_data: Box<TransformInvocationData> = Box::from_raw(raw_cb_data as *mut _);
29
30        // Drop source code refcount.
31        if let Some(ptr) = cb_data.src_vec_arc_raw {
32            let _: Arc<Vec<u8>> = Arc::from_raw(ptr);
33        };
34
35        // Drop options refcount.
36        if let Some(ptr) = cb_data.opt_arc_raw {
37            let _: Arc<TransformOptions> = Arc::from_raw(ptr);
38        };
39
40        let rust_cb_trait_box: Box<Box<dyn FnOnce(TransformResult)>> =
41            Box::from_raw(cb_data.cb_trait_ptr as *mut _);
42
43        let errors = SliceContainer {
44            ptr: raw_errors,
45            len: errors_len,
46        };
47        let warnings = SliceContainer {
48            ptr: raw_warnings,
49            len: warnings_len,
50        };
51
52        rust_cb_trait_box(TransformResult {
53            code,
54            map,
55            errors,
56            warnings,
57        });
58    };
59}
60
61unsafe fn call_ffi_transform(
62    cb_data: *mut TransformInvocationData,
63    go_code: GoString,
64    options: &TransformOptions,
65) -> () {
66    #[cfg(target_env = "msvc")]
67    #[allow(non_snake_case)]
68    let GoTransform =
69        std::mem::transmute::<_, GoTransform>(crate::bridge::DLL.get_function("GoTransform"));
70
71    // We can safely convert anything in TransformOptions into raw pointers, as the memory is managed the the Arc and we only used owned values.
72    GoTransform(
73        libc::malloc,
74        transform_callback,
75        cb_data as *mut c_void,
76        go_code,
77        options.ffiapi_ptr,
78    );
79}
80
81pub unsafe fn transform_direct_unmanaged<F>(code: &[u8], options: &TransformOptions, cb: F) -> ()
82where
83    F: FnOnce(TransformResult),
84{
85    // Prepare code.
86    let go_code = GoString::from_bytes_unmanaged(code);
87
88    // Prepare callback.
89    let cb_box = Box::new(cb) as Box<dyn FnOnce(TransformResult)>;
90    let cb_trait_box = Box::new(cb_box);
91    let cb_trait_ptr = Box::into_raw(cb_trait_box);
92
93    let data = Box::into_raw(Box::new(TransformInvocationData {
94        src_vec_arc_raw: None,
95        opt_arc_raw: None,
96        cb_trait_ptr: cb_trait_ptr as *mut c_void,
97    }));
98
99    call_ffi_transform(data, go_code, options);
100}
101
102/// This function transforms a string of source code into JavaScript. It can be used to minify
103/// JavaScript, convert TypeScript/JSX to JavaScript, or convert newer JavaScript to older
104/// JavaScript. The available options roughly correspond to esbuild's command-line flags.
105///
106/// The equivalent Go function will be called via Cgo, which will run the API from a goroutine. This
107/// means that this function will return immediately, and `cb` will be called sometime in the future
108/// once the goroutine completes. Additional concurrency management may be necessary to keep the
109/// Rust program alive until all calls to this function actually complete.
110///
111/// # Arguments
112///
113/// * `code` - Source code to transform. Must be UTF-8. A reference will be held on the Arc until
114///   the callback is asynchronously called from Go.
115/// * `options` - Built TransformOptions created from a TransformOptionsBuilder. A reference will be
116///   held on the Arc until the callback is asynchronously called from Go.
117/// * `cb` - Closure to call once the goroutine completes with the TransformResult.
118///
119/// # Examples
120///
121/// This example uses the [crossbeam](https://docs.rs/crossbeam/) crate to prevent Rust from exiting
122/// until the transform completes.
123///
124/// ```
125/// use std::sync::Arc;
126/// use crossbeam::sync::WaitGroup;
127/// use esbuild_rs::{TransformOptionsBuilder, transform_direct, TransformResult};
128///
129/// fn main() {
130///   let src = Arc::new(b"let x = NAME;".to_vec());
131///
132///   let mut options_builder = TransformOptionsBuilder::new();
133///   options_builder.define.insert("NAME".to_string(), "world".to_string());
134///   let options = options_builder.build();
135///
136///   let wg = WaitGroup::new();
137///   let task = wg.clone();
138///   transform_direct(src, options, |TransformResult { code, map, errors, warnings }| {
139///     assert_eq!(code.as_str(), "let x = world;\n");
140///     drop(task);
141///   });
142///   wg.wait();
143/// }
144/// ```
145pub fn transform_direct<F>(code: Arc<Vec<u8>>, options: Arc<TransformOptions>, cb: F) -> ()
146where
147    F: FnOnce(TransformResult),
148    F: Send + 'static,
149{
150    // Prepare code.
151    let go_code = unsafe { GoString::from_bytes_unmanaged(&code) };
152
153    // Prepare callback.
154    let cb_box = Box::new(cb) as Box<dyn FnOnce(TransformResult)>;
155    let cb_trait_box = Box::new(cb_box);
156    let cb_trait_ptr = Box::into_raw(cb_trait_box);
157
158    let data = Box::into_raw(Box::new(TransformInvocationData {
159        src_vec_arc_raw: Some(Arc::into_raw(code.clone())),
160        opt_arc_raw: Some(Arc::into_raw(options.clone())),
161        cb_trait_ptr: cb_trait_ptr as *mut c_void,
162    }));
163
164    unsafe {
165        call_ffi_transform(data, go_code, options.as_ref());
166    };
167}
168
169struct TransformFutureState {
170    result: Option<TransformResult>,
171    waker: Option<Waker>,
172}
173
174pub struct TransformFuture {
175    state: Arc<Mutex<TransformFutureState>>,
176}
177
178/// Future wrapper for `transform_direct`.
179///
180/// # Arguments
181///
182/// * `code` - Source code to transform. Must be UTF-8. A reference will be held on the Arc until
183///   the Future completes.
184/// * `options` - Built TransformOptions created from a TransformOptionsBuilder. A reference will be
185///   held on the Arc until the Future completes.
186///
187/// # Examples
188///
189/// This example uses the [async-std](https://crates.io/crates/async-std) async runtime.
190///
191/// ```
192/// use std::sync::Arc;
193/// use async_std::task;
194/// use esbuild_rs::{TransformOptionsBuilder, transform, TransformResult};
195///
196/// fn main() {
197///   let src = Arc::new(b"let x = NAME;".to_vec());
198///
199///   let mut options_builder = TransformOptionsBuilder::new();
200///   options_builder.define.insert("NAME".to_string(), "world".to_string());
201///   let options = options_builder.build();
202///
203///   let res = task::block_on(transform(src, options));
204///   assert_eq!(res.code.as_str(), "let x = world;\n");
205/// }
206/// ```
207pub fn transform(code: Arc<Vec<u8>>, options: Arc<TransformOptions>) -> TransformFuture {
208    let state = Arc::new(Mutex::new(TransformFutureState {
209        result: None,
210        waker: None,
211    }));
212    let state_cb_copy = state.clone();
213    transform_direct(code, options, move |result| {
214        let mut state = state_cb_copy.lock().unwrap();
215        state.result = Some(result);
216        if let Some(waker) = state.waker.take() {
217            waker.wake();
218        };
219    });
220    TransformFuture { state }
221}
222
223impl Future for TransformFuture {
224    type Output = TransformResult;
225
226    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
227        let mut state = self.state.lock().unwrap();
228        match state.result.take() {
229            Some(result) => Poll::Ready(result),
230            None => {
231                state.waker = Some(cx.waker().clone());
232                Poll::Pending
233            }
234        }
235    }
236}