Skip to content

Commit 28cf2f3

Browse files
committed
fix #4184: css prefixes for stretch
1 parent bee1b09 commit 28cf2f3

File tree

6 files changed

+120
-7
lines changed

6 files changed

+120
-7
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
* Add CSS prefixes for the `stretch` sizing value ([#4184](https://github.com/evanw/esbuild/issues/4184))
6+
7+
This release adds support for prefixing CSS declarations such as `div { width: stretch }`. That CSS is now transformed into this depending on what the `--target=` setting includes:
8+
9+
```css
10+
div {
11+
width: -webkit-fill-available;
12+
width: -moz-available;
13+
width: stretch;
14+
}
15+
```
16+
317
## 0.25.4
418

519
* Add simple support for CORS to esbuild's development server ([#4125](https://github.com/evanw/esbuild/issues/4125))

compat-table/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export const cssProperties = {
111111
DBoxDecorationBreak: true,
112112
DClipPath: true,
113113
DFontKerning: true,
114+
DHeight: true,
114115
DHyphens: true,
115116
DInitialLetter: true,
116117
DMaskComposite: true,
@@ -119,6 +120,10 @@ export const cssProperties = {
119120
DMaskPosition: true,
120121
DMaskRepeat: true,
121122
DMaskSize: true,
123+
DMaxHeight: true,
124+
DMaxWidth: true,
125+
DMinHeight: true,
126+
DMinWidth: true,
122127
DPosition: true,
123128
DPrintColorAdjust: true,
124129
DTabSize: true,
@@ -131,6 +136,7 @@ export const cssProperties = {
131136
DTextOrientation: true,
132137
DTextSizeAdjust: true,
133138
DUserSelect: true,
139+
DWidth: true,
134140
}
135141

136142
export interface Support {

compat-table/src/mdn.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,30 @@ const similarPrefixedProperty: Record<string, { prefix: string, property: string
8282
}
8383

8484
const cssPrefixFeatures: Record<string, CSSProperty> = {
85+
'css.properties.height.stretch': 'DHeight',
8586
'css.properties.mask-composite': 'DMaskComposite',
8687
'css.properties.mask-image': 'DMaskImage',
8788
'css.properties.mask-origin': 'DMaskOrigin',
8889
'css.properties.mask-position': 'DMaskPosition',
8990
'css.properties.mask-repeat': 'DMaskRepeat',
9091
'css.properties.mask-size': 'DMaskSize',
92+
'css.properties.max-height.stretch': 'DMaxHeight',
93+
'css.properties.max-width.stretch': 'DMaxWidth',
94+
'css.properties.min-height.stretch': 'DMinHeight',
95+
'css.properties.min-width.stretch': 'DMinWidth',
9196
'css.properties.text-decoration-color': 'DTextDecorationColor',
9297
'css.properties.text-decoration-line': 'DTextDecorationLine',
9398
'css.properties.text-decoration-skip': 'DTextDecorationSkip',
9499
'css.properties.text-emphasis-color': 'DTextEmphasisColor',
95100
'css.properties.text-emphasis-position': 'DTextEmphasisPosition',
96101
'css.properties.text-emphasis-style': 'DTextEmphasisStyle',
97102
'css.properties.user-select': 'DUserSelect',
103+
'css.properties.width.stretch': 'DWidth',
104+
}
105+
106+
const alternativeNameToPrefix: Record<string, string> = {
107+
'-webkit-fill-available': '-webkit-',
108+
'-moz-available': '-moz-',
98109
}
99110

100111
export const js: SupportMap<JSFeature> = {} as SupportMap<JSFeature>
@@ -186,8 +197,8 @@ for (const fullKey in cssPrefixFeatures) {
186197
// its engine from EdgeHTML to Blink, basically becoming another browser)
187198
// but we ignore those cases for now.
188199
let version_unprefixed: string | undefined
189-
for (const { prefix, flags, version_added, version_removed } of entries) {
190-
if (!prefix && !flags && typeof version_added === 'string' && !version_removed && isSemver.test(version_added)) {
200+
for (const { prefix, flags, version_added, version_removed, alternative_name } of entries) {
201+
if (!prefix && !alternative_name && !flags && typeof version_added === 'string' && !version_removed && isSemver.test(version_added)) {
191202
version_unprefixed = version_added
192203
}
193204
}
@@ -208,15 +219,15 @@ for (const fullKey in cssPrefixFeatures) {
208219

209220
// Find all version ranges where a given prefix is supported
210221
for (let i = 0; i < entries.length; i++) {
211-
let { prefix, flags, version_added, version_removed } = entries[i]
222+
let { prefix, flags, version_added, version_removed, alternative_name } = entries[i]
212223

213224
if (similar) {
214225
if (prefix) throw new Error(`Unexpected prefix "${prefix}" for similar property "${similar.property}"`)
215226
prefix = similar.prefix
216227
}
217228

218-
if (prefix && !flags && typeof version_added === 'string' && isSemver.test(version_added)) {
219-
const range: PrefixRange = { prefix, start: version_added }
229+
if ((prefix || alternative_name) && !flags && typeof version_added === 'string' && isSemver.test(version_added)) {
230+
const range: PrefixRange = { prefix: prefix || alternativeNameToPrefix[alternative_name!], start: version_added }
220231
let withoutPrefix: string | undefined
221232

222233
// The prefix is no longer needed if support for the feature was removed

internal/compat/css_table.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,13 @@ var cssPrefixTable = map[css_ast.D][]prefixData{
220220
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{20, 0, 0}},
221221
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{9, 1, 0}},
222222
},
223+
css_ast.DHeight: {
224+
{engine: Chrome, prefix: WebkitPrefix},
225+
{engine: Edge, prefix: WebkitPrefix},
226+
{engine: IOS, prefix: WebkitPrefix},
227+
{engine: Opera, prefix: WebkitPrefix},
228+
{engine: Safari, prefix: WebkitPrefix},
229+
},
223230
css_ast.DHyphens: {
224231
{engine: Edge, prefix: MsPrefix, withoutPrefix: v{79, 0, 0}},
225232
{engine: Firefox, prefix: MozPrefix, withoutPrefix: v{43, 0, 0}},
@@ -273,6 +280,34 @@ var cssPrefixTable = map[css_ast.D][]prefixData{
273280
{engine: Opera, prefix: WebkitPrefix, withoutPrefix: v{106, 0, 0}},
274281
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{15, 4, 0}},
275282
},
283+
css_ast.DMaxHeight: {
284+
{engine: Chrome, prefix: WebkitPrefix},
285+
{engine: Edge, prefix: WebkitPrefix},
286+
{engine: IOS, prefix: WebkitPrefix},
287+
{engine: Opera, prefix: WebkitPrefix},
288+
{engine: Safari, prefix: WebkitPrefix},
289+
},
290+
css_ast.DMaxWidth: {
291+
{engine: Chrome, prefix: WebkitPrefix},
292+
{engine: Edge, prefix: WebkitPrefix},
293+
{engine: IOS, prefix: WebkitPrefix},
294+
{engine: Opera, prefix: WebkitPrefix},
295+
{engine: Safari, prefix: WebkitPrefix},
296+
},
297+
css_ast.DMinHeight: {
298+
{engine: Chrome, prefix: WebkitPrefix},
299+
{engine: Edge, prefix: WebkitPrefix},
300+
{engine: IOS, prefix: WebkitPrefix},
301+
{engine: Opera, prefix: WebkitPrefix},
302+
{engine: Safari, prefix: WebkitPrefix},
303+
},
304+
css_ast.DMinWidth: {
305+
{engine: Chrome, prefix: WebkitPrefix},
306+
{engine: Edge, prefix: WebkitPrefix},
307+
{engine: IOS, prefix: WebkitPrefix},
308+
{engine: Opera, prefix: WebkitPrefix},
309+
{engine: Safari, prefix: WebkitPrefix},
310+
},
276311
css_ast.DPosition: {
277312
{engine: IOS, prefix: WebkitPrefix, withoutPrefix: v{13, 0, 0}},
278313
{engine: Safari, prefix: WebkitPrefix, withoutPrefix: v{13, 0, 0}},
@@ -333,6 +368,14 @@ var cssPrefixTable = map[css_ast.D][]prefixData{
333368
{engine: Safari, prefix: KhtmlPrefix, withoutPrefix: v{3, 0, 0}},
334369
{engine: Safari, prefix: WebkitPrefix},
335370
},
371+
css_ast.DWidth: {
372+
{engine: Chrome, prefix: WebkitPrefix},
373+
{engine: Edge, prefix: WebkitPrefix},
374+
{engine: Firefox, prefix: MozPrefix},
375+
{engine: IOS, prefix: WebkitPrefix},
376+
{engine: Opera, prefix: WebkitPrefix},
377+
{engine: Safari, prefix: WebkitPrefix},
378+
},
336379
}
337380

338381
func CSSPrefixData(constraints map[Engine]Semver) (entries map[css_ast.D]CSSPrefix) {

internal/css_parser/css_decls.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,13 @@ func (p *parser) insertPrefixedDeclaration(rules []css_ast.Rule, prefix string,
444444
if len(decl.Value) != 1 || decl.Value[0].Kind != css_lexer.TIdent || !strings.EqualFold(decl.Value[0].Text, "sticky") {
445445
return rules
446446
}
447+
448+
case css_ast.DWidth, css_ast.DMinWidth, css_ast.DMaxWidth,
449+
css_ast.DHeight, css_ast.DMinHeight, css_ast.DMaxHeight:
450+
// The prefix is only needed for "width: stretch"
451+
if len(decl.Value) != 1 || decl.Value[0].Kind != css_lexer.TIdent || !strings.EqualFold(decl.Value[0].Text, "stretch") {
452+
return rules
453+
}
447454
}
448455

449456
value := css_ast.CloneTokensWithoutImportRecords(decl.Value)
@@ -455,6 +462,19 @@ func (p *parser) insertPrefixedDeclaration(rules []css_ast.Rule, prefix string,
455462
keyText = decl.KeyText
456463
value[0].Text = "-webkit-sticky"
457464

465+
case css_ast.DWidth, css_ast.DMinWidth, css_ast.DMaxWidth,
466+
css_ast.DHeight, css_ast.DMinHeight, css_ast.DMaxHeight:
467+
// The prefix applies to the value, not the property
468+
keyText = decl.KeyText
469+
470+
// This currently only applies to "stretch" (already checked above)
471+
switch prefix {
472+
case "-webkit-":
473+
value[0].Text = "-webkit-fill-available"
474+
case "-moz-":
475+
value[0].Text = "-moz-available"
476+
}
477+
458478
case css_ast.DUserSelect:
459479
// The prefix applies to the value as well as the property
460480
if prefix == "-moz-" && len(value) == 1 && value[0].Kind == css_lexer.TIdent && strings.EqualFold(value[0].Text, "none") {
@@ -481,6 +501,15 @@ func (p *parser) insertPrefixedDeclaration(rules []css_ast.Rule, prefix string,
481501
}
482502
}
483503

504+
// If we didn't change the key, manually search for a previous duplicate rule
505+
if keyText == decl.KeyText {
506+
for _, rule := range rules {
507+
if prevDecl, ok := rule.Data.(*css_ast.RDeclaration); ok && prevDecl.KeyText == keyText && css_ast.TokensEqual(prevDecl.Value, value, nil) {
508+
return rules
509+
}
510+
}
511+
}
512+
484513
// Overwrite the latest declaration with the prefixed declaration
485514
rules[len(rules)-1] = css_ast.Rule{Loc: loc, Data: &css_ast.RDeclaration{
486515
KeyText: keyText,

internal/css_parser/css_parser_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2646,7 +2646,8 @@ func TestPrefixInsertion(t *testing.T) {
26462646
// Special-case tests
26472647
expectPrintedWithAllPrefixes(t, "a { appearance: none }", "a {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n", "")
26482648
expectPrintedWithAllPrefixes(t, "a { background-clip: not-text }", "a {\n background-clip: not-text;\n}\n", "")
2649-
expectPrintedWithAllPrefixes(t, "a { background-clip: text !important }", "a {\n -webkit-background-clip: text !important;\n -ms-background-clip: text !important;\n background-clip: text !important;\n}\n", "")
2649+
expectPrintedWithAllPrefixes(t, "a { background-clip: text !important }",
2650+
"a {\n -webkit-background-clip: text !important;\n -ms-background-clip: text !important;\n background-clip: text !important;\n}\n", "")
26502651
expectPrintedWithAllPrefixes(t, "a { background-clip: text }", "a {\n -webkit-background-clip: text;\n -ms-background-clip: text;\n background-clip: text;\n}\n", "")
26512652
expectPrintedWithAllPrefixes(t, "a { hyphens: auto }", "a {\n -webkit-hyphens: auto;\n -moz-hyphens: auto;\n -ms-hyphens: auto;\n hyphens: auto;\n}\n", "")
26522653
expectPrintedWithAllPrefixes(t, "a { position: absolute }", "a {\n position: absolute;\n}\n", "")
@@ -2656,9 +2657,12 @@ func TestPrefixInsertion(t *testing.T) {
26562657
expectPrintedWithAllPrefixes(t, "a { text-decoration-color: none }", "a {\n -webkit-text-decoration-color: none;\n -moz-text-decoration-color: none;\n text-decoration-color: none;\n}\n", "")
26572658
expectPrintedWithAllPrefixes(t, "a { text-decoration-line: none }", "a {\n -webkit-text-decoration-line: none;\n -moz-text-decoration-line: none;\n text-decoration-line: none;\n}\n", "")
26582659
expectPrintedWithAllPrefixes(t, "a { text-size-adjust: none }", "a {\n -webkit-text-size-adjust: none;\n -ms-text-size-adjust: none;\n text-size-adjust: none;\n}\n", "")
2659-
expectPrintedWithAllPrefixes(t, "a { user-select: none }", "a {\n -webkit-user-select: none;\n -khtml-user-select: none;\n -moz-user-select: -moz-none;\n -ms-user-select: none;\n user-select: none;\n}\n", "")
2660+
expectPrintedWithAllPrefixes(t, "a { user-select: none }",
2661+
"a {\n -webkit-user-select: none;\n -khtml-user-select: none;\n -moz-user-select: -moz-none;\n -ms-user-select: none;\n user-select: none;\n}\n", "")
26602662
expectPrintedWithAllPrefixes(t, "a { mask-composite: add, subtract, intersect, exclude }",
26612663
"a {\n -webkit-mask-composite:\n source-over,\n source-out,\n source-in,\n xor;\n mask-composite:\n add,\n subtract,\n intersect,\n exclude;\n}\n", "")
2664+
expectPrintedWithAllPrefixes(t, "a { width: stretch }",
2665+
"a {\n width: -webkit-fill-available;\n width: -moz-available;\n width: stretch;\n}\n", "")
26622666

26632667
// Check that we insert prefixed rules each time an unprefixed rule is
26642668
// encountered. This matches the behavior of the popular "autoprefixer" tool.
@@ -2677,6 +2681,12 @@ func TestPrefixInsertion(t *testing.T) {
26772681
expectPrintedWithAllPrefixes(t,
26782682
"a { before: value; -ms-text-size-adjust: 2; text-size-adjust: 3; after: value }",
26792683
"a {\n before: value;\n -ms-text-size-adjust: 2;\n -webkit-text-size-adjust: 3;\n text-size-adjust: 3;\n after: value;\n}\n", "")
2684+
expectPrintedWithAllPrefixes(t, "a { width: -moz-available; width: stretch }",
2685+
"a {\n width: -moz-available;\n width: -webkit-fill-available;\n width: stretch;\n}\n", "")
2686+
expectPrintedWithAllPrefixes(t, "a { width: -webkit-fill-available; width: stretch }",
2687+
"a {\n width: -webkit-fill-available;\n width: -moz-available;\n width: stretch;\n}\n", "")
2688+
expectPrintedWithAllPrefixes(t, "a { width: -webkit-fill-available; width: -moz-available; width: stretch }",
2689+
"a {\n width: -webkit-fill-available;\n width: -moz-available;\n width: stretch;\n}\n", "")
26802690
}
26812691

26822692
func TestNthChild(t *testing.T) {

0 commit comments

Comments
 (0)