Skip to content

Commit

Permalink
prefix some css properties (#3122)
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jun 26, 2023
1 parent 33322d2 commit 9d8a7e5
Show file tree
Hide file tree
Showing 18 changed files with 533 additions and 99 deletions.
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,51 @@
}
```

* Insert some prefixed CSS properties when appropriate ([#3122](https://github.com/evanw/esbuild/issues/3122))

With this release, esbuild will now insert prefixed CSS properties in certain cases when the `target` setting includes browsers that require a certain prefix. This is currently done for the following properties:

* `appearance: *;` => `-webkit-appearance: *; -moz-appearance: *;`
* `backdrop-filter: *;` => `-webkit-backdrop-filter: *;`
* `background-clip: text` => `-webkit-background-clip: text;`
* `clip-path: *` => `-webkit-clip-path: *;`
* `font-kerning: *;` => `-webkit-font-kerning: *;`
* `hyphens: *;` => `-webkit-hyphens: *;`
* `initial-letter: *;` => `-webkit-initial-letter: *;`
* `mask-image: *;` => `-webkit-mask-image: *;`
* `mask-origin: *;` => `-webkit-mask-origin: *;`
* `mask-position: *;` => `-webkit-mask-position: *;`
* `mask-repeat: *;` => `-webkit-mask-repeat: *;`
* `mask-size: *;` => `-webkit-mask-size: *;`
* `position: sticky;` => `position: -webkit-sticky;`
* `print-color-adjust: *;` => `-webkit-print-color-adjust: *;`
* `tab-size: *;` => `-moz-tab-size: *; -o-tab-size: *;`
* `text-orientation: *;` => `-webkit-text-orientation: *;`
* `text-size-adjust: *;` => `-webkit-text-size-adjust: *; -ms-text-size-adjust: *;`
* `user-select: *;` => `-webkit-user-select: *; -moz-user-select: *; -ms-user-select: *;`

Here is an example:

```css
/* Original code */
div {
mask-image: url(x.png);
}
/* Old output (with --target=chrome99) */
div {
mask-image: url(x.png);
}
/* New output (with --target=chrome99) */
div {
-webkit-mask-image: url(x.png);
mask-image: url(x.png);
}
```

Browser compatibility data was sourced from the tables on https://caniuse.com. Support for more CSS properties can be added in the future as appropriate.

* Fix an obscure identifier minification bug ([#2809](https://github.com/evanw/esbuild/issues/2809))

Function declarations in nested scopes behave differently depending on whether or not `"use strict"` is present. To avoid generating code that behaves differently depending on whether strict mode is enabled or not, esbuild transforms nested function declarations into variable declarations. However, there was a bug where the generated variable name was not being recorded as declared internally, which meant that it wasn't being renamed correctly by the minifier and could cause a name collision. This bug has been fixed:
Expand Down
7 changes: 1 addition & 6 deletions internal/bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,7 @@ func parseFile(args parseArgs) {
result.ok = ok

case config.LoaderCSS:
ast := args.caches.CSSCache.Parse(args.log, source, css_parser.Options{
MinifySyntax: args.options.MinifySyntax,
MinifyWhitespace: args.options.MinifyWhitespace,
UnsupportedCSSFeatures: args.options.UnsupportedCSSFeatures,
OriginalTargetEnv: args.options.OriginalTargetEnv,
})
ast := args.caches.CSSCache.Parse(args.log, source, css_parser.OptionsFromConfig(&args.options))
result.file.inputFile.Repr = &graph.CSSRepr{AST: ast}
result.ok = true

Expand Down
2 changes: 1 addition & 1 deletion internal/cache/cache_ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (c *CSSCache) Parse(log logger.Log, source logger.Source, options css_parse
}()

// Cache hit
if entry != nil && entry.source == source && entry.options == options {
if entry != nil && entry.source == source && entry.options.Equal(&options) {
for _, msg := range entry.msgs {
log.AddMsg(msg)
}
Expand Down
162 changes: 162 additions & 0 deletions internal/compat/css_table.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package compat

import (
"github.com/evanw/esbuild/internal/css_ast"
)

type CSSFeature uint8

const (
Expand Down Expand Up @@ -109,3 +113,161 @@ func UnsupportedCSSFeatures(constraints map[Engine][]int) (unsupported CSSFeatur
}
return
}

type CSSPrefix uint8

const (
WebkitPrefix CSSPrefix = 1 << iota
MozPrefix
MsPrefix
OPrefix

NoPrefix CSSPrefix = 0
)

type prefixData struct {
// Note: In some cases, earlier versions did not require a prefix but later
// ones do. This is the case for Microsoft Edge for example, which switched
// the underlying browser engine from a custom one to the one from Chrome.
// However, we assume that users specifying a browser version for CSS mean
// "works in this version or newer", so we still add a prefix when a target
// is an old Edge version.
withoutPrefix v
prefix CSSPrefix
}

var cssMaskPrefixTable = map[Engine]prefixData{
Chrome: {prefix: WebkitPrefix},
Edge: {prefix: WebkitPrefix},
IOS: {prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
Opera: {prefix: WebkitPrefix},
Safari: {prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
}

var cssPrefixTable = map[css_ast.D]map[Engine]prefixData{
// https://caniuse.com/css-appearance
css_ast.DAppearance: {
Chrome: {prefix: WebkitPrefix, withoutPrefix: v{84, 0, 0}},
Edge: {prefix: WebkitPrefix, withoutPrefix: v{84, 0, 0}},
Firefox: {prefix: MozPrefix, withoutPrefix: v{80, 4, 0}},
IOS: {prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
Opera: {prefix: WebkitPrefix, withoutPrefix: v{73, 4, 0}},
Safari: {prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
},

// https://caniuse.com/css-backdrop-filter
css_ast.DBackdropFilter: {
IOS: {prefix: WebkitPrefix},
Safari: {prefix: WebkitPrefix},
},

// https://caniuse.com/background-clip-text (Note: only for "background-clip: text")
css_ast.DBackgroundClip: {
Chrome: {prefix: WebkitPrefix},
Edge: {prefix: WebkitPrefix},
IOS: {prefix: WebkitPrefix, withoutPrefix: v{14, 0, 0}},
Opera: {prefix: WebkitPrefix},
Safari: {prefix: WebkitPrefix, withoutPrefix: v{14, 0, 0}},
},

// https://caniuse.com/css-clip-path
css_ast.DClipPath: {
Chrome: {prefix: WebkitPrefix, withoutPrefix: v{55, 0, 0}},
IOS: {prefix: WebkitPrefix, withoutPrefix: v{13, 0, 0}},
Opera: {prefix: WebkitPrefix, withoutPrefix: v{42, 0, 0}},
Safari: {prefix: WebkitPrefix, withoutPrefix: v{13, 1, 0}},
},

// https://caniuse.com/font-kerning
css_ast.DFontKerning: {
Chrome: {prefix: WebkitPrefix, withoutPrefix: v{33, 0, 0}},
IOS: {prefix: WebkitPrefix, withoutPrefix: v{12, 0, 0}},
Opera: {prefix: WebkitPrefix, withoutPrefix: v{20, 0, 0}},
Safari: {prefix: WebkitPrefix, withoutPrefix: v{9, 1, 0}},
},

// https://caniuse.com/css-hyphens
css_ast.DHyphens: {
Edge: {prefix: MsPrefix, withoutPrefix: v{79, 0, 0}},
Firefox: {prefix: MozPrefix, withoutPrefix: v{43, 0, 0}},
IE: {prefix: MsPrefix},
IOS: {prefix: WebkitPrefix},
Safari: {prefix: WebkitPrefix},
},

// https://caniuse.com/css-initial-letter
css_ast.DInitialLetter: {
IOS: {prefix: WebkitPrefix},
Safari: {prefix: WebkitPrefix},
},

css_ast.DMaskImage: cssMaskPrefixTable, // https://caniuse.com/mdn-css_properties_mask-image
css_ast.DMaskOrigin: cssMaskPrefixTable, // https://caniuse.com/mdn-css_properties_mask-origin
css_ast.DMaskPosition: cssMaskPrefixTable, // https://caniuse.com/mdn-css_properties_mask-position
css_ast.DMaskRepeat: cssMaskPrefixTable, // https://caniuse.com/mdn-css_properties_mask-repeat
css_ast.DMaskSize: cssMaskPrefixTable, // https://caniuse.com/mdn-css_properties_mask-size

// https://caniuse.com/css-sticky
css_ast.DPosition: {
IOS: {prefix: WebkitPrefix, withoutPrefix: v{13, 0, 0}},
Safari: {prefix: WebkitPrefix, withoutPrefix: v{13, 0, 0}},
},

// https://caniuse.com/css-color-adjust
css_ast.DPrintColorAdjust: {
Chrome: {prefix: WebkitPrefix},
Edge: {prefix: WebkitPrefix},
Opera: {prefix: WebkitPrefix},
Safari: {prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
},

// https://caniuse.com/css3-tabsize
css_ast.DTabSize: {
Firefox: {prefix: MozPrefix, withoutPrefix: v{91, 0, 0}},
Opera: {prefix: OPrefix, withoutPrefix: v{15, 0, 0}},
},

// https://caniuse.com/css-text-orientation
css_ast.DTextOrientation: {
Safari: {prefix: WebkitPrefix, withoutPrefix: v{14, 0, 0}},
},

// https://caniuse.com/text-size-adjust
css_ast.DTextSizeAdjust: {
Edge: {prefix: MsPrefix, withoutPrefix: v{79, 0, 0}},
IOS: {prefix: WebkitPrefix},
},

// https://caniuse.com/mdn-css_properties_user-select
css_ast.DUserSelect: {
Chrome: {prefix: WebkitPrefix, withoutPrefix: v{54, 0, 0}},
Edge: {prefix: MsPrefix, withoutPrefix: v{79, 0, 0}},
Firefox: {prefix: MozPrefix, withoutPrefix: v{69, 0, 0}},
IOS: {prefix: WebkitPrefix},
Opera: {prefix: WebkitPrefix, withoutPrefix: v{41, 0, 0}},
Safari: {prefix: WebkitPrefix},
IE: {prefix: MsPrefix},
},
}

func CSSPrefixData(constraints map[Engine][]int) (entries map[css_ast.D]CSSPrefix) {
for property, engines := range cssPrefixTable {
prefixes := NoPrefix
for engine, version := range constraints {
if !engine.IsBrowser() {
// Specifying "--target=es2020" shouldn't affect CSS
continue
}
if data, ok := engines[engine]; ok && (data.withoutPrefix == v{} || compareVersions(data.withoutPrefix, version) > 0) {
prefixes |= data.prefix
}
}
if prefixes != NoPrefix {
if entries == nil {
entries = make(map[css_ast.D]CSSPrefix)
}
entries[property] = prefixes
}
}
return
}
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/evanw/esbuild/internal/ast"
"github.com/evanw/esbuild/internal/compat"
"github.com/evanw/esbuild/internal/css_ast"
"github.com/evanw/esbuild/internal/js_ast"
"github.com/evanw/esbuild/internal/logger"
)
Expand Down Expand Up @@ -420,6 +421,7 @@ type Options struct {
JSX JSXOptions
LineLimit int

CSSPrefixData map[css_ast.D]compat.CSSPrefix
UnsupportedJSFeatures compat.JSFeature
UnsupportedCSSFeatures compat.CSSFeature

Expand Down
12 changes: 12 additions & 0 deletions internal/css_ast/css_decl_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const (
DAnimationName
DAnimationPlayState
DAnimationTimingFunction
DAppearance
DBackdropFilter
DBackfaceVisibility
DBackground
DBackgroundAttachment
Expand Down Expand Up @@ -177,6 +179,7 @@ const (
DHyphens
DImageOrientation
DImageRendering
DInitialLetter
DInlineSize
DInset
DJustifyContent
Expand Down Expand Up @@ -207,6 +210,7 @@ const (
DMask
DMaskComposite
DMaskImage
DMaskOrigin
DMaskPosition
DMaskRepeat
DMaskSize
Expand Down Expand Up @@ -259,6 +263,7 @@ const (
DPlaceSelf
DPointerEvents
DPosition
DPrintColorAdjust
DQuotes
DResize
DRight
Expand Down Expand Up @@ -299,6 +304,7 @@ const (
DTextOverflow
DTextRendering
DTextShadow
DTextSizeAdjust
DTextTransform
DTextUnderlinePosition
DTop
Expand Down Expand Up @@ -344,6 +350,8 @@ var KnownDeclarations = map[string]D{
"animation-name": DAnimationName,
"animation-play-state": DAnimationPlayState,
"animation-timing-function": DAnimationTimingFunction,
"appearance": DAppearance,
"backdrop-filter": DBackdropFilter,
"backface-visibility": DBackfaceVisibility,
"background": DBackground,
"background-attachment": DBackgroundAttachment,
Expand Down Expand Up @@ -496,6 +504,7 @@ var KnownDeclarations = map[string]D{
"hyphens": DHyphens,
"image-orientation": DImageOrientation,
"image-rendering": DImageRendering,
"initial-letter": DInitialLetter,
"inline-size": DInlineSize,
"inset": DInset,
"justify-content": DJustifyContent,
Expand Down Expand Up @@ -526,6 +535,7 @@ var KnownDeclarations = map[string]D{
"mask": DMask,
"mask-composite": DMaskComposite,
"mask-image": DMaskImage,
"mask-origin": DMaskOrigin,
"mask-position": DMaskPosition,
"mask-repeat": DMaskRepeat,
"mask-size": DMaskSize,
Expand Down Expand Up @@ -578,6 +588,7 @@ var KnownDeclarations = map[string]D{
"place-self": DPlaceSelf,
"pointer-events": DPointerEvents,
"position": DPosition,
"print-color-adjust": DPrintColorAdjust,
"quotes": DQuotes,
"resize": DResize,
"right": DRight,
Expand Down Expand Up @@ -618,6 +629,7 @@ var KnownDeclarations = map[string]D{
"text-overflow": DTextOverflow,
"text-rendering": DTextRendering,
"text-shadow": DTextShadow,
"text-size-adjust": DTextSizeAdjust,
"text-transform": DTextTransform,
"text-underline-position": DTextUnderlinePosition,
"top": DTop,
Expand Down
Loading

0 comments on commit 9d8a7e5

Please sign in to comment.