swc-project / swc

Rust-based platform for the Web
https://swc.rs
Apache License 2.0
31.29k stars 1.23k forks source link

Improve dead code removal for object's empty methods #9756

Open yf-yang opened 4 days ago

yf-yang commented 4 days ago

Describe the feature

const logger = { 
  info: () => {}, 
  log: x => console.log(x), 
  warn: x => console.log(x),
};
logger.info('abc');
// logger.log('def');
// logger.info('aaa');

playground link

When only one logger.log exists, in 1 pass, the code would be compiled to a chaining method call and blocks further dead code detection. When uncomment the second / the last two lines, 1 pass compiled codes are different and lead to different multi pass compiled code.

Terser could work more stable in this scenario. You can copy the code to https://try.terser.org/ and configure compress.passes to 1/2/3 to see how it is different from SWC.

Not a bug, but I hope it could be improved, so that I can remove lots of strings logged by logger in out app with SWC's minimizer.

Thank you!

Babel plugin or link to the feature description

No response

Additional context

No response

CPunisher commented 1 day ago

Investigation: Currently this optimization is prevented by the following code, where self.ctx.is_callee is true. https://github.com/swc-project/swc/blob/fa80a1eb86cf4babe1d4912f28152d62f068cbbe/crates/swc_ecma_minifier/src/compress/optimize/props.rs#L251

We can find the corresponding check in terser at: https://github.com/terser/terser/blob/3167d34557d9bbf6394b468605169e7f9648594a/lib/compress/index.js#L3716C66-L3716C79 where the restriction is released with !has_annotation(parent, _NOINLINE) (which is usually true).

We can make it by add a new is_noline context. However, to keep the this pointer in the inlined function correct, we also need to add a safety check: https://github.com/terser/terser/blob/3167d34557d9bbf6394b468605169e7f9648594a/lib/compress/index.js#L3475

The only problem is that when the value of the property is Ident(AST_SymbolRef), we have not collected the fixed expr of all idents but those to be inlined. So for now, by the above, we can only optimize ({ f: (x) => console.log(x) }).f(1) to console.log(1), but we can't optimize:

const f = (x) => console.log(x);
console.log(f); // another reference that prevents minifier inlining f
({ f: f }).f(1);