esbuild_rs/
wrapper.rs

1use std::collections::HashMap;
2use std::fmt::{Display, Formatter};
3use std::os::raw::{c_char, c_void};
4use std::sync::Arc;
5use std::{convert, fmt, slice, str};
6
7use libc::{ptrdiff_t, size_t};
8
9use crate::bridge::{
10    get_allocation_pointer, FfiapiBuildOptions, FfiapiEngine, FfiapiEntryPoint,
11    FfiapiGoStringGoSlice, FfiapiLoader, FfiapiMapStringStringEntry, FfiapiTransformOptions,
12    GoString,
13};
14
15#[inline(always)]
16fn transform<I, S: IntoIterator<Item = I>, O, T: Fn(I) -> O>(src: S, mapper: T) -> Vec<O> {
17    src.into_iter().map(mapper).collect::<Vec<O>>()
18}
19
20// We wrap C arrays allocated from Go and sent to us in SliceContainer, such as `*ffiapi_message`.
21// This will own the memory, make it usable as a slice, and drop using the matching deallocator.
22pub struct SliceContainer<T> {
23    pub(crate) ptr: *mut T,
24    pub(crate) len: usize,
25}
26
27impl<T> SliceContainer<T> {
28    pub fn as_slice(&self) -> &[T] {
29        unsafe { slice::from_raw_parts(self.ptr, self.len) }
30    }
31}
32
33unsafe impl<T> Send for SliceContainer<T> {}
34
35unsafe impl<T> Sync for SliceContainer<T> {}
36
37impl<T> convert::AsRef<[T]> for SliceContainer<T> {
38    fn as_ref(&self) -> &[T] {
39        self.as_slice()
40    }
41}
42
43impl<T> Drop for SliceContainer<T> {
44    fn drop(&mut self) {
45        unsafe {
46            for i in 0..self.len {
47                drop(self.ptr.offset(i as isize));
48            }
49            // We pass `malloc` to Go as the allocator.
50            libc::free(self.ptr as *mut c_void);
51        };
52    }
53}
54
55// This is the ffiapi_string struct in C; we declare it here to avoid having to needlessly rewrap in StrContainer.
56// This will own the memory, make it usable as a str, and drop using the matching deallocator.
57#[repr(C)]
58pub struct StrContainer {
59    len: size_t,
60    data: *mut c_char,
61}
62
63impl StrContainer {
64    pub fn as_str(&self) -> &str {
65        unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.data as *mut u8, self.len)) }
66    }
67}
68
69unsafe impl Send for StrContainer {}
70
71unsafe impl Sync for StrContainer {}
72
73impl convert::AsRef<str> for StrContainer {
74    fn as_ref(&self) -> &str {
75        self.as_str()
76    }
77}
78
79impl Display for StrContainer {
80    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
81        self.as_ref().fmt(f)
82    }
83}
84
85impl Drop for StrContainer {
86    fn drop(&mut self) {
87        unsafe {
88            // We pass `malloc` to Go as the allocator.
89            libc::free(self.data as *mut c_void);
90        };
91    }
92}
93
94// This is the ffiapi_message struct in C; we declare it here to avoid having to needlessly rewrap in Message.
95#[repr(C)]
96pub struct Message {
97    pub file: StrContainer,
98    pub line: ptrdiff_t,
99    pub column: ptrdiff_t,
100    pub length: ptrdiff_t,
101    pub text: StrContainer,
102}
103
104impl Display for Message {
105    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
106        write!(
107            f,
108            "{} [{}:{}:{}]",
109            self.text.as_str(),
110            self.file.as_str(),
111            self.line,
112            self.column
113        )
114    }
115}
116
117// This is the ffiapi_output_file struct in C; we declare it here to avoid having to needlessly rewrap in OutputFile.
118#[repr(C)]
119pub struct OutputFile {
120    pub path: StrContainer,
121    pub data: StrContainer,
122}
123
124#[derive(Copy, Clone)]
125pub enum Charset {
126    Default,
127    ASCII,
128    UTF8,
129}
130
131#[derive(Copy, Clone)]
132pub enum EngineName {
133    Chrome,
134    Edge,
135    Firefox,
136    IOS,
137    Node,
138    Safari,
139}
140
141#[derive(Copy, Clone)]
142pub enum Format {
143    Default,
144    IIFE,
145    CommonJS,
146    ESModule,
147}
148
149#[derive(Copy, Clone)]
150pub enum JSXMode {
151    Transform,
152    Preserve,
153}
154
155#[derive(Copy, Clone)]
156pub enum LegalComments {
157    Default,
158    None,
159    Inline,
160    EndOfFile,
161    Linked,
162    External,
163}
164
165#[derive(Copy, Clone)]
166pub enum Loader {
167    None,
168    JS,
169    JSX,
170    TS,
171    TSX,
172    JSON,
173    Text,
174    Base64,
175    DataURL,
176    File,
177    Binary,
178    CSS,
179    Default,
180}
181
182#[derive(Copy, Clone)]
183pub enum Platform {
184    Browser,
185    Node,
186    Neutral,
187}
188
189#[derive(Copy, Clone)]
190pub enum SourceMap {
191    None,
192    Inline,
193    Linked,
194    External,
195    InlineAndExternal,
196}
197
198#[derive(Copy, Clone)]
199pub enum SourcesContent {
200    Include,
201    Exclude,
202}
203
204#[derive(Copy, Clone)]
205pub enum Target {
206    Default,
207    ESNext,
208    ES5,
209    ES2015,
210    ES2016,
211    ES2017,
212    ES2018,
213    ES2019,
214    ES2020,
215    ES2021,
216}
217
218#[derive(Copy, Clone)]
219pub enum TreeShaking {
220    Default,
221    False,
222    True,
223}
224
225#[derive(Clone)]
226pub struct Engine {
227    pub name: EngineName,
228    pub version: String,
229}
230
231#[derive(Clone)]
232pub struct EntryPoint {
233    pub input_path: String,
234    pub output_path: String,
235}
236
237// BuildOptions and TransformOptions are nice APIs that mimics official Go API and use standard Rust
238// types. They're similar to Ffiapi*Options, but we create a separate struct for ease of use, as
239// Ffiapi*Options uses raw pointers which are difficult to mutate, either directly or in
240// abstracted methods/helper functions.
241
242#[derive(Clone)]
243pub struct BuildOptionsBuilder {
244    pub source_map: SourceMap,
245    pub source_root: String,
246    pub sources_content: SourcesContent,
247
248    pub target: Target,
249    pub engines: Vec<Engine>,
250
251    pub minify_whitespace: bool,
252    pub minify_identifiers: bool,
253    pub minify_syntax: bool,
254    pub charset: Charset,
255    pub tree_shaking: TreeShaking,
256    pub ignore_annotations: bool,
257    pub legal_comments: LegalComments,
258
259    pub jsx_mode: JSXMode,
260    pub jsx_factory: String,
261    pub jsx_fragment: String,
262
263    pub define: HashMap<String, String>,
264    pub pure: Vec<String>,
265    pub keep_names: bool,
266
267    pub global_name: String,
268    pub bundle: bool,
269    pub preserve_symlinks: bool,
270    pub splitting: bool,
271    pub outfile: String,
272    pub metafile: bool,
273    pub outdir: String,
274    pub outbase: String,
275    pub abs_working_dir: String,
276    pub platform: Platform,
277    pub format: Format,
278    pub external: Vec<String>,
279    pub main_fields: Vec<String>,
280    pub conditions: Vec<String>,
281    pub loader: HashMap<String, Loader>,
282    pub resolve_extensions: Vec<String>,
283    pub tsconfig: String,
284    pub out_extensions: HashMap<String, String>,
285    pub public_path: String,
286    pub inject: Vec<String>,
287    pub banner: HashMap<String, String>,
288    pub footer: HashMap<String, String>,
289    pub node_paths: Vec<String>,
290
291    pub entry_names: String,
292    pub chunk_names: String,
293    pub asset_names: String,
294
295    pub entry_points: Vec<String>,
296    pub entry_points_advanced: Vec<EntryPoint>,
297
298    pub write: bool,
299    pub allow_overwrite: bool,
300    pub incremental: bool,
301}
302
303pub struct BuildOptions {
304    // We keep data that fields of ffiapi_ptr point to.
305    source_root: String,
306    engines: Vec<FfiapiEngine>,
307    jsx_factory: String,
308    jsx_fragment: String,
309    define: Vec<FfiapiMapStringStringEntry>,
310    pure: Vec<GoString>,
311    global_name: String,
312    outfile: String,
313    outdir: String,
314    outbase: String,
315    abs_working_dir: String,
316    external: Vec<GoString>,
317    main_fields: Vec<GoString>,
318    conditions: Vec<GoString>,
319    loader: Vec<FfiapiLoader>,
320    resolve_extensions: Vec<GoString>,
321    tsconfig: String,
322    out_extensions: Vec<FfiapiMapStringStringEntry>,
323    public_path: String,
324    inject: Vec<GoString>,
325    banner: Vec<FfiapiMapStringStringEntry>,
326    footer: Vec<FfiapiMapStringStringEntry>,
327    node_paths: Vec<GoString>,
328    entry_names: String,
329    chunk_names: String,
330    asset_names: String,
331    entry_points: Vec<GoString>,
332    entry_points_advanced: Vec<FfiapiEntryPoint>,
333    pub(crate) ffiapi_ptr: *const FfiapiBuildOptions,
334}
335
336unsafe impl Send for BuildOptions {}
337
338unsafe impl Sync for BuildOptions {}
339
340impl Drop for BuildOptions {
341    fn drop(&mut self) {
342        unsafe {
343            let _: Box<FfiapiBuildOptions> = Box::from_raw(self.ffiapi_ptr as _);
344        };
345    }
346}
347
348impl BuildOptionsBuilder {
349    pub fn new() -> BuildOptionsBuilder {
350        BuildOptionsBuilder {
351            source_map: SourceMap::None,
352            source_root: "".to_string(),
353            sources_content: SourcesContent::Include,
354            target: Target::Default,
355            engines: vec![],
356            minify_whitespace: false,
357            minify_identifiers: false,
358            minify_syntax: false,
359            charset: Charset::Default,
360            tree_shaking: TreeShaking::Default,
361            ignore_annotations: false,
362            legal_comments: LegalComments::Default,
363            jsx_mode: JSXMode::Transform,
364            jsx_factory: "".to_string(),
365            jsx_fragment: "".to_string(),
366            define: Default::default(),
367            pure: vec![],
368            keep_names: false,
369            global_name: "".to_string(),
370            bundle: false,
371            preserve_symlinks: false,
372            splitting: false,
373            outfile: "".to_string(),
374            metafile: false,
375            outdir: "".to_string(),
376            outbase: "".to_string(),
377            abs_working_dir: "".to_string(),
378            platform: Platform::Browser,
379            format: Format::Default,
380            external: vec![],
381            main_fields: vec![],
382            conditions: vec![],
383            loader: Default::default(),
384            resolve_extensions: vec![],
385            tsconfig: "".to_string(),
386            out_extensions: Default::default(),
387            public_path: "".to_string(),
388            inject: vec![],
389            banner: Default::default(),
390            footer: Default::default(),
391            node_paths: vec![],
392            entry_names: "".to_string(),
393            chunk_names: "".to_string(),
394            asset_names: "".to_string(),
395            entry_points: vec![],
396            entry_points_advanced: vec![],
397            write: false,
398            allow_overwrite: false,
399            incremental: false,
400        }
401    }
402
403    pub fn build(self) -> Arc<BuildOptions> {
404        let mut res = Arc::new(BuildOptions {
405            // We move into Arc first before creating pointers to data in it, as the move to the
406            // heap by Arc should change the data's location.
407            source_root: self.source_root,
408            engines: transform(self.engines, FfiapiEngine::from_engine),
409            jsx_factory: self.jsx_factory,
410            jsx_fragment: self.jsx_fragment,
411            define: transform(self.define, FfiapiMapStringStringEntry::from_map_entry),
412            pure: transform(self.pure, GoString::from_string),
413            global_name: self.global_name,
414            outfile: self.outfile,
415            outdir: self.outdir,
416            outbase: self.outbase,
417            abs_working_dir: self.abs_working_dir,
418            external: transform(self.external, GoString::from_string),
419            main_fields: transform(self.main_fields, GoString::from_string),
420            conditions: transform(self.conditions, GoString::from_string),
421            loader: transform(self.loader, FfiapiLoader::from_map_entry),
422            resolve_extensions: transform(self.resolve_extensions, GoString::from_string),
423            tsconfig: self.tsconfig,
424            out_extensions: transform(
425                self.out_extensions,
426                FfiapiMapStringStringEntry::from_map_entry,
427            ),
428            public_path: self.public_path,
429            inject: transform(self.inject, GoString::from_string),
430            banner: transform(self.banner, FfiapiMapStringStringEntry::from_map_entry),
431            footer: transform(self.footer, FfiapiMapStringStringEntry::from_map_entry),
432            node_paths: transform(self.node_paths, GoString::from_string),
433            entry_names: self.entry_names,
434            chunk_names: self.chunk_names,
435            asset_names: self.asset_names,
436            entry_points: transform(self.entry_points, GoString::from_string),
437            entry_points_advanced: transform(
438                self.entry_points_advanced,
439                FfiapiEntryPoint::from_entry_point,
440            ),
441            ffiapi_ptr: std::ptr::null(),
442        });
443
444        unsafe {
445            let ffiapi_ptr = Box::into_raw(Box::new(FfiapiBuildOptions {
446                source_map: self.source_map as u8,
447                source_root: GoString::from_bytes_unmanaged(res.source_root.as_bytes()),
448                sources_content: self.sources_content as u8,
449
450                target: self.target as u8,
451                engines: get_allocation_pointer(&res.engines),
452                engines_len: res.engines.len(),
453
454                minify_whitespace: self.minify_whitespace,
455                minify_identifiers: self.minify_identifiers,
456                minify_syntax: self.minify_syntax,
457                charset: self.charset as u8,
458                tree_shaking: self.tree_shaking as u8,
459                ignore_annotations: self.ignore_annotations,
460                legal_comments: self.legal_comments as u8,
461
462                jsx_mode: self.jsx_mode as u8,
463                jsx_factory: GoString::from_bytes_unmanaged(res.jsx_factory.as_bytes()),
464                jsx_fragment: GoString::from_bytes_unmanaged(res.jsx_fragment.as_bytes()),
465
466                define: get_allocation_pointer(&res.define),
467                define_len: res.define.len(),
468                pure: FfiapiGoStringGoSlice::from_vec_unamanged(&res.pure),
469                keep_names: self.keep_names,
470
471                global_name: GoString::from_bytes_unmanaged(res.global_name.as_bytes()),
472                bundle: self.bundle,
473                preserve_symlinks: self.preserve_symlinks,
474                splitting: self.splitting,
475                outfile: GoString::from_bytes_unmanaged(res.outfile.as_bytes()),
476                metafile: self.metafile,
477                outdir: GoString::from_bytes_unmanaged(res.outdir.as_bytes()),
478                outbase: GoString::from_bytes_unmanaged(res.outbase.as_bytes()),
479                abs_working_dir: GoString::from_bytes_unmanaged(res.abs_working_dir.as_bytes()),
480                platform: self.platform as u8,
481                format: self.format as u8,
482                external: FfiapiGoStringGoSlice::from_vec_unamanged(&res.external),
483                main_fields: FfiapiGoStringGoSlice::from_vec_unamanged(&res.main_fields),
484                conditions: FfiapiGoStringGoSlice::from_vec_unamanged(&res.conditions),
485                loader: get_allocation_pointer(&res.loader),
486                loader_len: res.loader.len(),
487                resolve_extensions: FfiapiGoStringGoSlice::from_vec_unamanged(
488                    &res.resolve_extensions,
489                ),
490                tsconfig: GoString::from_bytes_unmanaged(res.tsconfig.as_bytes()),
491                out_extensions: get_allocation_pointer(&res.out_extensions),
492                out_extensions_len: res.out_extensions.len(),
493                public_path: GoString::from_bytes_unmanaged(res.public_path.as_bytes()),
494                inject: FfiapiGoStringGoSlice::from_vec_unamanged(&res.inject),
495                banner: get_allocation_pointer(&res.banner),
496                banner_len: res.banner.len(),
497                footer: get_allocation_pointer(&res.footer),
498                footer_len: res.footer.len(),
499                node_paths: FfiapiGoStringGoSlice::from_vec_unamanged(&res.node_paths),
500
501                entry_names: GoString::from_bytes_unmanaged(res.entry_names.as_bytes()),
502                chunk_names: GoString::from_bytes_unmanaged(res.chunk_names.as_bytes()),
503                asset_names: GoString::from_bytes_unmanaged(res.asset_names.as_bytes()),
504
505                entry_points: FfiapiGoStringGoSlice::from_vec_unamanged(&res.entry_points),
506                entry_points_advanced: get_allocation_pointer(&res.entry_points_advanced),
507                entry_points_advanced_len: res.entry_points_advanced.len(),
508
509                write: self.write,
510                allow_overwrite: self.allow_overwrite,
511                incremental: self.incremental,
512            }));
513            Arc::get_mut(&mut res).unwrap().ffiapi_ptr = ffiapi_ptr;
514        };
515
516        res
517    }
518}
519
520pub struct BuildResult {
521    pub metafile: StrContainer,
522    pub output_files: SliceContainer<OutputFile>,
523    pub errors: SliceContainer<Message>,
524    pub warnings: SliceContainer<Message>,
525}
526
527#[derive(Clone)]
528pub struct TransformOptionsBuilder {
529    pub source_map: SourceMap,
530    pub source_root: String,
531    pub sources_content: SourcesContent,
532
533    pub target: Target,
534    pub format: Format,
535    pub global_name: String,
536    pub engines: Vec<Engine>,
537
538    pub minify_whitespace: bool,
539    pub minify_identifiers: bool,
540    pub minify_syntax: bool,
541    pub charset: Charset,
542    pub tree_shaking: TreeShaking,
543    pub ignore_annotations: bool,
544    pub legal_comments: LegalComments,
545
546    pub jsx_mode: JSXMode,
547    pub jsx_factory: String,
548    pub jsx_fragment: String,
549
550    pub tsconfig_raw: String,
551    pub footer: String,
552    pub banner: String,
553
554    pub define: HashMap<String, String>,
555    pub pure: Vec<String>,
556    pub keep_names: bool,
557
558    pub source_file: String,
559    pub loader: Loader,
560}
561
562pub struct TransformOptions {
563    // We keep data that fields of ffiapi_ptr point to.
564    source_root: String,
565    global_name: String,
566    engines: Vec<FfiapiEngine>,
567    jsx_factory: String,
568    jsx_fragment: String,
569    tsconfig_raw: String,
570    footer: String,
571    banner: String,
572    define: Vec<FfiapiMapStringStringEntry>,
573    pure: Vec<GoString>,
574    source_file: String,
575    pub(crate) ffiapi_ptr: *const FfiapiTransformOptions,
576}
577
578unsafe impl Send for TransformOptions {}
579
580unsafe impl Sync for TransformOptions {}
581
582impl Drop for TransformOptions {
583    fn drop(&mut self) {
584        unsafe {
585            let _: Box<FfiapiTransformOptions> = Box::from_raw(self.ffiapi_ptr as _);
586        };
587    }
588}
589
590impl TransformOptionsBuilder {
591    pub fn new() -> TransformOptionsBuilder {
592        TransformOptionsBuilder {
593            source_map: SourceMap::None,
594            source_root: "".to_string(),
595            sources_content: SourcesContent::Include,
596            target: Target::Default,
597            format: Format::Default,
598            global_name: "".to_string(),
599            engines: vec![],
600            minify_whitespace: false,
601            minify_identifiers: false,
602            minify_syntax: false,
603            charset: Charset::Default,
604            tree_shaking: TreeShaking::Default,
605            ignore_annotations: false,
606            legal_comments: LegalComments::Default,
607            jsx_mode: JSXMode::Transform,
608            jsx_factory: "".to_string(),
609            jsx_fragment: "".to_string(),
610            tsconfig_raw: "".to_string(),
611            footer: "".to_string(),
612            banner: "".to_string(),
613            define: Default::default(),
614            pure: vec![],
615            keep_names: false,
616            source_file: "".to_string(),
617            loader: Loader::None,
618        }
619    }
620
621    pub fn build(self) -> Arc<TransformOptions> {
622        let mut res = Arc::new(TransformOptions {
623            // We move into Arc first before creating pointers to data in it, as the move to the
624            // heap by Arc should change the data's location.
625            source_root: self.source_root,
626            global_name: self.global_name,
627            engines: transform(self.engines, FfiapiEngine::from_engine),
628            jsx_factory: self.jsx_factory,
629            jsx_fragment: self.jsx_fragment,
630            tsconfig_raw: self.tsconfig_raw,
631            footer: self.footer,
632            banner: self.banner,
633            define: transform(self.define, FfiapiMapStringStringEntry::from_map_entry),
634            pure: transform(self.pure, GoString::from_string),
635            source_file: self.source_file,
636            ffiapi_ptr: std::ptr::null(),
637        });
638
639        unsafe {
640            let ffiapi_ptr = Box::into_raw(Box::new(FfiapiTransformOptions {
641                source_map: self.source_map as u8,
642                source_root: GoString::from_bytes_unmanaged(res.source_root.as_bytes()),
643                sources_content: self.sources_content as u8,
644
645                target: self.target as u8,
646                format: self.format as u8,
647                global_name: GoString::from_bytes_unmanaged(res.global_name.as_bytes()),
648                engines: get_allocation_pointer(&res.engines),
649                engines_len: res.engines.len(),
650
651                minify_whitespace: self.minify_whitespace,
652                minify_identifiers: self.minify_identifiers,
653                minify_syntax: self.minify_syntax,
654                charset: self.charset as u8,
655                tree_shaking: self.tree_shaking as u8,
656                ignore_annotations: self.ignore_annotations,
657                legal_comments: self.legal_comments as u8,
658
659                jsx_mode: self.jsx_mode as u8,
660                jsx_factory: GoString::from_bytes_unmanaged(res.jsx_factory.as_bytes()),
661                jsx_fragment: GoString::from_bytes_unmanaged(res.jsx_fragment.as_bytes()),
662                tsconfig_raw: GoString::from_bytes_unmanaged(res.tsconfig_raw.as_bytes()),
663                footer: GoString::from_bytes_unmanaged(res.footer.as_bytes()),
664                banner: GoString::from_bytes_unmanaged(res.banner.as_bytes()),
665
666                define: get_allocation_pointer(&res.define),
667                define_len: res.define.len(),
668                pure: FfiapiGoStringGoSlice::from_vec_unamanged(&res.pure),
669                keep_names: self.keep_names,
670
671                source_file: GoString::from_bytes_unmanaged(res.source_file.as_bytes()),
672                loader: self.loader as u8,
673            }));
674            Arc::get_mut(&mut res).unwrap().ffiapi_ptr = ffiapi_ptr;
675        };
676
677        res
678    }
679}
680
681pub struct TransformResult {
682    pub code: StrContainer,
683    pub map: StrContainer,
684    pub errors: SliceContainer<Message>,
685    pub warnings: SliceContainer<Message>,
686}