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