Skip to content

Commit 5bad391

Browse files
committed
fix #2057: work around safari css transform bugs
1 parent 62e74e5 commit 5bad391

File tree

3 files changed

+60
-85
lines changed

3 files changed

+60
-85
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@
22

33
## Unreleased
44

5+
* Reduce minification of CSS transforms to avoid Safari bugs ([#2057](https://github.com/evanw/esbuild/issues/2057))
6+
7+
In Safari, applying a 3D CSS transform to an element can cause it to render in a different order than applying a 2D CSS transform even if the transformation matrix is identical. I believe this is a bug in Safari because the [CSS `transform` specification](https://drafts.csswg.org/css-transforms-1/#transform-rendering) doesn't seem to distinguish between 2D and 3D transforms as far as rendering order:
8+
9+
> For elements whose layout is governed by the CSS box model, any value other than `none` for the `transform` property results in the creation of a stacking context.
10+
11+
This bug means that minifying a 3D transform into a 2D transform must be avoided even though it's a valid transformation because it can cause rendering differences in Safari. Previously esbuild sometimes minified 3D CSS transforms into 2D CSS transforms but with this release, esbuild will no longer do that:
12+
13+
```css
14+
/* Original code */
15+
div { transform: matrix3d(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) }
16+
17+
/* Old output (with --minify) */
18+
div{transform:scale(2)}
19+
20+
/* New output (with --minify) */
21+
div{transform:scale3d(2,2,1)}
22+
```
23+
524
* Minification now takes advantage of the `?.` operator
625

726
This adds new code minification rules that shorten code with the `?.` optional chaining operator when the result is equivalent:

internal/css_parser/css_decls_transform.go

Lines changed: 21 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ func (p *parser) mangleTransforms(tokens []css_ast.Token) []css_ast.Token {
155155
args[0].TurnLengthIntoNumberIfZero()
156156
}
157157

158+
// Note: This is considered a 2D transform even though it's specified
159+
// in terms of a 3D transform because it doesn't trigger Safari's 3D
160+
// transform bugs.
161+
case "rotatez":
162+
// same as rotate3d(0, 0, 1, <angle>), which is a 3d transform
163+
// equivalent to the 2d transform rotate(<angle>).
164+
if n == 1 {
165+
// "rotateZ(angle)" => "rotate(angle)"
166+
token.Text = "rotate"
167+
args[0].TurnLengthIntoNumberIfZero()
168+
}
169+
158170
case "skew":
159171
// specifies a 2D skew by [ax,ay] for X and Y. If the second
160172
// parameter is not provided, it has a zero value.
@@ -189,6 +201,11 @@ func (p *parser) mangleTransforms(tokens []css_ast.Token) []css_ast.Token {
189201
////////////////////////////////////////////////////////////////////////////////
190202
// 3D transforms
191203

204+
// Note: Safari has a bug where 3D transforms render differently than
205+
// other transforms. This means we should not minify a 3D transform
206+
// into a 2D transform or it will cause a rendering difference in
207+
// Safari.
208+
192209
case "matrix3d":
193210
// specifies a 3D transformation as a 4x4 homogeneous matrix of 16
194211
// values in column-major order.
@@ -206,39 +223,19 @@ func (p *parser) mangleTransforms(tokens []css_ast.Token) []css_ast.Token {
206223
}
207224
}
208225
const onlyScale = 0b1000_0000_0000_0000_0111_1011_1101_1110
209-
const only2D = 0b1000_0100_0000_0000_0100_1011_1100_1100
210226
if (mask & onlyScale) == onlyScale {
211227
// | m0 0 0 0 |
212228
// | 0 m5 0 0 |
213229
// | 0 0 m10 0 |
214230
// | 0 0 0 1 |
215-
sx, sy, sz := args[0], args[10], args[20]
216-
if sx.EqualIgnoringWhitespace(sy) && sz.IsOne() {
217-
token.Text = "scale"
218-
*token.Children = args[:1]
219-
} else if sy.IsOne() && sz.IsOne() {
220-
token.Text = "scaleX"
221-
*token.Children = args[:1]
222-
} else if sx.IsOne() && sz.IsOne() {
223-
token.Text = "scaleY"
224-
*token.Children = args[10:11]
225-
} else if sx.IsOne() && sy.IsOne() {
231+
sx, sy := args[0], args[10]
232+
if sx.IsOne() && sy.IsOne() {
226233
token.Text = "scaleZ"
227234
*token.Children = args[20:21]
228-
} else if sz.IsOne() {
229-
token.Text = "scale"
230-
*token.Children = append(args[0:2], args[10])
231235
} else {
232236
token.Text = "scale3d"
233237
*token.Children = append(append(args[0:2], args[10:12]...), args[20])
234238
}
235-
} else if (mask & only2D) == only2D {
236-
// | m0 m4 0 m12 |
237-
// | m1 m5 0 m13 |
238-
// | 0 0 1 0 |
239-
// | 0 0 0 1 |
240-
token.Text = "matrix"
241-
*token.Children = append(append(args[0:4], args[8:12]...), args[24:27]...)
242239
}
243240

244241
// Note: A "matrix3d" cannot be directly converted into a "translate3d"
@@ -258,22 +255,10 @@ func (p *parser) mangleTransforms(tokens []css_ast.Token) []css_ast.Token {
258255
tx.TurnLengthOrPercentageIntoNumberIfZero()
259256
ty.TurnLengthOrPercentageIntoNumberIfZero()
260257
tz.TurnLengthIntoNumberIfZero()
261-
if ty.IsZero() && tz.IsZero() {
262-
// "translate3d(tx, 0, 0)" => "translate(tx)"
263-
token.Text = "translate"
264-
*token.Children = args[:1]
265-
} else if tx.IsZero() && tz.IsZero() {
266-
// "translate3d(0, ty, 0)" => "translateY(ty)"
267-
token.Text = "translateY"
268-
*token.Children = args[2:3]
269-
} else if tx.IsZero() && ty.IsZero() {
258+
if tx.IsZero() && ty.IsZero() {
270259
// "translate3d(0, 0, tz)" => "translateZ(tz)"
271260
token.Text = "translateZ"
272261
*token.Children = args[4:]
273-
} else if tz.IsZero() {
274-
// "translate3d(tx, ty, 0)" => "translate(tx, ty)"
275-
token.Text = "translate"
276-
*token.Children = args[:3]
277262
}
278263
}
279264

@@ -292,26 +277,10 @@ func (p *parser) mangleTransforms(tokens []css_ast.Token) []css_ast.Token {
292277
turnPercentIntoNumberIfShorter(sx)
293278
turnPercentIntoNumberIfShorter(sy)
294279
turnPercentIntoNumberIfShorter(sz)
295-
if sx.EqualIgnoringWhitespace(*sy) && sz.IsOne() {
296-
// "scale3d(s, s, 1)" => "scale(s)"
297-
token.Text = "scale"
298-
*token.Children = args[:1]
299-
} else if sy.IsOne() && sz.IsOne() {
300-
// "scale3d(sx, 1, 1)" => "scaleX(sx)"
301-
token.Text = "scaleX"
302-
*token.Children = args[:1]
303-
} else if sx.IsOne() && sz.IsOne() {
304-
// "scale3d(1, sy, 1)" => "scaleY(sy)"
305-
token.Text = "scaleY"
306-
*token.Children = args[2:3]
307-
} else if sx.IsOne() && sy.IsOne() {
280+
if sx.IsOne() && sy.IsOne() {
308281
// "scale3d(1, 1, sz)" => "scaleZ(sz)"
309282
token.Text = "scaleZ"
310283
*token.Children = args[4:]
311-
} else if sz.IsOne() {
312-
// "scale3d(sx, sy, 1)" => "scale(sx, sy)"
313-
token.Text = "scale"
314-
*token.Children = args[:3]
315284
}
316285
}
317286

@@ -338,10 +307,6 @@ func (p *parser) mangleTransforms(tokens []css_ast.Token) []css_ast.Token {
338307
// "rotate3d(0, 1, 0, angle)" => "rotateY(angle)"
339308
token.Text = "rotateY"
340309
*token.Children = args[6:]
341-
} else if x.IsZero() && y.IsZero() && z.IsOne() {
342-
// "rotate3d(0, 0, 1, angle)" => "rotate(angle)"
343-
token.Text = "rotate"
344-
*token.Children = args[6:]
345310
}
346311
}
347312

@@ -357,15 +322,6 @@ func (p *parser) mangleTransforms(tokens []css_ast.Token) []css_ast.Token {
357322
args[0].TurnLengthIntoNumberIfZero()
358323
}
359324

360-
case "rotatez":
361-
// same as rotate3d(0, 0, 1, <angle>), which is a 3d transform
362-
// equivalent to the 2d transform rotate(<angle>).
363-
if n == 1 {
364-
// "rotateZ(angle)" => "rotate(angle)"
365-
token.Text = "rotate"
366-
args[0].TurnLengthIntoNumberIfZero()
367-
}
368-
369325
case "perspective":
370326
// specifies a perspective projection matrix. This matrix scales
371327
// points in X and Y based on their Z value, scaling points with

internal/css_parser/css_parser_test.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,19 +1559,19 @@ func TestTransform(t *testing.T) {
15591559
"a {\n transform: matrix3d(1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1);\n}\n")
15601560
expectPrintedMangle(t,
15611561
"a { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) }",
1562-
"a {\n transform: scale(1);\n}\n")
1562+
"a {\n transform: scaleZ(1);\n}\n")
15631563
expectPrintedMangle(t,
15641564
"a { transform: matrix3d(2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) }",
1565-
"a {\n transform: scaleX(2);\n}\n")
1565+
"a {\n transform: scale3d(2, 1, 1);\n}\n")
15661566
expectPrintedMangle(t,
15671567
"a { transform: matrix3d(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) }",
1568-
"a {\n transform: scaleY(2);\n}\n")
1568+
"a {\n transform: scale3d(1, 2, 1);\n}\n")
15691569
expectPrintedMangle(t,
15701570
"a { transform: matrix3d(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) }",
1571-
"a {\n transform: scale(2);\n}\n")
1571+
"a {\n transform: scale3d(2, 2, 1);\n}\n")
15721572
expectPrintedMangle(t,
15731573
"a { transform: matrix3d(2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) }",
1574-
"a {\n transform: scale(2, 3);\n}\n")
1574+
"a {\n transform: scale3d(2, 3, 1);\n}\n")
15751575
expectPrintedMangle(t,
15761576
"a { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1) }",
15771577
"a {\n transform: scaleZ(2);\n}\n")
@@ -1580,32 +1580,32 @@ func TestTransform(t *testing.T) {
15801580
"a {\n transform: scale3d(1, 2, 3);\n}\n")
15811581
expectPrintedMangle(t,
15821582
"a { transform: matrix3d(2, 3, 0, 0, 4, 5, 0, 0, 0, 0, 1, 0, 6, 7, 0, 1) }",
1583-
"a {\n transform: matrix(2, 3, 4, 5, 6, 7);\n}\n")
1583+
"a {\n transform: matrix3d(2, 3, 0, 0, 4, 5, 0, 0, 0, 0, 1, 0, 6, 7, 0, 1);\n}\n")
15841584

