Commit graph

233 commits

Author SHA1 Message Date
Boshen
29417ddc03 feat(minifier): minimize !(a, b) -> a, !b (#8746) 2025-01-27 08:24:07 +00:00
sapphi-red
e0117db531
feat(minifier): replace const with let for non-exported read-only variables (#8733)
Replace `const` with `let` when that value does not have any assignments and not exposed.
2025-01-26 12:44:01 +00:00
sapphi-red
360d49e962
feat(minifier): replace Math.pow with ** (#8730)
Replaces `Math.pow(a, b)` with `(+a) ** (+b)`. `+` does `ToNumber` and `**` does `Number::exponentiate` when both operands are number.
`+` is not added when `a` or `b` is already a number.

**Reference**
- [Spec of `Math.pow`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-math.pow)
- [Spec of `**`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-exp-operator-runtime-semantics-evaluation)
- [Spec of unary `+`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-unary-plus-operator-runtime-semantics-evaluation)
2025-01-26 11:24:19 +00:00
Boshen
e9fb5febdf
feat(minifier): dce pure expressions such as new Map() (#8725) 2025-01-26 18:08:34 +08:00
Boshen
0af0267077 refactor(minifier): side effect detection needs symbols resolution (#8715) 2025-01-25 15:01:00 +00:00
翠 / green
6589c3bbb3
feat(mangler): reuse variable names (#8562)
Changed the mangler to reuse variable names where possible.

This will reduce the code size as shorter variable names can be used in
more places. But requires global information and limits parallelism in a
single file and requires more memory.

---------

Co-authored-by: Boshen <boshenc@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-01-25 14:00:24 +08:00
Boshen
8587965e45
perf(minifier): normalize undefined to void 0 before everything else (#8699)
so subsequent code don't need to lookup `undefined`.
2025-01-25 11:50:08 +08:00
sapphi-red
343690e178
feat(minifier): replace Number.*_SAFE_INTEGER/Number.EPSILON (#8682)
The value of `Number.*_SAFE_INTEGER`, `Number.EPSILON` are constants as they cannot be changed. This PR replaces them with `2**53-1` / `-(2**53-1)` / `2**-52` for ES2016+. For ES2015, `Number.EPSILON` is not changed but `Number.*_SAFE_INTEGER`s are replaced with `9007199254740991` / `-9007199254740991`.

**Reference**
- Spec of [`Number.MAX_SAFE_INTEGER`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.max_safe_integer)
- Spec of [`Number.MIN_SAFE_INTEGER`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.min_safe_integer)
- Spec of [`Number.EPSILON`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.epsilon)

### Additional Information
- [`Number.MIN_VALUE`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.min_value) cannot be replaced as the value depends on the runtime
- [`Number.MAX_VALUE`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.max_value) can be replaced but I didn't come up with a shorter representation that does not lack precision
2025-01-24 03:47:52 +00:00
sapphi-red
0c5bb30859
feat(minifier): replace Number.POSITIVE_INFINITY/Number.NEGATIVE_INFINITY/Number.NaN (#8681)
The value of `Number.POSITIVE_INFINITY`, `Number.NEGATIVE_INFINITY`, `Number.NaN` are constants as they cannot be changed. This PR replaces them with `Infinity`/`-Infinity`/`NaN`.

**Reference**
- Spec of [`Number.POSITIVE_INFINITY`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.positive_infinity)
- Spec of [`Number.NEGATIVE_INFINITY`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.positive_infinity)
- Spec of [`Number.NaN`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.positive_infinity)
2025-01-23 15:26:22 +00:00
Boshen
ba201a6511 fix(minifier): remove "non esbuild optimizations" which is incorrect (#8668) 2025-01-23 03:45:42 +00:00
Boshen
3802d28233 refactor(minifier): clean up try_minimize_conditional (#8660) 2025-01-22 15:17:47 +00:00
Boshen
9953ac7cad
perf(minifier): add LatePeepholeOptimizations (#8651)
This PR adds a `LatePeepholeOptimizations` pass for minifications that
don't interact with the fixed point loop.

While working on this I found a couple of cases where the previous fixed
point loop is not idempotent.
2025-01-22 16:19:06 +08:00
Boshen
5b3c412e26
perf(minifier): only run optimizations on local changes (#8644)
Previously all code are ran in a fixed point loop when ast changes.

This PR changes running code when a function changes its enclosing ast only.
2025-01-21 23:55:54 +08:00
sapphi-red
835b25889b
feat(minifier): compress typeof foo === 'object' && foo !== null to typeof foo == 'object' && !!foo (#8638)
If `typeof foo == 'object'`, then `foo` is guaranteed to be an object or null. In that case, `foo !== null` can be replaced with `!!foo` because objects return `true` for `!!foo` and null returns `false` for it.

**References**
- [Spec of `typeof`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-typeof-operator)
- [Spec of `!`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-logical-not-operator)
- [Spec of `ToBoolean`](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-toboolean)
2025-01-21 14:59:25 +00:00
sapphi-red
2bcbed2d50
feat(minifier): compress (a = b) === null || a === undefined to (a = b) == null (#8637) 2025-01-21 14:59:23 +00:00
Boshen
6f95cd599a
refactor(minifier): remove all the unnecessary fake ast passes (#8618)
This also removes handling of making cjs-module-lexer to work.
2025-01-20 22:05:51 +08:00
翠 / green
8c8b5fa9be
fix(minifier): avoid minifing String(a) into "" + a for symbols (#8612)
We shouldn't change `String(a)` into `"" + a` if `a` can be a Symbol.
`String(Symbol())` does not throw an error, but `"" + Symbol()` does.

**References**
- [Spec of `ToString` (called for `"" +
variable`)](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-tostring)
- [Spec of
`String(a)`](https://tc39.es/ecma262/multipage/text-processing.html#sec-string-constructor-string-value)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-01-20 13:34:46 +08:00
Boshen
2a2a2eb5ea
chore(tasks/minsize): enable test coverage 2025-01-19 12:00:40 +08:00
Boshen
7b219a930f fix(minifier): fix dce shadowed undefined (#8582) 2025-01-18 09:04:28 +00:00
camc314
4d4e805691 feat(minifier): collapse if stmt with empty consequent (#8577) 2025-01-18 03:51:40 +00:00
sapphi-red
991a22f907
feat(minifier): fold Array::concat into literal (#8442)
Compress `[].concat(a, [b])` into `[a, b]`.

**References**
- [Spec of `Array::concat`](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.concat)
2025-01-17 06:00:07 +00:00
sapphi-red
3dc2d8b8e9
feat(minifier): fold string concat chaining (#8441)
Compress `"".concat(a).concat(b)` into `"".concat(a, b)`.

**References**
- [Spec of `String::concat`](https://tc39.es/ecma262/multipage/text-processing.html#sec-string.prototype.concat)
2025-01-17 06:00:06 +00:00
sapphi-red
a4ae4505f1
feat(minifier): fold array concat chaining (#8440)
Compress `[].concat(a).concat(b)` into `[].concat(a, b)`.

**References**
- [Spec of `Array::concat`](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.concat)

---

### The new assumption

I added a new assumption description in `crates/oxc_minifier/README.md`: "Errors thrown when creating a String or an Array that exceeds the maximum length can disappear or moved".
This is an assumption held by other minifiers. Without this assumption, we have to treat `+` and array creation/update to have a sideeffect and that limits the minification.

For input:
```js
[...Array(Number(2n ** 32n - 1n)),""]
" ".repeat(Number(2n ** 29n - 24n - 1n)) + ' '
export {}
```
(Note that `2 ** 32 - 1` is the max array length and `2 ** 29 - 24` is the max string length on V8.)
This code errors with too long array and too long string on V8 based runtimes.

terser outputs:
```js
Array(Number(2n**32n-1n))," ".repeat(Number(536870887n));export{};
```
No error will happen with this code.

SWC outputs:
```js
[...Array(Number(2n**32n-1n))]," ".repeat(Number(2n**29n-24n-1n));export{};
```
No error will happen with this code.
[playground](https://play.swc.rs/?version=1.10.7&code=H4sIAAAAAAAAA4vW09NzLCpKrNTwK81NSi3SMMpT0NJSMAZSugqGeZqaOkpKsbxcSgpKekWpBamJJajqjCxB6oxMYKoVtBXUFdR5uVIrCvKLShSqa3m5AGRqy%2FBiAAAA&config=H4sIAAAAAAAAA32UO3LjMAyG%2B5zCozrFjostcoDtcgYOTYIyvXxoCNCxJuO7L0RJtjeG3En48AMkAOL7bbfrTmi6j903f%2FLPoAtCuf2zBcdE%2BsKWDkzUaIofqHtf6Qkn5HRAaKbrTDrSpQdqKtz%2F2u8XRRdyRlgViy365N34mNPkOBRAfLCxlUPWCInwf%2F3CSv6aAJX6aD%2FkHECnF0RpVD4R9FCkwCaHoAcEddZFiDKdVBePWUoxwUpg1VDyIPJkPfmcOOcztaCtMtmCgHwBQ%2F4MkoxzsSwhX0%2B4T8MWDrXvW59%2FqOGsQ9Uk5IRLawmfVoh6zB5JuZqkEs5wowYzXIr7U%2BmdKkC1pGfdKfu00ZO%2FAFyBoBGTjiDFbR6O52lL7V4qfXI8sjQKnOdbumWCnouqvHdCZafKQCEvdbOArQamyhrpOAveKB96Cwqc41kRQuOXJ3OUktI4QHYC4P5qJ03VDNTtFW7w6UG8wH%2F4liQP2OIRNR23KY7xkMOLBBHomO0LB24F5W1ceEtchm1ekwUeDbCiS8UGnpcAPwDKKrR9%2BTQb%2FDw4oupDPtzXxOJwve3hqFN%2Ff%2B%2FzKn5bHLqYbW1wWfJTf%2BfV%2FLu7O61beD1B5%2FFzFbac13%2FKOhgeLwYAAA%3D%3D)

esbuild outputs:
```js
[...Array(Number(2n**32n-1n))]," ".repeat(Number(2n**29n-24n-1n))+"";export{};
```
No error will happen with this code.
[esbuild try](https://esbuild.github.io/try/#dAAwLjI0LjIALS1taW5pZnkAWy4uLkFycmF5KE51bWJlcigybiAqKiAzMm4gLSAxbikpLCIiXQoiICIucmVwZWF0KE51bWJlcigybiAqKiAyOW4gLSAyNG4gLSAxbikpICsgJyAnCmV4cG9ydCB7fQ)

OXC outputs:
```js
[...Array(Number(2n**32n-1n))]," ".repeat(Number(2n**29n-24n-1n))+" ";export{};
```
The array error won't happen and the String error will happen with this code.
[playground](https://playground.oxc.rs/#eNpVT71OwzAQfhXrlv4QIhpgIBtLR8SOGZxwCUH22To7baMo746dNEVMd5/u+7sRaijhI8/zV2Y1bN96UyFvCxL7vXiM414caLfLJEj4lCRBSMgZHarwn1u8JG7xtCrEndiIjSS8OMtBjJMkyMBCOQL3lIYfKKgLlIF7zEB3FKBslPYR+No6XC9+MJXVKwqsyDeWzZU8ZeAUe+TZ0vZc47HTSMpEAwjoQ/7jY7JjjKQTvitG8n/ilDuL437zXtyC4hZjKUBfPByeITJq+4UtpvoRmI66plu4tTUpIRovNc/fXcx2qr69YRS1+opmJwps9VHbc9KfkCvr43npNU2/v/WIsw==)
2025-01-17 05:53:54 +00:00
Boshen
b1d018622b fix(minifier): do not fold !!void b (#8533) 2025-01-16 05:52:03 +00:00
Boshen
53adde5003
fix(minifier): x['-2147483648'] -> x[-2147483648] (#8528) 2025-01-16 13:43:23 +08:00
Boshen
1860411656 feat(minifier): remove last redundant return statement (#8523) 2025-01-16 02:07:28 +00:00
Boshen
8accfefa74 feat(minifier): minify var x; void x -> void 0 (#8466) 2025-01-13 16:07:59 +00:00
Boshen
3c9354983d fix(minifier): dce if statement should keep side effects and vars (#8433)
closes #7209

Note: Current output is sub-optimal.
2025-01-11 14:02:12 +00:00
sapphi-red
d56020b84c
feat(minifier): drop 0 from new Int8Array(0) and other TypedArrays (#8431)
Compresses `new Int8Array(0)` into `new Int8Array()`. (then will be compress into `new Int8Array`).

Partial quote from the [spec](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-typedarray):

> 5. If numberOfArgs = 0, then
>   a. Return ? AllocateTypedArray(constructorName, NewTarget, proto, 0).
> 6. Else,
>   c. Else,
>       ii. Let elementLength be ? ToIndex(firstArgument).
>      iii. Return ? AllocateTypedArray(constructorName, NewTarget, proto, elementLength).
2025-01-11 07:45:36 +00:00
sapphi-red
f935d9434f
feat(minifier): remove new from NativeErrors / AggregateError (#8430)
Remove `new` in the some cases:

- NativeErrors (e.g. `new EvalError(...)` -> `EvalError(...)`): [spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-nativeerror-constructors:~:text=the%20function%20call%20NativeError(%E2%80%A6)%20is%20equivalent%20to%20the%20object%20creation%20expression%20new%20NativeError(%E2%80%A6)%20with%20the%20same%20arguments.), [the list of NativeErrors in spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-native-error-types-used-in-this-standard)
- `new AggregateError(...)` -> `AggregateError(...)`: [spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-aggregate-error-constructor:~:text=the%20function%20call%20AggregateError(%E2%80%A6)%20is%20equivalent%20to%20the%20object%20creation%20expression%20new%20AggregateError(%E2%80%A6)%20with%20the%20same%20arguments.)
2025-01-11 07:23:07 +00:00
Boshen
dab7a51e78 feat(minifier): minimize not !(x === undefined) -> x !== undefined (#8429) 2025-01-11 06:30:36 +00:00
Boshen
357b61d179 fix(minifier): do not minify Object.defineProperty in sequence expressions (#8416) 2025-01-10 12:04:39 +00:00
Boshen
5b5b8443f4
feat(minifier): fold ambiguous if else (#8415) 2025-01-10 19:51:31 +08:00
Boshen
fb2acd87b3 refactor(minifier): change minimize conditionals into a loop (#8413) 2025-01-10 09:49:09 +00:00
Boshen
baaec6020c refactor(minifier): remove the buggy ?? transform (#8411)
e.g. `(a != null ? a : b);` is not `a ?? b`

There are also no unit tests.
2025-01-10 09:33:09 +00:00
Boshen
438a6e7abc feat(minifier): minimize conditions in boolean context (#8381) 2025-01-10 03:47:10 +00:00
camc314
793cb43138 feat(minifier): a != null ? a : b -> a ?? b (#8352) 2025-01-09 10:12:52 +00:00
sapphi-red
814da55f81
feat(minifier): compress x = x || 1 to x ||= 1 (#8368)
The simplified version of the evaluation of `a = a || b` is:

> AssignmentExpression : LeftHandSideExpression = LogicalORExpression || LogicalANDExpression
> 1. Let lRef be ? Evaluation of LeftHandSideExpression.
> 2. Let llRef be ? Evaluation of LogicalORExpression.
> 3. Let llVal be ? GetValue(llRef).
> 4. If ToBoolean(llVal) is true
>   a. Perform ? PutValue(lRef, llVal).
>   b. return llVal.
> 5. Let lrRef be ? Evaluation of LogicalANDExpression.
> 6. Let rRef be ? GetValue(lrRef).
> 7. Let rVal be ? GetValue(rRef). [Note GetValue(rRef) returns rRef itself]
> 8. Perform ? PutValue(lRef, rVal).
> 9. Return rVal.

The simplified version of the evaluation of `a ||= b` is:

> AssignmentExpression : LeftHandSideExpression ||= AssignmentExpression
> 1. Let lRef be ? Evaluation of LeftHandSideExpression.
> 2. Let lVal be ? GetValue(lRef).
> 3. If ToBoolean(lVal) is true, return lVal.
> 4. Let rRef be ? Evaluation of AssignmentExpression.
> 5. Let rVal be ? GetValue(rRef).
> 6. Perform ? PutValue(lRef, rVal).
> 7. Return rVal.

The difference of these is that

- the evaluation of `a` is done twice for `a = a || b`, one with `1. Let lRef be ? Evaluation of LeftHandSideExpression` and one with `2. Let llRef be ? Evaluation of LogicalORExpression.`. This is same with #8366, #8367.
- `PutValue(lRef, llVal)` is performed when `ToBoolean(lVal)` is `true`.

So `x = x || 1` can be compressed to `x ||= 1` when the conditions written in #8366 are met and `PutValue(lRef, llVal)` does not have a side effect. When `a` is a non-global identifier (and not a reference created by a `with` statement), these conditions are met.

**References**
- [Spec of `||`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-binary-logical-operators-runtime-semantics-evaluation)
- [Spec of `=` / `||=`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-binary-logical-operators-runtime-semantics-evaluation)
2025-01-09 03:47:12 +00:00
sapphi-red
a596821d85
feat(minifier): compress a.b = a.b + c to a.b += c (#8367)
The simplified version of the evaluation of `a += b` is:

> AssignmentExpression : LeftHandSideExpression AssignmentOperator AssignmentExpression
> 1. Let lRef be ? Evaluation of LeftHandSideExpression.
> 2. Let lVal be ? GetValue(lRef).
> 3. Let rRef be ? Evaluation of AssignmentExpression.
> 4. Let rVal be ? GetValue(rRef).
> 5. Let r be ? ApplyStringOrNumericBinaryOperator(lVal, opText, rVal).
> 6. Perform ? PutValue(lRef, r).
> 7. Return r.

The simplified version of the evaluation of `a = a + b` is:

> AssignmentExpression : LeftHandSideExpression = AssignmentExpressionLeft + AssignmentExpressionRight
> 1. Let lRef be ? Evaluation of LeftHandSideExpression.
> 2. Let alRef be ? Evaluation of AssignmentExpressionLeft.
> 3. Let alVal be ? GetValue(alRef).
> 4. Let arRef be ? Evaluation of AssignmentExpressionRight.
> 5. Let arVal be ? GetValue(arRef).
> 6. Let rRef be ? ApplyStringOrNumericBinaryOperator(alVal, opText, arVal).
> 7. Let rVal be ? GetValue(rRef). [Note GetValue(rRef) returns rRef itself]
> 8. Perform ? PutValue(lRef, rVal).
> 9. Return rVal.

The difference of these is that the evaluation of `a` is done twice for `a = a + b`, one with `1. Let lRef be ? Evaluation of LeftHandSideExpression` and one with `2. Let alRef be ? Evaluation of AssignmentExpressionLeft.`

So this is same with #8366 and can be compressed similarly when the conditions are met (`a.b = a.b + c` -> `a.b += c`).

**References**
- [Spec of `=`, `+=`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-assignment-operators-runtime-semantics-evaluation)
- [Spec of `+`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-addition-operator-plus-runtime-semantics-evaluation)
2025-01-09 03:47:11 +00:00
sapphi-red
579eb603d5
feat(minifier): compress a.b || (a.b = c) to a.b ||= c (#8366)
As noted in https://github.com/oxc-project/oxc/pull/8315, the only difference of `x ||= y` and `x || (x = y)` is that `Let lRef be ? Evaluation of LeftHandSideExpression` is done twice.

If the LeftHandSideExpression is a MemberExpression, `GetValue(baseReference)` is the only place that might have a side effect. Further more, if the `baseReference` is an IdentifierReference that is not a global reference (and not a reference created by a `with` statement), `GetValue(baseReference)` does not have a side effect.

When those conditions are met, `a.b || (a.b = c)` can be compress to `a.b ||= c`.

**References**
- [Spec of `a.b`](<https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-property-accessors-runtime-semantics-evaluation:~:text=%2C%20strict).-,MemberExpression,4.%20Return%20MakePrivateReference(baseValue%2C%20fieldNameString).,-CallExpression>)
- [Spec of `GetValue`](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-getvalue)
- [Spec of `IdentifierReference`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-identifiers-runtime-semantics-evaluation)
-  [Spec of `DeclarativeEnvironmentRecord.GetBindingValue`](https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-declarative-environment-records-getbindingvalue-n-s)
2025-01-09 03:47:10 +00:00
camc314
f367a16364 feat(minifier): port esbuild conditional expr minification (#8351)
still some TODOs, but the main framework is there
2025-01-09 02:49:33 +00:00
Boshen
09f0f483f6 refactor(minifier): remove the buggy minimize_exit_points implementation (#8349) 2025-01-09 02:49:32 +00:00
sapphi-red
8d52cd0a5e
feat(minifier): merge assign expression in conditional expression (#8345)
compresses `a ? b = 0 : b = 1` into `b = a ? 0 : 1`

This can be done when `b` is an IdentifierReference and the assignment operator is `=`.

In this circumstance, the evaluation of `b = a ? 0 : 1` is:

1. Let lref be ? Evaluation of LeftHandSideExpression. (this does not have a side effect when LeftHandSideExpression is an IdentifierReference)
2. Let rref be ? Evaluation of AssignmentExpression. (ConditionalExpression is evaluated here)
3. Let rval be ? GetValue(rref).
4. Perform ? PutValue(lref, rval).
5. Return rval.

**References**
- [spec of `=`](https://262.ecma-international.org/15.0/index.html#sec-assignment-operators-runtime-semantics-evaluation)
- [spec of `? :`](https://262.ecma-international.org/15.0/index.html#sec-conditional-operator-runtime-semantics-evaluation)
2025-01-08 13:34:12 +00:00
sapphi-red
a69d15f299
feat(minifier): compress new Array(2) -> [,,] (#8344)
For an integer value `n` smaller than 6, `Array(n)` can be compressed to `[,,]` (the number of `,` is `n`).
2025-01-08 12:07:46 +00:00
Boshen
6220e05f38 feat(minifier): remove empty if statment if (test) {} -> test (#8336) 2025-01-08 08:50:10 +00:00
sapphi-red
ec88c68c28
feat(minifier): compress a || (a = b) to a ||= b (#8315)
Compress `a || (a = b)` to `a ||= b` and for other logical operators that are possible to.

I didn't find other minifiers doing this, but this is safe for identifiers. [Quoting MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR_assignment#description):

> Logical OR assignment short-circuits, meaning that `x ||= y` is equivalent to `x || (x = y)`, except that the expression x is only evaluated once.

I actually checked the spec and the only difference was that `Let lRef be ? Evaluation of LeftHandSideExpression` was done twice. Evaluating an IdentifierReference is idempotent so it should be safe to do this compression.

References:

- [Spec of `&&=`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-assignment-operators:~:text=Return%20r.-,AssignmentExpression,7.%20Return%20rVal.,-AssignmentExpression)
- [Spec of `=`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#prod-AssignmentExpression:~:text=Runtime%20Semantics%3A%20Evaluation-,AssignmentExpression,6.%20Return%20rVal.,-AssignmentExpression)
- [Spec of `&&`](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-binary-logical-operators:~:text=Runtime%20Semantics%3A%20Evaluation-,LogicalANDExpression,5.%20Return%20%3F%C2%A0GetValue(rRef).,-LogicalORExpression)

I think this is safe for `a.b || (a.b = foo)` as well because the number of `GetValue` and `SetValue` does not change, but I didn't include that in this PR.
2025-01-07 23:19:52 +00:00
sapphi-red
e6fe84d674
feat(minifier): compress a = a + b to a += b (#8314)
Compress `a = a + b` to `a += b` and for other binary operators that are possible to.
2025-01-07 23:19:50 +00:00
sapphi-red
9ea4e31ba3
feat(minifier): remove new from new Error/new Function/new RegExp (#8313)
Remove `new` from some cases:

- `new Error(...)` -> `Error(...)`: `new` can be removed unconditionally, [spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-error-constructor:~:text=Thus%20the%20function%20call%20Error(%E2%80%A6)%20is%20equivalent%20to%20the%20object%20creation%20expression%20new%20Error(%E2%80%A6)%20with%20the%20same%20arguments.)
- `new Function(...)` -> `Function(...)`: `new` can be removed unconditionally, [spec](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-function-constructor:~:text=Thus%20the%20function%20call%20Function(%E2%80%A6)%20is%20equivalent%20to%20the%20object%20creation%20expression%20new%20Function(%E2%80%A6)%20with%20the%20same%20arguments.)
- `new RegExp(nonRegexp)` -> `RegExp(nonRegexp)`: `new` can be removed if there's no argument or the first argument is not regex object, [mdn](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp#return_value), [spec](https://tc39.es/ecma262/multipage/text-processing.html#sec-regexp-constructor:~:text=when%20called%20as%20a%20function%20rather%20than%20as%20a%20constructor%2C%20returns%20either%20a%20new%20RegExp%20object%2C%20or%20the%20argument%20itself%20if%20the%20only%20argument%20is%20a%20RegExp%20object.). I made this to happen if the first argument is not an object.
2025-01-07 14:48:56 +00:00
Boshen
051fbb6909 feat(minifier): minimize x["0"] -> x[0] (#8316) 2025-01-07 13:12:35 +00:00
Boshen
a542013773 feat(minifier): minimize do{}while(true) -> do;while(true) (#8311) 2025-01-07 12:55:03 +00:00