dbg_swc/util/
minifier.rs

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        // Drop comments
99        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
134/// We target es5 while esbuild does not support it.
135///
136/// Due to the difference, reducer generates something useless
137struct 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        // if let Prop::KeyValue(kv) = p {
146        //     if let PropName::Ident(k) = &kv.key {
147        //         match &*kv.value {
148        //             Expr::Ident(value) if k.sym == value.sym => {
149        //                 *p = Prop::Shorthand(k.clone());
150        //             }
151        //             _ => {}
152        //         }
153        //     }
154        // }
155    }
156}