Does not replace constant when in brackets
Feature request
I guess because sub-optimal, but I really don't understand what is going on here.
Version (complete output of terser -V or specific git commit)
terser 5.16.8
I give a complete test case code:
import { minify } from 'terser';
const options =
{
compress: { ecma: 6 },
module: true,
output: { beautify: true, },
};
const code1 =
`{
const e = 32767;
ql.render = function(t) {
if (t > e) s.save()
};
}`
const code2 = '(()=>' + code1 + ')();';
const code3 = '(()=>{' + code1 + '})();';
const r1 = await minify( code1, options );
const r2 = await minify( code2, options );
const r3 = await minify( code3, options );
console.log( r1.code );
console.log( '======================' );
console.log( r2.code );
console.log( '======================' );
console.log( r3.code );
Expected result
All 3 cases should have (almost) the same result (except keeping the capsule)
But in case 2 e gets inlined, in case 1 and 3 not.
Output is:
{
const n = 32767;
ql.render = function(e) {
e > n && s.save();
};
}
======================
ql.render = function(e) {
e > 32767 && s.save();
};
======================
(() => {
{
const n = 32767;
ql.render = function(e) {
e > n && s.save();
};
}
})();
Unless I misunderstood something, when providing {module:true} as option, { } environments while not having any old style 'var's around, should be effective the same as these empty call capsules.
Especially strange is case 3, where I just put another pair of brackets around the code in the capsule and it stops getting minified well.
This is an extremely reduced case of some kind of "rollup-like" I wrote myself, and when encapsulating the partial codes in these (()=>{CODE})() capsules the result gets after zip compression 25% smaller than without. (since also mangle names seem to not reset and thus zip having a harder time). These empty capsules were all understandable and used them a lot in old style javascript, but in a modern module world.. IMO should no longer be necessary.
Interesting case! I'm convinced const is a part of the issue here. Terser doesn't like messing with it usually.
It is the same with 'let'. Interestingly, it works as expected with 'var'. (where on the other hand in case 1 I wouldn't inline it, since "e" could be visible elsewhere since 'vars' where global to function scope, which is why we often had these does-nothing-function capsules back the day.)
These are similar cases. This happens when rollup tree-shakes the if (true) (rollup repl).
input
function fn1 () {
{
let a = 'foo';
console.log(a);
console.log(a);
}
}
const fn2 = () => {
{
let a = 'foo';
console.log(a);
console.log(a);
}
};
class Foo {
static {
{
let a = 'foo';
console.log(a);
console.log(a);
}
}
method() {
{
let a = 'foo';
console.log(a);
console.log(a);
}
}
}
const obj = {
method2() {
{
let a = 'foo';
console.log(a);
console.log(a);
}
}
};
export { Foo, fn1, fn2, obj };
output
function o(){{let o="foo";console.log(o),console.log(o)}}const l=()=>{{let o="foo";console.log(o),console.log(o)}};class e{static{{let o="foo";console.log(o),console.log(o)}}method(){{let o="foo";console.log(o),console.log(o)}}}const c={method2(){{let o="foo";console.log(o),console.log(o)}}};export{e as Foo,o as fn1,l as fn2,c as obj};
You're welcome to look into it!
I'm aware that some compilation steps generate empty blocks so I've had a few tries. These didn't go so well because I always found complexity raised too much to maintain over time.
So I've instead been investing more time on another project I'm working on that aims to remove scoping and AST manipulation concerns from optimisations, by not working with ASTs at all and instead doing the usual compiler thing where you work with a list of instructions. This project will take a while to complete but I've been giving it most of my time, which does leave terser behind.
From what I remember, there's a function can_be_evicted_from_block which returns false when something can't be removed from one of those blocks.
That function is there to protect the rest of the optimisations from making mistakes. It's technically possible to remove function definitions and const/let from blocks, it's just disabled for safety.
So the problem lies in lots of places, that thing is just a safekeeping measure that trades some code size for having less bugs.