dbg_swc/es/minifier/
reduce.rs1use std::{
2 env::current_exe,
3 fs::{self, create_dir_all, read_to_string},
4 path::{Path, PathBuf},
5 process::Command,
6 sync::Arc,
7};
8
9use anyhow::{Context, Result};
10use clap::{ArgEnum, Args};
11use par_iter::prelude::*;
12use sha1::{Digest, Sha1};
13use swc_common::{SourceMap, GLOBALS};
14use tempfile::TempDir;
15
16use crate::{
17 util::{all_js_files, parse_js, print_js, ChildGuard},
18 CREDUCE_INPUT_ENV_VAR, CREDUCE_MODE_ENV_VAR,
19};
20
21#[derive(Debug, Args)]
37pub struct ReduceCommand {
38 pub path: PathBuf,
41
42 #[clap(long, arg_enum)]
49 pub mode: ReduceMode,
50
51 #[clap(long)]
54 pub remove: bool,
55}
56
57#[derive(Debug, Clone, Copy, ArgEnum)]
58pub enum ReduceMode {
59 Size,
60 Semantics,
61}
62
63impl ReduceCommand {
64 pub fn run(self, cm: Arc<SourceMap>) -> Result<()> {
65 let js_files = all_js_files(&self.path)?;
66
67 GLOBALS.with(|globals| {
68 js_files
69 .into_par_iter()
70 .map(|path| GLOBALS.set(globals, || self.reduce_file(cm.clone(), &path)))
71 .collect::<Result<Vec<_>>>()
72 })?;
73
74 Ok(())
75 }
76
77 fn reduce_file(&self, cm: Arc<SourceMap>, src_path: &Path) -> Result<()> {
78 let fm = cm.load_file(src_path).context("failed to prepare file")?;
81 let m = parse_js(fm)?;
82 let code = print_js(cm, &m.module, false)?;
83
84 fs::write(src_path, code.as_bytes()).context("failed to strip comments")?;
85
86 let dir = TempDir::new().context("failed to create a temp directory")?;
88
89 let input = dir.path().join("input.js");
90
91 fs::copy(src_path, &input).context("failed to copy")?;
92
93 let mut c = Command::new("creduce");
94
95 c.arg("--not-c");
96
97 c.env(
98 CREDUCE_MODE_ENV_VAR,
99 match self.mode {
100 ReduceMode::Size => "SIZE",
101 ReduceMode::Semantics => "SEMANTICS",
102 },
103 );
104 c.env(CREDUCE_INPUT_ENV_VAR, &input);
105
106 let exe = current_exe()?;
107 c.arg(&exe);
108 c.arg(&input);
109 let mut child = ChildGuard(c.spawn().context("failed to run creduce")?);
110 let status = child.0.wait().context("failed to wait for creduce")?;
111
112 if status.success() {
113 move_to_data_dir(&input)?;
114 }
115
116 if let Some(1) = status.code() {
117 if self.remove {
118 fs::remove_file(src_path).context("failed to remove")?;
119 }
120 } else {
121 dbg!(&status, status.code());
122 }
123
124 Ok(())
125 }
126}
127
128fn move_to_data_dir(input_path: &Path) -> Result<PathBuf> {
129 let src = read_to_string(input_path).context("failed to read input file")?;
130
131 let mut hasher = Sha1::new();
133
134 hasher.update(src.as_bytes());
136
137 let result = hasher.finalize();
140 let hash_str = format!("{result:x}");
141
142 create_dir_all(format!(".swc-reduce/{hash_str}")).context("failed to create `.data`")?;
143
144 let to = PathBuf::from(format!(".swc-reduce/{hash_str}/input.js"));
145 fs::write(&to, src.as_bytes()).context("failed to write")?;
146
147 Ok(to)
148}