|
1 | 1 | use std::sync::Arc; |
2 | 2 |
|
3 | | -use anyhow::anyhow; |
4 | 3 | use rspack_core::ModuleType; |
5 | | -use rspack_error::{Error, Result}; |
| 4 | +use rspack_error::{internal_error, DiagnosticKind, Error, Result, TraceableError}; |
6 | 5 | use swc_core::{ |
7 | 6 | base::{ |
8 | 7 | config::{IsModule, JsMinifyCommentOption, JsMinifyOptions, SourceMapsConfig}, |
9 | | - try_with_handler, BoolOr, TransformOutput, |
| 8 | + BoolOr, TransformOutput, |
10 | 9 | }, |
11 | 10 | common::{ |
12 | 11 | collections::AHashMap, |
13 | 12 | comments::{Comment, Comments, SingleThreadedComments}, |
14 | | - errors::HANDLER, |
| 13 | + errors::{Emitter, Handler, HANDLER}, |
15 | 14 | BytePos, FileName, Mark, SourceMap, GLOBALS, |
16 | 15 | }, |
17 | 16 | ecma::{ |
@@ -39,156 +38,159 @@ use crate::utils::ecma_parse_error_to_rspack_error; |
39 | 38 | pub fn minify(opts: &JsMinifyOptions, input: String, filename: &str) -> Result<TransformOutput> { |
40 | 39 | let cm: Arc<SourceMap> = Default::default(); |
41 | 40 | GLOBALS.set(&Default::default(), || -> Result<TransformOutput> { |
42 | | - let ret = try_with_handler(cm.clone(), Default::default(), |handler| { |
43 | | - let fm = cm.new_source_file(FileName::Custom(filename.to_string()), input); |
44 | | - let target = opts.ecma.clone().into(); |
45 | | - |
46 | | - let (source_map, _) = opts |
47 | | - .source_map |
48 | | - .as_ref() |
49 | | - .map(|obj| -> std::result::Result<_, anyhow::Error> { |
50 | | - let orig = obj |
51 | | - .content |
52 | | - .as_ref() |
53 | | - .map(|s| sourcemap::SourceMap::from_slice(s.as_bytes())); |
54 | | - let orig = match orig { |
55 | | - Some(v) => Some(v?), |
56 | | - None => None, |
57 | | - }; |
58 | | - Ok((SourceMapsConfig::Bool(true), orig)) |
59 | | - }) |
60 | | - .unwrap_as_option(|v| { |
61 | | - Some(Ok(match v { |
62 | | - Some(true) => (SourceMapsConfig::Bool(true), None), |
63 | | - _ => (SourceMapsConfig::Bool(false), None), |
64 | | - })) |
65 | | - }) |
66 | | - .expect("TODO:")?; |
67 | | - |
68 | | - let mut min_opts = MinifyOptions { |
69 | | - compress: opts |
70 | | - .compress |
71 | | - .clone() |
72 | | - .unwrap_as_option(|default| match default { |
73 | | - Some(true) | None => Some(Default::default()), |
74 | | - _ => None, |
| 41 | + with_rspack_error_handler( |
| 42 | + "Minify Error".to_string(), |
| 43 | + DiagnosticKind::JavaScript, |
| 44 | + cm.clone(), |
| 45 | + |handler| { |
| 46 | + let fm = cm.new_source_file(FileName::Custom(filename.to_string()), input); |
| 47 | + let target = opts.ecma.clone().into(); |
| 48 | + |
| 49 | + let (source_map, _) = opts |
| 50 | + .source_map |
| 51 | + .as_ref() |
| 52 | + .map(|obj| -> std::result::Result<_, anyhow::Error> { |
| 53 | + let orig = obj |
| 54 | + .content |
| 55 | + .as_ref() |
| 56 | + .map(|s| sourcemap::SourceMap::from_slice(s.as_bytes())); |
| 57 | + let orig = match orig { |
| 58 | + Some(v) => Some(v?), |
| 59 | + None => None, |
| 60 | + }; |
| 61 | + Ok((SourceMapsConfig::Bool(true), orig)) |
75 | 62 | }) |
76 | | - .map(|v| v.into_config(cm.clone())), |
77 | | - mangle: opts |
78 | | - .mangle |
79 | | - .clone() |
80 | | - .unwrap_as_option(|default| match default { |
81 | | - Some(true) | None => Some(Default::default()), |
82 | | - _ => None, |
83 | | - }), |
84 | | - ..Default::default() |
85 | | - }; |
| 63 | + .unwrap_as_option(|v| { |
| 64 | + Some(Ok(match v { |
| 65 | + Some(true) => (SourceMapsConfig::Bool(true), None), |
| 66 | + _ => (SourceMapsConfig::Bool(false), None), |
| 67 | + })) |
| 68 | + }) |
| 69 | + .expect("TODO:")?; |
| 70 | + |
| 71 | + let mut min_opts = MinifyOptions { |
| 72 | + compress: opts |
| 73 | + .compress |
| 74 | + .clone() |
| 75 | + .unwrap_as_option(|default| match default { |
| 76 | + Some(true) | None => Some(Default::default()), |
| 77 | + _ => None, |
| 78 | + }) |
| 79 | + .map(|v| v.into_config(cm.clone())), |
| 80 | + mangle: opts |
| 81 | + .mangle |
| 82 | + .clone() |
| 83 | + .unwrap_as_option(|default| match default { |
| 84 | + Some(true) | None => Some(Default::default()), |
| 85 | + _ => None, |
| 86 | + }), |
| 87 | + ..Default::default() |
| 88 | + }; |
86 | 89 |
|
87 | | - // top_level defaults to true if module is true |
| 90 | + // top_level defaults to true if module is true |
88 | 91 |
|
89 | | - // https://github.com/swc-project/swc/issues/2254 |
| 92 | + // https://github.com/swc-project/swc/issues/2254 |
90 | 93 |
|
91 | | - if opts.module { |
92 | | - if let Some(opts) = &mut min_opts.compress { |
93 | | - if opts.top_level.is_none() { |
94 | | - opts.top_level = Some(TopLevelOptions { functions: true }); |
| 94 | + if opts.module { |
| 95 | + if let Some(opts) = &mut min_opts.compress { |
| 96 | + if opts.top_level.is_none() { |
| 97 | + opts.top_level = Some(TopLevelOptions { functions: true }); |
| 98 | + } |
95 | 99 | } |
96 | | - } |
97 | 100 |
|
98 | | - if let Some(opts) = &mut min_opts.mangle { |
99 | | - opts.top_level = Some(true); |
| 101 | + if let Some(opts) = &mut min_opts.mangle { |
| 102 | + opts.top_level = Some(true); |
| 103 | + } |
100 | 104 | } |
101 | | - } |
102 | | - |
103 | | - let comments = SingleThreadedComments::default(); |
104 | | - |
105 | | - let module = parse_js( |
106 | | - fm.clone(), |
107 | | - target, |
108 | | - Syntax::Es(EsConfig { |
109 | | - jsx: true, |
110 | | - decorators: true, |
111 | | - decorators_before_export: true, |
112 | | - import_assertions: true, |
113 | | - ..Default::default() |
114 | | - }), |
115 | | - IsModule::Bool(true), |
116 | | - Some(&comments), |
117 | | - ) |
118 | | - .map_err(|errs| { |
119 | | - Error::BatchErrors( |
120 | | - errs |
121 | | - .into_iter() |
122 | | - .map(|err| ecma_parse_error_to_rspack_error(err, &fm, &ModuleType::Js)) |
123 | | - .collect::<Vec<_>>(), |
| 105 | + |
| 106 | + let comments = SingleThreadedComments::default(); |
| 107 | + |
| 108 | + let module = parse_js( |
| 109 | + fm.clone(), |
| 110 | + target, |
| 111 | + Syntax::Es(EsConfig { |
| 112 | + jsx: true, |
| 113 | + decorators: true, |
| 114 | + decorators_before_export: true, |
| 115 | + import_assertions: true, |
| 116 | + ..Default::default() |
| 117 | + }), |
| 118 | + IsModule::Bool(true), |
| 119 | + Some(&comments), |
124 | 120 | ) |
125 | | - })?; |
| 121 | + .map_err(|errs| { |
| 122 | + Error::BatchErrors( |
| 123 | + errs |
| 124 | + .into_iter() |
| 125 | + .map(|err| ecma_parse_error_to_rspack_error(err, &fm, &ModuleType::Js)) |
| 126 | + .collect::<Vec<_>>(), |
| 127 | + ) |
| 128 | + })?; |
| 129 | + |
| 130 | + let source_map_names = if source_map.enabled() { |
| 131 | + let mut v = IdentCollector { |
| 132 | + names: Default::default(), |
| 133 | + }; |
126 | 134 |
|
127 | | - let source_map_names = if source_map.enabled() { |
128 | | - let mut v = IdentCollector { |
129 | | - names: Default::default(), |
| 135 | + module.visit_with(&mut v); |
| 136 | + |
| 137 | + v.names |
| 138 | + } else { |
| 139 | + Default::default() |
130 | 140 | }; |
131 | 141 |
|
132 | | - module.visit_with(&mut v); |
| 142 | + let unresolved_mark = Mark::new(); |
| 143 | + let top_level_mark = Mark::new(); |
133 | 144 |
|
134 | | - v.names |
135 | | - } else { |
136 | | - Default::default() |
137 | | - }; |
| 145 | + let is_mangler_enabled = min_opts.mangle.is_some(); |
138 | 146 |
|
139 | | - let unresolved_mark = Mark::new(); |
140 | | - let top_level_mark = Mark::new(); |
141 | | - |
142 | | - let is_mangler_enabled = min_opts.mangle.is_some(); |
143 | | - |
144 | | - let module = helpers::HELPERS.set(&Helpers::new(false), || { |
145 | | - HANDLER.set(handler, || { |
146 | | - let module = module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false)); |
147 | | - |
148 | | - let mut module = minifier::optimize( |
149 | | - module, |
150 | | - cm.clone(), |
151 | | - Some(&comments), |
152 | | - None, |
153 | | - &min_opts, |
154 | | - &minifier::option::ExtraOptions { |
155 | | - unresolved_mark, |
156 | | - top_level_mark, |
157 | | - }, |
158 | | - ); |
159 | | - |
160 | | - if !is_mangler_enabled { |
161 | | - module.visit_mut_with(&mut hygiene()) |
162 | | - } |
163 | | - module.fold_with(&mut fixer(Some(&comments as &dyn Comments))) |
164 | | - }) |
165 | | - }); |
166 | | - |
167 | | - let preserve_comments = opts |
168 | | - .format |
169 | | - .comments |
170 | | - .clone() |
171 | | - .into_inner() |
172 | | - .unwrap_or(BoolOr::Data(JsMinifyCommentOption::PreserveSomeComments)); |
173 | | - minify_file_comments(&comments, preserve_comments); |
174 | | - |
175 | | - print( |
176 | | - &module, |
177 | | - cm.clone(), |
178 | | - target, |
179 | | - SourceMapConfig { |
180 | | - enable: source_map.enabled(), |
181 | | - inline_sources_content: opts.inline_sources_content, |
182 | | - emit_columns: opts.emit_source_map_columns, |
183 | | - names: source_map_names, |
184 | | - }, |
185 | | - true, |
186 | | - Some(&comments), |
187 | | - opts.format.ascii_only, |
188 | | - ) |
189 | | - .map_err(|e| anyhow!(e)) |
190 | | - }); |
191 | | - ret.map_err(|e| e.into()) |
| 147 | + let module = helpers::HELPERS.set(&Helpers::new(false), || { |
| 148 | + HANDLER.set(handler, || { |
| 149 | + let module = module.fold_with(&mut resolver(unresolved_mark, top_level_mark, false)); |
| 150 | + |
| 151 | + let mut module = minifier::optimize( |
| 152 | + module, |
| 153 | + cm.clone(), |
| 154 | + Some(&comments), |
| 155 | + None, |
| 156 | + &min_opts, |
| 157 | + &minifier::option::ExtraOptions { |
| 158 | + unresolved_mark, |
| 159 | + top_level_mark, |
| 160 | + }, |
| 161 | + ); |
| 162 | + |
| 163 | + if !is_mangler_enabled { |
| 164 | + module.visit_mut_with(&mut hygiene()) |
| 165 | + } |
| 166 | + module.fold_with(&mut fixer(Some(&comments as &dyn Comments))) |
| 167 | + }) |
| 168 | + }); |
| 169 | + |
| 170 | + let preserve_comments = opts |
| 171 | + .format |
| 172 | + .comments |
| 173 | + .clone() |
| 174 | + .into_inner() |
| 175 | + .unwrap_or(BoolOr::Data(JsMinifyCommentOption::PreserveSomeComments)); |
| 176 | + minify_file_comments(&comments, preserve_comments); |
| 177 | + |
| 178 | + print( |
| 179 | + &module, |
| 180 | + cm.clone(), |
| 181 | + target, |
| 182 | + SourceMapConfig { |
| 183 | + enable: source_map.enabled(), |
| 184 | + inline_sources_content: opts.inline_sources_content, |
| 185 | + emit_columns: opts.emit_source_map_columns, |
| 186 | + names: source_map_names, |
| 187 | + }, |
| 188 | + true, |
| 189 | + Some(&comments), |
| 190 | + opts.format.ascii_only, |
| 191 | + ) |
| 192 | + }, |
| 193 | + ) |
192 | 194 | }) |
193 | 195 | } |
194 | 196 |
|
@@ -230,3 +232,68 @@ fn minify_file_comments( |
230 | 232 | } |
231 | 233 | } |
232 | 234 | } |
| 235 | + |
| 236 | +// keep this private to make sure with_rspack_error_handler is safety |
| 237 | +struct RspackErrorEmitter { |
| 238 | + tx: crossbeam_channel::Sender<rspack_error::Error>, |
| 239 | + source_map: Arc<SourceMap>, |
| 240 | + title: String, |
| 241 | + kind: DiagnosticKind, |
| 242 | +} |
| 243 | + |
| 244 | +impl Emitter for RspackErrorEmitter { |
| 245 | + fn emit(&mut self, db: &swc_core::common::errors::DiagnosticBuilder<'_>) { |
| 246 | + let source_file_and_byte_pos = db |
| 247 | + .span |
| 248 | + .primary_span() |
| 249 | + .map(|s| self.source_map.lookup_byte_offset(s.lo())); |
| 250 | + if let Some(source_file_and_byte_pos) = source_file_and_byte_pos { |
| 251 | + self |
| 252 | + .tx |
| 253 | + .send(Error::TraceableError( |
| 254 | + TraceableError::from_source_file( |
| 255 | + &source_file_and_byte_pos.sf, |
| 256 | + source_file_and_byte_pos.pos.0 as usize, |
| 257 | + source_file_and_byte_pos.pos.0 as usize, |
| 258 | + self.title.to_string(), |
| 259 | + db.message(), |
| 260 | + ) |
| 261 | + .with_kind(self.kind), |
| 262 | + )) |
| 263 | + .expect("Sender should drop after emit called"); |
| 264 | + } else { |
| 265 | + self |
| 266 | + .tx |
| 267 | + .send(internal_error!(db.message())) |
| 268 | + .expect("Sender should drop after emit called"); |
| 269 | + } |
| 270 | + } |
| 271 | +} |
| 272 | + |
| 273 | +pub fn with_rspack_error_handler<F, Ret>( |
| 274 | + title: String, |
| 275 | + kind: DiagnosticKind, |
| 276 | + cm: Arc<SourceMap>, |
| 277 | + op: F, |
| 278 | +) -> Result<Ret> |
| 279 | +where |
| 280 | + F: FnOnce(&Handler) -> Result<Ret>, |
| 281 | +{ |
| 282 | + let (tx, rx) = crossbeam_channel::unbounded(); |
| 283 | + let emitter = RspackErrorEmitter { |
| 284 | + title, |
| 285 | + kind, |
| 286 | + source_map: cm, |
| 287 | + tx, |
| 288 | + }; |
| 289 | + let handler = Handler::with_emitter(true, false, Box::new(emitter)); |
| 290 | + |
| 291 | + let ret = HANDLER.set(&handler, || op(&handler)); |
| 292 | + |
| 293 | + if handler.has_errors() { |
| 294 | + drop(handler); |
| 295 | + Err(rspack_error::Error::BatchErrors(rx.into_iter().collect())) |
| 296 | + } else { |
| 297 | + ret |
| 298 | + } |
| 299 | +} |
0 commit comments