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