From 85f85f25735b43b955f9f74c16edbe0e6bda184a Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Thu, 14 Oct 2021 21:44:13 -0400 Subject: [PATCH] fix #1682: always use the shortest css alpha value --- CHANGELOG.md | 17 +++++++++++++++ internal/css_parser/css_decls_color.go | 28 ++++++++++++++++++++----- internal/css_parser/css_parser_test.go | 29 ++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b8a482e7d1..3c726bbfe25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## Unreleased + +* Minify CSS alpha values correctly ([#1682](https://github.com/evanw/esbuild/issues/1682)) + + When esbuild uses the `rgba()` syntax for a color instead of the 8-character hex code (e.g. when `target` is set to Chrome 61 or earlier), the 0-to-255 integer alpha value must be printed as a floating-point fraction between 0 and 1. The fraction was only printed to three decimal places since that is the minimal number of decimal places required for all 256 different alpha values to be uniquely determined. However, using three decimal places does not necessarily result in the shortest result. For example, `128 / 255` is `0.5019607843137255` which is printed as `".502"` using three decimal places, but `".5"` is equivalent because `round(0.5 * 255) == 128`, so printing `".5"` would be better. With this release, esbuild will always use the minimal numeric representation for the alpha value: + + ```css + /* Original code */ + a { color: #FF800080 } + + /* Old output (with --minify --target=chrome61) */ + a{color:rgba(255,128,0,.502)} + + /* New output (with --minify --target=chrome61) */ + a{color:rgba(255,128,0,.5)} + ``` + ## 0.13.6 * Emit decorators for `declare` class fields ([#1675](https://github.com/evanw/esbuild/issues/1675)) diff --git a/internal/css_parser/css_decls_color.go b/internal/css_parser/css_decls_color.go index f9535405979..320d22e1365 100644 --- a/internal/css_parser/css_decls_color.go +++ b/internal/css_parser/css_decls_color.go @@ -633,11 +633,10 @@ func (p *parser) mangleColor(token css_ast.Token, hex uint32) css_ast.Token { token.Kind = css_lexer.TFunction token.Text = "rgba" commaToken := p.commaToken() - alpha := floatToString(float64(hexA(hex)) / 255) - if p.options.MangleSyntax { - if text, ok := mangleNumber(alpha); ok { - alpha = text - } + index := hexA(hex) * 4 + alpha := alphaFractionTable[index : index+4] + if space := strings.IndexByte(alpha, ' '); space != -1 { + alpha = alpha[:space] } token.Children = &[]css_ast.Token{ {Kind: css_lexer.TNumber, Text: strconv.Itoa(hexR(hex))}, commaToken, @@ -649,3 +648,22 @@ func (p *parser) mangleColor(token css_ast.Token, hex uint32) css_ast.Token { return token } + +// Every four characters in this table is the fraction for that index +const alphaFractionTable string = "" + + "0 .004.008.01 .016.02 .024.027.03 .035.04 .043.047.05 .055.06 " + + ".063.067.07 .075.08 .082.086.09 .094.098.1 .106.11 .114.118.12 " + + ".125.13 .133.137.14 .145.15 .153.157.16 .165.17 .173.176.18 .184" + + ".19 .192.196.2 .204.208.21 .216.22 .224.227.23 .235.24 .243.247" + + ".25 .255.26 .263.267.27 .275.28 .282.286.29 .294.298.3 .306.31 " + + ".314.318.32 .325.33 .333.337.34 .345.35 .353.357.36 .365.37 .373" + + ".376.38 .384.39 .392.396.4 .404.408.41 .416.42 .424.427.43 .435" + + ".44 .443.447.45 .455.46 .463.467.47 .475.48 .482.486.49 .494.498" + + ".5 .506.51 .514.518.52 .525.53 .533.537.54 .545.55 .553.557.56 " + + ".565.57 .573.576.58 .584.59 .592.596.6 .604.608.61 .616.62 .624" + + ".627.63 .635.64 .643.647.65 .655.66 .663.667.67 .675.68 .682.686" + + ".69 .694.698.7 .706.71 .714.718.72 .725.73 .733.737.74 .745.75 " + + ".753.757.76 .765.77 .773.776.78 .784.79 .792.796.8 .804.808.81 " + + ".816.82 .824.827.83 .835.84 .843.847.85 .855.86 .863.867.87 .875" + + ".88 .882.886.89 .894.898.9 .906.91 .914.918.92 .925.93 .933.937" + + ".94 .945.95 .953.957.96 .965.97 .973.976.98 .984.99 .992.9961 " diff --git a/internal/css_parser/css_parser_test.go b/internal/css_parser/css_parser_test.go index db4c1bdd679..efaa3603bc3 100644 --- a/internal/css_parser/css_parser_test.go +++ b/internal/css_parser/css_parser_test.go @@ -1,6 +1,7 @@ package css_parser import ( + "fmt" "testing" "github.com/evanw/esbuild/internal/compat" @@ -1316,3 +1317,31 @@ func TestTransform(t *testing.T) { expectPrintedMangle(t, "a { transform: perspective(0px) }", "a {\n transform: perspective(0);\n}\n") expectPrintedMangle(t, "a { transform: perspective(1px) }", "a {\n transform: perspective(1px);\n}\n") } + +func TestMangleAlpha(t *testing.T) { + alphas := []string{ + "0", ".004", ".008", ".01", ".016", ".02", ".024", ".027", ".03", ".035", ".04", ".043", ".047", ".05", ".055", ".06", + ".063", ".067", ".07", ".075", ".08", ".082", ".086", ".09", ".094", ".098", ".1", ".106", ".11", ".114", ".118", ".12", + ".125", ".13", ".133", ".137", ".14", ".145", ".15", ".153", ".157", ".16", ".165", ".17", ".173", ".176", ".18", ".184", + ".19", ".192", ".196", ".2", ".204", ".208", ".21", ".216", ".22", ".224", ".227", ".23", ".235", ".24", ".243", ".247", + ".25", ".255", ".26", ".263", ".267", ".27", ".275", ".28", ".282", ".286", ".29", ".294", ".298", ".3", ".306", ".31", + ".314", ".318", ".32", ".325", ".33", ".333", ".337", ".34", ".345", ".35", ".353", ".357", ".36", ".365", ".37", ".373", + ".376", ".38", ".384", ".39", ".392", ".396", ".4", ".404", ".408", ".41", ".416", ".42", ".424", ".427", ".43", ".435", + ".44", ".443", ".447", ".45", ".455", ".46", ".463", ".467", ".47", ".475", ".48", ".482", ".486", ".49", ".494", ".498", + ".5", ".506", ".51", ".514", ".518", ".52", ".525", ".53", ".533", ".537", ".54", ".545", ".55", ".553", ".557", ".56", + ".565", ".57", ".573", ".576", ".58", ".584", ".59", ".592", ".596", ".6", ".604", ".608", ".61", ".616", ".62", ".624", + ".627", ".63", ".635", ".64", ".643", ".647", ".65", ".655", ".66", ".663", ".667", ".67", ".675", ".68", ".682", ".686", + ".69", ".694", ".698", ".7", ".706", ".71", ".714", ".718", ".72", ".725", ".73", ".733", ".737", ".74", ".745", ".75", + ".753", ".757", ".76", ".765", ".77", ".773", ".776", ".78", ".784", ".79", ".792", ".796", ".8", ".804", ".808", ".81", + ".816", ".82", ".824", ".827", ".83", ".835", ".84", ".843", ".847", ".85", ".855", ".86", ".863", ".867", ".87", ".875", + ".88", ".882", ".886", ".89", ".894", ".898", ".9", ".906", ".91", ".914", ".918", ".92", ".925", ".93", ".933", ".937", + ".94", ".945", ".95", ".953", ".957", ".96", ".965", ".97", ".973", ".976", ".98", ".984", ".99", ".992", ".996", + } + + for i, alpha := range alphas { + expectPrintedLowerMangle(t, fmt.Sprintf("a { color: #%08X }", i), "a {\n color: rgba(0, 0, 0, "+alpha+");\n}\n") + } + + // An alpha value of 100% does not use "rgba(...)" + expectPrintedLowerMangle(t, "a { color: #000000FF }", "a {\n color: #000;\n}\n") +}