Skip to content

Commit

Permalink
Shorten "top", "right" properties into "inset" property (#1758)
Browse files Browse the repository at this point in the history
  • Loading branch information
sapphi-red committed Nov 9, 2021
1 parent 4416658 commit c153d63
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 93 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Expand Up @@ -43,6 +43,27 @@
a,b{color:red}
```

* Shorten `top`, `right`, `bottom`, `left` CSS property into `inset` when it is supported ([#1758](https://github.com/evanw/esbuild/pull/1758))

This release enables collapsing of `inset` related properties:

```css
/* Original code */
div {
top: 0;
right: 0;
bottom: 0;
left: 0;
}

/* Output with "--minify-syntax" */
div {
inset: 0;
}
```

This minification rule is only enabled when `inset` property is supported by the target environment. Make sure to set esbuild's `target` setting correctly when minifying if the code will be running in an older environment (e.g. earlier than Chrome 87).

## 0.13.12

* Implement initial support for simplifying `calc()` expressions in CSS ([#1607](https://github.com/evanw/esbuild/issues/1607))
Expand Down
10 changes: 10 additions & 0 deletions internal/compat/css_table.go
Expand Up @@ -13,6 +13,8 @@ const (
// - rgb() can accept alpha values
// - Space-separated functional color notations
Modern_RGB_HSL

InsetProperty
)

func (features CSSFeature) Has(feature CSSFeature) bool {
Expand Down Expand Up @@ -42,6 +44,14 @@ var cssTable = map[CSSFeature]map[Engine][]int{
IOS: {12, 2},
Safari: {12, 1},
},
// Data from: https://developer.mozilla.org/en-US/docs/Web/CSS/inset
InsetProperty: {
Chrome: {87},
Edge: {87},
Firefox: {66},
IOS: {14, 5},
Safari: {14, 1},
},
}

// Return all features that are not available in at least one environment
Expand Down
2 changes: 2 additions & 0 deletions internal/css_ast/css_decl_table.go
Expand Up @@ -171,6 +171,7 @@ const (
DImageOrientation
DImageRendering
DInlineSize
DInset
DJustifyContent
DJustifyItems
DJustifySelf
Expand Down Expand Up @@ -489,6 +490,7 @@ var KnownDeclarations = map[string]D{
"image-orientation": DImageOrientation,
"image-rendering": DImageRendering,
"inline-size": DInlineSize,
"inset": DInset,
"justify-content": DJustifyContent,
"justify-items": DJustifyItems,
"justify-self": DJustifySelf,
Expand Down
57 changes: 40 additions & 17 deletions internal/css_parser/css_decls.go
@@ -1,6 +1,7 @@
package css_parser

import (
"github.com/evanw/esbuild/internal/compat"
"github.com/evanw/esbuild/internal/css_ast"
"github.com/evanw/esbuild/internal/css_lexer"
)
Expand Down Expand Up @@ -77,8 +78,9 @@ func compactTokenQuad(a css_ast.Token, b css_ast.Token, c css_ast.Token, d css_a
}

func (p *parser) processDeclarations(rules []css_ast.Rule) []css_ast.Rule {
margin := boxTracker{}
padding := boxTracker{}
margin := newBoxTracker(css_ast.DMargin, "margin", true)
padding := newBoxTracker(css_ast.DPadding, "padding", false)
inset := newBoxTracker(css_ast.DInset, "inset", true)
borderRadius := borderRadiusTracker{}

for i, rule := range rules {
Expand Down Expand Up @@ -131,6 +133,27 @@ func (p *parser) processDeclarations(rules []css_ast.Rule) []css_ast.Rule {
decl.Value = p.mangleBoxShadows(decl.Value)
}

case css_ast.DMargin:
if p.options.MangleSyntax {
margin.mangleSides(rules, decl, i, p.options.RemoveWhitespace)
}
case css_ast.DMarginTop:
if p.options.MangleSyntax {
margin.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxTop)
}
case css_ast.DMarginRight:
if p.options.MangleSyntax {
margin.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxRight)
}
case css_ast.DMarginBottom:
if p.options.MangleSyntax {
margin.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxBottom)
}
case css_ast.DMarginLeft:
if p.options.MangleSyntax {
margin.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxLeft)
}

case css_ast.DPadding:
if p.options.MangleSyntax {
padding.mangleSides(rules, decl, i, p.options.RemoveWhitespace)
Expand All @@ -152,25 +175,25 @@ func (p *parser) processDeclarations(rules []css_ast.Rule) []css_ast.Rule {
padding.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxLeft)
}

case css_ast.DMargin:
if p.options.MangleSyntax {
margin.mangleSides(rules, decl, i, p.options.RemoveWhitespace)
case css_ast.DInset:
if !p.options.UnsupportedCSSFeatures.Has(compat.InsetProperty) && p.options.MangleSyntax {
inset.mangleSides(rules, decl, i, p.options.RemoveWhitespace)
}
case css_ast.DMarginTop:
if p.options.MangleSyntax {
margin.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxTop)
case css_ast.DTop:
if !p.options.UnsupportedCSSFeatures.Has(compat.InsetProperty) && p.options.MangleSyntax {
inset.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxTop)
}
case css_ast.DMarginRight:
if p.options.MangleSyntax {
margin.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxRight)
case css_ast.DRight:
if !p.options.UnsupportedCSSFeatures.Has(compat.InsetProperty) && p.options.MangleSyntax {
inset.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxRight)
}
case css_ast.DMarginBottom:
if p.options.MangleSyntax {
margin.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxBottom)
case css_ast.DBottom:
if !p.options.UnsupportedCSSFeatures.Has(compat.InsetProperty) && p.options.MangleSyntax {
inset.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxBottom)
}
case css_ast.DMarginLeft:
if p.options.MangleSyntax {
margin.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxLeft)
case css_ast.DLeft:
if !p.options.UnsupportedCSSFeatures.Has(compat.InsetProperty) && p.options.MangleSyntax {
inset.mangleSide(rules, decl, i, p.options.RemoveWhitespace, boxLeft)
}

case css_ast.DBorderRadius:
Expand Down
42 changes: 19 additions & 23 deletions internal/css_parser/css_decls_box.go
Expand Up @@ -20,10 +20,22 @@ type boxSide struct {
}

type boxTracker struct {
key css_ast.D
keyText string
allowAuto bool

sides [4]boxSide
important bool
}

func newBoxTracker(key css_ast.D, keyText string, allowAuto bool) boxTracker {
return boxTracker{
key: key,
keyText: keyText,
allowAuto: allowAuto,
}
}

func (box *boxTracker) updateSide(rules []css_ast.Rule, side int, new boxSide) {
if old := box.sides[side]; old.token.Kind != css_lexer.TEndOfFile && (!new.single || old.single) {
rules[old.index] = css_ast.Rule{}
Expand All @@ -39,16 +51,15 @@ func (box *boxTracker) mangleSides(rules []css_ast.Rule, decl *css_ast.RDeclarat
}

allowedIdent := ""
isMargin := decl.Key == css_ast.DMargin
if isMargin {
if box.allowAuto {
allowedIdent = "auto"
}
if quad, ok := expandTokenQuad(decl.Value, allowedIdent); ok {
for side, t := range quad {
t.TurnLengthIntoNumberIfZero()
box.updateSide(rules, side, boxSide{token: t, index: uint32(index)})
}
box.compactRules(rules, decl.KeyRange, removeWhitespace, isMargin)
box.compactRules(rules, decl.KeyRange, removeWhitespace)
} else {
box.sides = [4]boxSide{}
}
Expand All @@ -61,27 +72,21 @@ func (box *boxTracker) mangleSide(rules []css_ast.Rule, decl *css_ast.RDeclarati
box.important = decl.Important
}

isMargin := false
switch decl.Key {
case css_ast.DMarginTop, css_ast.DMarginRight, css_ast.DMarginBottom, css_ast.DMarginLeft:
isMargin = true
}

if tokens := decl.Value; len(tokens) == 1 {
if t := tokens[0]; t.Kind.IsNumeric() || (t.Kind == css_lexer.TIdent && isMargin && t.Text == "auto") {
if t := tokens[0]; t.Kind.IsNumeric() || (t.Kind == css_lexer.TIdent && box.allowAuto && t.Text == "auto") {
if t.TurnLengthIntoNumberIfZero() {
tokens[0] = t
}
box.updateSide(rules, side, boxSide{token: t, index: uint32(index), single: true})
box.compactRules(rules, decl.KeyRange, removeWhitespace, isMargin)
box.compactRules(rules, decl.KeyRange, removeWhitespace)
return
}
}

box.sides = [4]boxSide{}
}

func (box *boxTracker) compactRules(rules []css_ast.Rule, keyRange logger.Range, removeWhitespace bool, isMargin bool) {
func (box *boxTracker) compactRules(rules []css_ast.Rule, keyRange logger.Range, removeWhitespace bool) {
// All tokens must be present
if eof := css_lexer.TEndOfFile; box.sides[0].token.Kind == eof || box.sides[1].token.Kind == eof ||
box.sides[2].token.Kind == eof || box.sides[3].token.Kind == eof {
Expand All @@ -104,18 +109,9 @@ func (box *boxTracker) compactRules(rules []css_ast.Rule, keyRange logger.Range,
rules[box.sides[3].index] = css_ast.Rule{}

// Insert the combined declaration where the last rule was
var key css_ast.D
var keyText string
if isMargin {
key = css_ast.DMargin
keyText = "margin"
} else {
key = css_ast.DPadding
keyText = "padding"
}
rules[box.sides[3].index].Data = &css_ast.RDeclaration{
Key: key,
KeyText: keyText,
Key: box.key,
KeyText: box.keyText,
Value: tokens,
KeyRange: keyRange,
Important: box.important,
Expand Down

0 comments on commit c153d63

Please sign in to comment.