Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Shorten "top", "right"... properties into "inset" property #1758

Merged
merged 2 commits into from Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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