diff --git a/crates/swc/tests/fixture/issues-8xxx/8275/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8275/input/.swcrc new file mode 100644 index 00000000000..37796ffcbab --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8275/input/.swcrc @@ -0,0 +1,22 @@ +{ + "jsc": { + "parser": { + "syntax": "ecmascript", + "jsx": false + }, + "target": "es2022", + "loose": false, + "minify": { + "compress": { + "pure_funcs": [ + "pure_html" + ] + }, + "mangle": false + } + }, + "module": { + "type": "es6" + }, + "isModule": true +} \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-8xxx/8275/input/1.js b/crates/swc/tests/fixture/issues-8xxx/8275/input/1.js new file mode 100644 index 00000000000..9300d994969 --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8275/input/1.js @@ -0,0 +1,15 @@ +/*#__PURE__*/ +function pure_html(strings, ...values) { + return { strings, values }; +} + +// ✅ This is tree-shaken from the minified output. +const a = `aaaa`; + +// ❌ This is not tree-shaken from the minified output, +// despite the "html" function being declare using +// `jsc.minify.compress.pure_funcs` configuration. +const b = pure_html`bbbb`; + +// ✅ This is not tree-shaken from the minified output. +export const c = pure_html`cccc`; \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-8xxx/8275/output/1.js b/crates/swc/tests/fixture/issues-8xxx/8275/output/1.js new file mode 100644 index 00000000000..41a9c9ad507 --- /dev/null +++ b/crates/swc/tests/fixture/issues-8xxx/8275/output/1.js @@ -0,0 +1,6 @@ +export const c = (function(strings, ...values) { + return { + strings, + values + }; +})`cccc`; diff --git a/crates/swc_ecma_minifier/src/compress/pure/misc.rs b/crates/swc_ecma_minifier/src/compress/pure/misc.rs index 7e41aa1b651..1eee79a6f98 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/misc.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/misc.rs @@ -928,20 +928,35 @@ impl Pure<'_> { _ => {} } - if let Expr::Call(CallExpr { - callee: Callee::Expr(callee), - args, - .. - }) = e - { - if callee.is_pure_callee(&self.expr_ctx) { - self.changed = true; - report_change!("Dropping pure call as callee is pure"); - *e = self - .make_ignored_expr(args.take().into_iter().map(|arg| arg.expr)) - .unwrap_or(Expr::Invalid(Invalid { span: DUMMY_SP })); - return; + match e { + Expr::Call(CallExpr { + callee: Callee::Expr(callee), + args, + .. + }) => { + if callee.is_pure_callee(&self.expr_ctx) { + self.changed = true; + report_change!("Dropping pure call as callee is pure"); + *e = self + .make_ignored_expr(args.take().into_iter().map(|arg| arg.expr)) + .unwrap_or(Expr::Invalid(Invalid { span: DUMMY_SP })); + return; + } } + + Expr::TaggedTpl(TaggedTpl { + tag: callee, tpl, .. + }) => { + if callee.is_pure_callee(&self.expr_ctx) { + self.changed = true; + report_change!("Dropping pure tag tpl as callee is pure"); + *e = self + .make_ignored_expr(tpl.exprs.take().into_iter()) + .unwrap_or(Expr::Invalid(Invalid { span: DUMMY_SP })); + return; + } + } + _ => (), } if self.options.unused { diff --git a/crates/swc_ecma_minifier/src/metadata/mod.rs b/crates/swc_ecma_minifier/src/metadata/mod.rs index ee353458e37..3d564b95da9 100644 --- a/crates/swc_ecma_minifier/src/metadata/mod.rs +++ b/crates/swc_ecma_minifier/src/metadata/mod.rs @@ -58,6 +58,39 @@ struct InfoMarker<'a> { state: State, } +impl InfoMarker<'_> { + fn is_pure_callee(&self, callee: &Expr) -> bool { + match callee { + Expr::Ident(callee) => { + if self.pure_callee.contains(&callee.to_id()) { + return true; + } + } + + Expr::Seq(callee) => { + if has_pure(self.comments, callee.span) { + return true; + } + } + _ => (), + } + + if let Some(pure_fns) = &self.pure_funcs { + if let Expr::Ident(..) = callee { + // Check for pure_funcs + if Ident::within_ignored_ctxt(|| { + // + pure_fns.contains(&NodeIgnoringSpan::borrowed(callee)) + }) { + return true; + } + } + } + + has_pure(self.comments, callee.span()) + } +} + impl VisitMut for InfoMarker<'_> { noop_visit_mut_type!(); @@ -71,19 +104,9 @@ impl VisitMut for InfoMarker<'_> { // We check callee in some cases because we move comments // See https://github.com/swc-project/swc/issues/7241 if match &n.callee { - Callee::Expr(e) => match &**e { - Expr::Ident(callee) => self.pure_callee.contains(&callee.to_id()), - _ => false, - }, + Callee::Expr(e) => self.is_pure_callee(e), _ => false, } || has_pure(self.comments, n.span) - || match &n.callee { - Callee::Expr(e) => match &**e { - Expr::Seq(callee) => has_pure(self.comments, callee.span), - _ => false, - }, - _ => false, - } { if !n.span.is_dummy_ignoring_cmt() { n.span = n.span.apply_mark(self.marks.pure); @@ -185,6 +208,16 @@ impl VisitMut for InfoMarker<'_> { } } + fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) { + n.visit_mut_children_with(self); + + if has_pure(self.comments, n.span) || self.is_pure_callee(&n.tag) { + if !n.span.is_dummy_ignoring_cmt() { + n.span = n.span.apply_mark(self.marks.pure); + } + } + } + fn visit_mut_var_decl(&mut self, n: &mut VarDecl) { n.visit_mut_children_with(self);