1use std::{
2 path::Path,
3 process::{Command, Stdio},
4 sync::Arc,
5};
6
7use anyhow::{bail, Context, Result};
8use swc_common::{FileName, SourceMap};
9use swc_ecma_ast::*;
10use swc_ecma_minifier::option::{CompressOptions, MangleOptions, MinifyOptions};
11use swc_ecma_transforms_base::fixer::fixer;
12use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
13
14use super::{parse_js, print_js, wrap_task, ModuleRecord};
15
16pub fn get_minified(
17 cm: Arc<SourceMap>,
18 file: &Path,
19 compress: bool,
20 mangle: bool,
21) -> Result<ModuleRecord> {
22 get_minified_with_opts(
23 cm,
24 file,
25 if compress {
26 Some(Default::default())
27 } else {
28 None
29 },
30 if mangle {
31 Some(Default::default())
32 } else {
33 None
34 },
35 )
36}
37
38pub fn get_minified_with_opts(
39 cm: Arc<SourceMap>,
40 file: &Path,
41 compress: Option<CompressOptions>,
42 mangle: Option<MangleOptions>,
43) -> Result<ModuleRecord> {
44 let fm = cm.load_file(file)?;
45
46 let m = parse_js(fm)?;
47
48 let mut module = {
49 swc_ecma_minifier::optimize(
50 m.module.into(),
51 cm,
52 Some(&m.comments),
53 None,
54 &MinifyOptions {
55 compress,
56 mangle,
57 ..Default::default()
58 },
59 &swc_ecma_minifier::option::ExtraOptions {
60 unresolved_mark: m.unresolved_mark,
61 top_level_mark: m.top_level_mark,
62 mangle_name_cache: None,
63 },
64 )
65 .expect_module()
66 };
67
68 module.visit_mut_with(&mut Normalizer {});
69 module.visit_mut_with(&mut fixer(None));
70
71 Ok(ModuleRecord { module, ..m })
72}
73
74pub fn get_terser_output(file: &Path, compress: bool, mangle: bool) -> Result<String> {
75 wrap_task(|| {
76 let mut cmd = Command::new("npx");
77 cmd.arg("terser");
78 cmd.stderr(Stdio::inherit());
79
80 if compress {
81 cmd.arg("--compress");
82 }
83 if mangle {
84 cmd.arg("--mangle");
85 }
86 cmd.args(["--comments", "false"]);
87 cmd.arg("--");
88 cmd.arg(file);
89
90 let output = cmd.output().context("failed to get output")?;
91
92 if !output.status.success() {
93 bail!("failed to run terser");
94 }
95
96 let output = String::from_utf8(output.stdout).context("terser emitted non-utf8 string")?;
97
98 let cm = Arc::new(SourceMap::default());
100 let fm = cm.new_source_file(FileName::Anon.into(), output);
101 let m = parse_js(fm)?;
102
103 let code = print_js(cm, &m.module, true)?;
104
105 Ok(code)
106 })
107 .with_context(|| format!("failed to get output of {} from terser", file.display()))
108}
109
110pub fn get_esbuild_output(file: &Path, mangle: bool) -> Result<String> {
111 wrap_task(|| {
112 let mut cmd = Command::new("esbuild");
113 cmd.stderr(Stdio::inherit());
114
115 cmd.arg(file);
116
117 if mangle {
118 cmd.arg("--minify");
119 } else {
120 cmd.arg("--minify-syntax").arg("--minify-whitespace");
121 }
122
123 let output = cmd.output().context("failed to get output")?;
124
125 if !output.status.success() {
126 bail!("failed to run esbuild");
127 }
128
129 String::from_utf8(output.stdout).context("esbuild emitted non-utf8 string")
130 })
131 .with_context(|| format!("failed to get output of {} from esbuild", file.display()))
132}
133
134struct Normalizer {}
138
139impl VisitMut for Normalizer {
140 noop_visit_mut_type!(fail);
141
142 fn visit_mut_prop(&mut self, p: &mut Prop) {
143 p.visit_mut_children_with(self);
144
145 }
156}