wasm/
lib.rs

1use std::sync::Arc;
2
3use anyhow::{Context, Error};
4use js_sys::JsString;
5use next_custom_transforms::chain_transforms::{custom_before_pass, TransformOptions};
6use swc_core::{
7    base::{
8        config::{JsMinifyOptions, ParseOptions},
9        try_with_handler, Compiler,
10    },
11    common::{
12        comments::{Comments, SingleThreadedComments},
13        errors::ColorConfig,
14        FileName, FilePathMapping, Mark, SourceMap, GLOBALS,
15    },
16    ecma::ast::noop_pass,
17};
18use wasm_bindgen::prelude::*;
19use wasm_bindgen_futures::future_to_promise;
20
21pub mod mdx;
22
23fn convert_err(err: Error) -> JsValue {
24    format!("{err:?}").into()
25}
26
27#[wasm_bindgen(js_name = "minifySync")]
28pub fn minify_sync(s: JsString, opts: JsValue) -> Result<JsValue, JsValue> {
29    console_error_panic_hook::set_once();
30
31    let c = compiler();
32
33    let opts: JsMinifyOptions = serde_wasm_bindgen::from_value(opts)?;
34
35    let value = try_with_handler(
36        c.cm.clone(),
37        swc_core::base::HandlerOpts {
38            color: ColorConfig::Never,
39            skip_filename: false,
40        },
41        |handler| {
42            GLOBALS.set(&Default::default(), || {
43                let fm = c.cm.new_source_file(FileName::Anon.into(), s.into());
44                let program = c
45                    .minify(fm, handler, &opts, Default::default())
46                    .context("failed to minify file")?;
47
48                Ok(program)
49            })
50        },
51    )
52    .map_err(|e| e.to_pretty_error())
53    .map_err(convert_err)?;
54
55    Ok(serde_wasm_bindgen::to_value(&value)?)
56}
57
58#[wasm_bindgen(js_name = "minify")]
59pub fn minify(s: JsString, opts: JsValue) -> js_sys::Promise {
60    // TODO: This'll be properly scheduled once wasm have standard backed thread
61    // support.
62    future_to_promise(async { minify_sync(s, opts) })
63}
64
65#[wasm_bindgen(js_name = "transformSync")]
66pub fn transform_sync(s: JsValue, opts: JsValue) -> Result<JsValue, JsValue> {
67    console_error_panic_hook::set_once();
68
69    let c = compiler();
70    let mut opts: TransformOptions = serde_wasm_bindgen::from_value(opts)?;
71
72    let s = s.dyn_into::<js_sys::JsString>();
73    let out = try_with_handler(
74        c.cm.clone(),
75        swc_core::base::HandlerOpts {
76            color: ColorConfig::Never,
77            skip_filename: false,
78        },
79        |handler| {
80            GLOBALS.set(&Default::default(), || {
81                let unresolved_mark = Mark::new();
82                opts.swc.unresolved_mark = Some(unresolved_mark);
83
84                let out = match s {
85                    Ok(s) => {
86                        let fm = c.cm.new_source_file(
87                            if opts.swc.filename.is_empty() {
88                                FileName::Anon.into()
89                            } else {
90                                FileName::Real(opts.swc.filename.clone().into()).into()
91                            },
92                            s.into(),
93                        );
94                        let cm = c.cm.clone();
95                        let file = fm.clone();
96                        let comments = SingleThreadedComments::default();
97                        c.process_js_with_custom_pass(
98                            fm,
99                            None,
100                            handler,
101                            &opts.swc,
102                            comments.clone(),
103                            |_| {
104                                custom_before_pass(
105                                    cm,
106                                    file,
107                                    &opts,
108                                    comments.clone(),
109                                    Default::default(),
110                                    unresolved_mark,
111                                    Default::default(),
112                                )
113                            },
114                            |_| noop_pass(),
115                        )
116                        .context("failed to process js file")?
117                    }
118                    Err(v) => c.process_js(
119                        handler,
120                        serde_wasm_bindgen::from_value(v).expect(""),
121                        &opts.swc,
122                    )?,
123                };
124
125                Ok(out)
126            })
127        },
128    )
129    .map_err(|e| e.to_pretty_error())
130    .map_err(convert_err)?;
131
132    Ok(serde_wasm_bindgen::to_value(&out)?)
133}
134
135#[wasm_bindgen(js_name = "transform")]
136pub fn transform(s: JsValue, opts: JsValue) -> js_sys::Promise {
137    // TODO: This'll be properly scheduled once wasm have standard backed thread
138    // support.
139    future_to_promise(async { transform_sync(s, opts) })
140}
141
142#[wasm_bindgen(js_name = "parseSync")]
143pub fn parse_sync(s: JsString, opts: JsValue) -> Result<JsValue, JsValue> {
144    console_error_panic_hook::set_once();
145
146    let c = swc_core::base::Compiler::new(Arc::new(SourceMap::new(FilePathMapping::empty())));
147    let opts: ParseOptions = serde_wasm_bindgen::from_value(opts)?;
148
149    try_with_handler(
150        c.cm.clone(),
151        swc_core::base::HandlerOpts {
152            ..Default::default()
153        },
154        |handler| {
155            c.run(|| {
156                GLOBALS.set(&Default::default(), || {
157                    let fm = c.cm.new_source_file(FileName::Anon.into(), s.into());
158
159                    let cmts = c.comments().clone();
160                    let comments = if opts.comments {
161                        Some(&cmts as &dyn Comments)
162                    } else {
163                        None
164                    };
165
166                    let program = c
167                        .parse_js(
168                            fm,
169                            handler,
170                            opts.target,
171                            opts.syntax,
172                            opts.is_module,
173                            comments,
174                        )
175                        .context("failed to parse code")?;
176
177                    let s = serde_json::to_string(&program).unwrap();
178                    Ok(JsValue::from_str(&s))
179                })
180            })
181        },
182    )
183    .map_err(|e| e.to_pretty_error())
184    .map_err(convert_err)
185}
186
187#[wasm_bindgen(js_name = "parse")]
188pub fn parse(s: JsString, opts: JsValue) -> js_sys::Promise {
189    // TODO: This'll be properly scheduled once wasm have standard backed thread
190    // support.
191    future_to_promise(async { parse_sync(s, opts) })
192}
193
194/// Get global sourcemap
195fn compiler() -> Arc<Compiler> {
196    let cm = Arc::new(SourceMap::new(FilePathMapping::empty()));
197
198    Arc::new(Compiler::new(cm))
199}