1use std::{
2 io::Write,
3 path::{Path, PathBuf},
4 process::{Child, Command, Stdio},
5 sync::Arc,
6};
7
8use anyhow::{bail, Context, Result};
9use flate2::{write::ZlibEncoder, Compression};
10use swc_common::{comments::SingleThreadedComments, errors::HANDLER, Mark, SourceFile, SourceMap};
11use swc_ecma_ast::{EsVersion, Module};
12use swc_ecma_codegen::text_writer::{omit_trailing_semi, JsWriter, WriteJs};
13use swc_ecma_parser::{parse_file_as_module, Syntax};
14use swc_ecma_transforms_base::resolver;
15use swc_ecma_visit::VisitMutWith;
16
17pub mod minifier;
18
19pub fn wrap_task<T, F>(op: F) -> Result<T>
21where
22 F: FnOnce() -> Result<T>,
23{
24 op()
25}
26
27pub fn gzipped_size(code: &str) -> usize {
28 let mut e = ZlibEncoder::new(Vec::new(), Compression::new(9));
29 e.write_all(code.as_bytes()).unwrap();
30 let compressed_bytes = e.finish().unwrap();
31 compressed_bytes.len()
32}
33
34pub fn make_pretty(f: &Path) -> Result<()> {
35 let mut c = Command::new("npx");
36 c.stderr(Stdio::inherit());
37 c.arg("js-beautify").arg("--replace").arg(f);
38
39 let output = c.output().context("failed to run prettier")?;
40
41 if !output.status.success() {
42 bail!("prettier failed");
43 }
44
45 Ok(())
46}
47
48pub fn parse_js(fm: Arc<SourceFile>) -> Result<ModuleRecord> {
49 let unresolved_mark = Mark::new();
50 let top_level_mark = Mark::new();
51
52 let mut errors = Vec::new();
53 let comments = SingleThreadedComments::default();
54 let res = parse_file_as_module(
55 &fm,
56 Syntax::Es(Default::default()),
57 EsVersion::latest(),
58 Some(&comments),
59 &mut errors,
60 )
61 .map_err(|err| HANDLER.with(|handler| err.into_diagnostic(handler).emit()));
62
63 for err in errors {
64 HANDLER.with(|handler| err.into_diagnostic(handler).emit());
65 }
66
67 let mut m = match res {
68 Ok(v) => v,
69 Err(()) => bail!("failed to parse a js file as a module"),
70 };
71
72 m.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
73
74 Ok(ModuleRecord {
75 module: m,
76 comments,
77 top_level_mark,
78 unresolved_mark,
79 })
80}
81
82pub fn print_js(cm: Arc<SourceMap>, m: &Module, minify: bool) -> Result<String> {
83 let mut buf = Vec::new();
84
85 {
86 let mut wr = Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None)) as Box<dyn WriteJs>;
87 if minify {
88 wr = Box::new(omit_trailing_semi(wr));
89 }
90
91 let mut e = swc_ecma_codegen::Emitter {
92 cfg: swc_ecma_codegen::Config::default().with_minify(true),
93 cm,
94 comments: None,
95 wr,
96 };
97
98 e.emit_module(m).unwrap();
99 }
100
101 String::from_utf8(buf).context("swc emitted non-utf8 output")
102}
103
104#[derive(Debug, Clone)]
105pub struct ModuleRecord {
106 pub module: Module,
107 pub comments: SingleThreadedComments,
108 pub top_level_mark: Mark,
109 pub unresolved_mark: Mark,
110}
111
112pub fn all_js_files(path: &Path) -> Result<Vec<PathBuf>> {
113 wrap_task(|| {
114 if path.is_dir() {
115 let mut files = Vec::new();
116 for entry in path.read_dir().context("failed to read dir")? {
117 let entry = entry.context("read_dir returned an error")?;
118 let path = entry.path();
119 files.extend(all_js_files(&path)?);
120 }
121 Ok(files)
122 } else if path.extension() == Some("js".as_ref()) {
123 Ok(vec![path.to_path_buf()])
124 } else {
125 Ok(Vec::new())
126 }
127 })
128 .with_context(|| format!("failed to get list of `.js` files in {}", path.display()))
129}
130
131pub(crate) struct ChildGuard(pub Child);
132
133impl Drop for ChildGuard {
134 fn drop(&mut self) {
135 if let Err(e) = self.0.kill() {
136 eprintln!("Could not kill child process: {e}")
137 }
138 }
139}