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 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 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 future_to_promise(async { parse_sync(s, opts) })
192}
193
194fn compiler() -> Arc<Compiler> {
196 let cm = Arc::new(SourceMap::new(FilePathMapping::empty()));
197
198 Arc::new(Compiler::new(cm))
199}