Skip to content

Commit 629e10b

Browse files
authored
fix: show codeframe for minify error (#2046)
1 parent 906ed48 commit 629e10b

File tree

3 files changed

+233
-170
lines changed

3 files changed

+233
-170
lines changed
Lines changed: 208 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
use std::sync::Arc;
22

3-
use anyhow::anyhow;
43
use rspack_core::ModuleType;
5-
use rspack_error::{Error, Result};
4+
use rspack_error::{internal_error, DiagnosticKind, Error, Result, TraceableError};
65
use swc_core::{
76
base::{
87
config::{IsModule, JsMinifyCommentOption, JsMinifyOptions, SourceMapsConfig},
9-
try_with_handler, BoolOr, TransformOutput,
8+
BoolOr, TransformOutput,
109
},
1110
common::{
1211
collections::AHashMap,
1312
comments::{Comment, Comments, SingleThreadedComments},
14-
errors::HANDLER,
13+
errors::{Emitter, Handler, HANDLER},
1514
BytePos, FileName, Mark, SourceMap, GLOBALS,
1615
},
1716
ecma::{
@@ -39,156 +38,159 @@ use crate::utils::ecma_parse_error_to_rspack_error;
3938
pub fn minify(opts: &JsMinifyOptions, input: String, filename: &str) -> Result<TransformOutput> {
4039
let cm: Arc<SourceMap> = Default::default();
4140
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))
7562
})
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+
};
8689

87-
// top_level defaults to true if module is true
90+
// top_level defaults to true if module is true
8891

89-
// https://github.com/swc-project/swc/issues/2254
92+
// https://github.com/swc-project/swc/issues/2254
9093

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+
}
9599
}
96-
}
97100

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+
}
100104
}
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),
124120
)
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+
};
126134

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()
130140
};
131141

132-
module.visit_with(&mut v);
142+
let unresolved_mark = Mark::new();
143+
let top_level_mark = Mark::new();
133144

134-
v.names
135-
} else {
136-
Default::default()
137-
};
145+
let is_mangler_enabled = min_opts.mangle.is_some();
138146

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+
)
192194
})
193195
}
194196

@@ -230,3 +232,68 @@ fn minify_file_comments(
230232
}
231233
}
232234
}
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

Comments
 (0)