1585-
expectPrintedMangle(t, "a { transform: translate3d(0, 0, 0) }", "a {\n transform: translate(0);\n}\n")
1586-
expectPrintedMangle(t, "a { transform: translate3d(0%, 0%, 0) }", "a {\n transform: translate(0);\n}\n")
1587-
expectPrintedMangle(t, "a { transform: translate3d(0px, 0px, 0px) }", "a {\n transform: translate(0);\n}\n")
1588-
expectPrintedMangle(t, "a { transform: translate3d(1px, 0px, 0px) }", "a {\n transform: translate(1px);\n}\n")
1589-
expectPrintedMangle(t, "a { transform: translate3d(0px, 1px, 0px) }", "a {\n transform: translateY(1px);\n}\n")
1585+
expectPrintedMangle(t, "a { transform: translate3d(0, 0, 0) }", "a {\n transform: translateZ(0);\n}\n")
1586+
expectPrintedMangle(t, "a { transform: translate3d(0%, 0%, 0) }", "a {\n transform: translateZ(0);\n}\n")
1587+
expectPrintedMangle(t, "a { transform: translate3d(0px, 0px, 0px) }", "a {\n transform: translateZ(0);\n}\n")
1588+
expectPrintedMangle(t, "a { transform: translate3d(1px, 0px, 0px) }", "a {\n transform: translate3d(1px, 0, 0);\n}\n")
1589+
expectPrintedMangle(t, "a { transform: translate3d(0px, 1px, 0px) }", "a {\n transform: translate3d(0, 1px, 0);\n}\n")
15901590
expectPrintedMangle(t, "a { transform: translate3d(0px, 0px, 1px) }", "a {\n transform: translateZ(1px);\n}\n")
15911591
expectPrintedMangle(t, "a { transform: translate3d(1px, 2px, 3px) }", "a {\n transform: translate3d(1px, 2px, 3px);\n}\n")
15921592
expectPrintedMangle(t, "a { transform: translate3d(1px, 0, 3px) }", "a {\n transform: translate3d(1px, 0, 3px);\n}\n")
15931593
expectPrintedMangle(t, "a { transform: translate3d(0, 2px, 3px) }", "a {\n transform: translate3d(0, 2px, 3px);\n}\n")
1594-
expectPrintedMangle(t, "a { transform: translate3d(1px, 2px, 0px) }", "a {\n transform: translate(1px, 2px);\n}\n")
1595-
expectPrintedMangle(t, "a { transform: translate3d(40%, 60%, 0px) }", "a {\n transform: translate(40%, 60%);\n}\n")
1594+
expectPrintedMangle(t, "a { transform: translate3d(1px, 2px, 0px) }", "a {\n transform: translate3d(1px, 2px, 0);\n}\n")
1595+
expectPrintedMangle(t, "a { transform: translate3d(40%, 60%, 0px) }", "a {\n transform: translate3d(40%, 60%, 0);\n}\n")
15961596

15971597
expectPrintedMangle(t, "a { transform: translateZ(0) }", "a {\n transform: translateZ(0);\n}\n")
15981598
expectPrintedMangle(t, "a { transform: translateZ(0px) }", "a {\n transform: translateZ(0);\n}\n")
15991599
expectPrintedMangle(t, "a { transform: translateZ(1px) }", "a {\n transform: translateZ(1px);\n}\n")
16001600

1601-
expectPrintedMangle(t, "a { transform: scale3d(1, 1, 1) }", "a {\n transform: scale(1);\n}\n")
1602-
expectPrintedMangle(t, "a { transform: scale3d(2, 1, 1) }", "a {\n transform: scaleX(2);\n}\n")
1603-
expectPrintedMangle(t, "a { transform: scale3d(1, 2, 1) }", "a {\n transform: scaleY(2);\n}\n")
1601+
expectPrintedMangle(t, "a { transform: scale3d(1, 1, 1) }", "a {\n transform: scaleZ(1);\n}\n")
1602+
expectPrintedMangle(t, "a { transform: scale3d(2, 1, 1) }", "a {\n transform: scale3d(2, 1, 1);\n}\n")
1603+
expectPrintedMangle(t, "a { transform: scale3d(1, 2, 1) }", "a {\n transform: scale3d(1, 2, 1);\n}\n")
16041604
expectPrintedMangle(t, "a { transform: scale3d(1, 1, 2) }", "a {\n transform: scaleZ(2);\n}\n")
16051605
expectPrintedMangle(t, "a { transform: scale3d(1, 2, 3) }", "a {\n transform: scale3d(1, 2, 3);\n}\n")
1606-
expectPrintedMangle(t, "a { transform: scale3d(2, 3, 1) }", "a {\n transform: scale(2, 3);\n}\n")
1607-
expectPrintedMangle(t, "a { transform: scale3d(2, 2, 1) }", "a {\n transform: scale(2);\n}\n")
1608-
expectPrintedMangle(t, "a { transform: scale3d(3, 300%, 100.00%) }", "a {\n transform: scale(3);\n}\n")
1606+
expectPrintedMangle(t, "a { transform: scale3d(2, 3, 1) }", "a {\n transform: scale3d(2, 3, 1);\n}\n")
1607+
expectPrintedMangle(t, "a { transform: scale3d(2, 2, 1) }", "a {\n transform: scale3d(2, 2, 1);\n}\n")
1608+
expectPrintedMangle(t, "a { transform: scale3d(3, 300%, 100.00%) }", "a {\n transform: scale3d(3, 3, 1);\n}\n")
16091609
expectPrintedMangle(t, "a { transform: scale3d(1%, 2%, 3%) }", "a {\n transform: scale3d(1%, 2%, 3%);\n}\n")
16101610

16111611
expectPrintedMangle(t, "a { transform: scaleZ(1) }", "a {\n transform: scaleZ(1);\n}\n")
@@ -1619,7 +1619,7 @@ func TestTransform(t *testing.T) {
16191619
expectPrintedMangle(t, "a { transform: rotate3d(0, 0, 0, 45deg) }", "a {\n transform: rotate3d(0, 0, 0, 45deg);\n}\n")
16201620
expectPrintedMangle(t, "a { transform: rotate3d(1, 0, 0, 45deg) }", "a {\n transform: rotateX(45deg);\n}\n")
16211621
expectPrintedMangle(t, "a { transform: rotate3d(0, 1, 0, 45deg) }", "a {\n transform: rotateY(45deg);\n}\n")
1622-
expectPrintedMangle(t, "a { transform: rotate3d(0, 0, 1, 45deg) }", "a {\n transform: rotate(45deg);\n}\n")
1622+
expectPrintedMangle(t, "a { transform: rotate3d(0, 0, 1, 45deg) }", "a {\n transform: rotate3d(0, 0, 1, 45deg);\n}\n")
16231623

16241624
expectPrintedMangle(t, "a { transform: rotateX(0) }", "a {\n transform: rotateX(0);\n}\n")
16251625
expectPrintedMangle(t, "a { transform: rotateX(0deg) }", "a {\n transform: rotateX(0);\n}\n")

0 commit comments

Comments
 (0)