Skip to content

Commit 352e77f

Browse files
Fix polyfill in combiantion with unsued CSS variable removal
1 parent 57e55a6 commit 352e77f

File tree

6 files changed

+490
-338
lines changed

6 files changed

+490
-338
lines changed

packages/tailwindcss/src/ast.ts

+73-65
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ export function optimizeAst(
270270
Extract<AstNode, { nodes: AstNode[] }>['nodes'],
271271
Set<Declaration>
272272
>(() => new Set())
273+
let colorMixDeclarations = new DefaultMap<
274+
Extract<AstNode, { nodes: AstNode[] }>['nodes'],
275+
Set<Declaration>
276+
>(() => new Set())
273277
let keyframes = new Set<AtRule>()
274278
let usedKeyframeNames = new Set()
275279

@@ -326,71 +330,7 @@ export function optimizeAst(
326330
// Create fallback values for usages of the `color-mix(…)` function that reference variables
327331
// found in the theme config.
328332
if (polyfills & Polyfills.ColorMix && node.value.includes('color-mix(')) {
329-
let ast = ValueParser.parse(node.value)
330-
331-
let requiresPolyfill = false
332-
ValueParser.walk(ast, (node, { replaceWith }) => {
333-
if (node.kind !== 'function' || node.value !== 'color-mix') return
334-
335-
let containsUnresolvableVars = false
336-
let containsCurrentcolor = false
337-
ValueParser.walk(node.nodes, (node, { replaceWith }) => {
338-
if (node.kind == 'word' && node.value.toLowerCase() === 'currentcolor') {
339-
containsCurrentcolor = true
340-
requiresPolyfill = true
341-
return
342-
}
343-
if (node.kind !== 'function' || node.value !== 'var') return
344-
let firstChild = node.nodes[0]
345-
if (!firstChild || firstChild.kind !== 'word') return
346-
347-
requiresPolyfill = true
348-
349-
let inlinedColor = designSystem.theme.resolveValue(null, [firstChild.value as any])
350-
if (!inlinedColor) {
351-
containsUnresolvableVars = true
352-
return
353-
}
354-
355-
replaceWith({ kind: 'word', value: inlinedColor })
356-
})
357-
358-
if (containsUnresolvableVars || containsCurrentcolor) {
359-
let separatorIndex = node.nodes.findIndex(
360-
(node) => node.kind === 'separator' && node.value.trim().includes(','),
361-
)
362-
if (separatorIndex === -1) return
363-
let firstColorValue =
364-
node.nodes.length > separatorIndex ? node.nodes[separatorIndex + 1] : null
365-
if (!firstColorValue) return
366-
replaceWith(firstColorValue)
367-
} else if (requiresPolyfill) {
368-
// Change the colorspace to `srgb` since the fallback values should not be represented as
369-
// `oklab(…)` functions again as their support in Safari <16 is very limited.
370-
let colorspace = node.nodes[2]
371-
if (
372-
colorspace.kind === 'word' &&
373-
(colorspace.value === 'oklab' ||
374-
colorspace.value === 'oklch' ||
375-
colorspace.value === 'lab' ||
376-
colorspace.value === 'lch')
377-
) {
378-
colorspace.value = 'srgb'
379-
}
380-
}
381-
})
382-
383-
if (requiresPolyfill) {
384-
let fallback = {
385-
...node,
386-
value: ValueParser.toCss(ast),
387-
}
388-
let colorMixQuery = rule('@supports (color: color-mix(in lab, red, red))', [node])
389-
390-
parent.push(fallback, colorMixQuery)
391-
392-
return
393-
}
333+
colorMixDeclarations.get(parent).add(node)
394334
}
395335

396336
parent.push(node)
@@ -595,6 +535,74 @@ export function optimizeAst(
595535
newAst = newAst.concat(atRoots)
596536

597537
// Fallbacks
538+
// Create fallback values for usages of the `color-mix(…)` function that reference variables
539+
// found in the theme config.
540+
if (polyfills & Polyfills.ColorMix) {
541+
for (let [parent, declarations] of colorMixDeclarations) {
542+
for (let declaration of declarations) {
543+
let idx = parent.indexOf(declaration)
544+
// If the declaration is no longer present, we don't need to create a polyfill anymore
545+
if (idx === -1 || declaration.value == null) continue
546+
547+
let ast = ValueParser.parse(declaration.value)
548+
let requiresPolyfill = false
549+
ValueParser.walk(ast, (node, { replaceWith }) => {
550+
if (node.kind !== 'function' || node.value !== 'color-mix') return
551+
let containsUnresolvableVars = false
552+
let containsCurrentcolor = false
553+
ValueParser.walk(node.nodes, (node, { replaceWith }) => {
554+
if (node.kind == 'word' && node.value.toLowerCase() === 'currentcolor') {
555+
containsCurrentcolor = true
556+
requiresPolyfill = true
557+
return
558+
}
559+
if (node.kind !== 'function' || node.value !== 'var') return
560+
let firstChild = node.nodes[0]
561+
if (!firstChild || firstChild.kind !== 'word') return
562+
requiresPolyfill = true
563+
let inlinedColor = designSystem.theme.resolveValue(null, [firstChild.value as any])
564+
if (!inlinedColor) {
565+
containsUnresolvableVars = true
566+
return
567+
}
568+
replaceWith({ kind: 'word', value: inlinedColor })
569+
})
570+
if (containsUnresolvableVars || containsCurrentcolor) {
571+
let separatorIndex = node.nodes.findIndex(
572+
(node) => node.kind === 'separator' && node.value.trim().includes(','),
573+
)
574+
if (separatorIndex === -1) return
575+
let firstColorValue =
576+
node.nodes.length > separatorIndex ? node.nodes[separatorIndex + 1] : null
577+
if (!firstColorValue) return
578+
replaceWith(firstColorValue)
579+
} else if (requiresPolyfill) {
580+
// Change the colorspace to `srgb` since the fallback values should not be represented as
581+
// `oklab(…)` functions again as their support in Safari <16 is very limited.
582+
let colorspace = node.nodes[2]
583+
if (
584+
colorspace.kind === 'word' &&
585+
(colorspace.value === 'oklab' ||
586+
colorspace.value === 'oklch' ||
587+
colorspace.value === 'lab' ||
588+
colorspace.value === 'lch')
589+
) {
590+
colorspace.value = 'srgb'
591+
}
592+
}
593+
})
594+
if (!requiresPolyfill) continue
595+
596+
let fallback = {
597+
...declaration,
598+
value: ValueParser.toCss(ast),
599+
}
600+
let colorMixQuery = rule('@supports (color: color-mix(in lab, red, red))', [declaration])
601+
parent.splice(idx, 1, fallback, colorMixQuery)
602+
}
603+
}
604+
}
605+
598606
if (polyfills & Polyfills.AtProperty) {
599607
let fallbackAst = []
600608

packages/tailwindcss/src/compat/config.test.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -1164,21 +1164,21 @@ test('utilities must be prefixed', async () => {
11641164
// Prefixed utilities are generated
11651165
expect(compiler.build(['tw:underline', 'tw:hover:line-through', 'tw:custom']))
11661166
.toMatchInlineSnapshot(`
1167-
".tw\\:custom {
1168-
color: red;
1169-
}
1170-
.tw\\:underline {
1171-
text-decoration-line: underline;
1172-
}
1173-
.tw\\:hover\\:line-through {
1174-
&:hover {
1175-
@media (hover: hover) {
1176-
text-decoration-line: line-through;
1167+
".tw\\:custom {
1168+
color: red;
1169+
}
1170+
.tw\\:underline {
1171+
text-decoration-line: underline;
1172+
}
1173+
.tw\\:hover\\:line-through {
1174+
&:hover {
1175+
@media (hover: hover) {
1176+
text-decoration-line: line-through;
1177+
}
11771178
}
11781179
}
1179-
}
1180-
"
1181-
`)
1180+
"
1181+
`)
11821182

11831183
// Non-prefixed utilities are ignored
11841184
compiler = await compile(input, {

packages/tailwindcss/src/compat/plugin-api.test.ts

+45-45
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,14 @@ describe('theme', async () => {
243243

244244
expect(compiler.build(['animate-duration-316', 'animate-duration-slow']))
245245
.toMatchInlineSnapshot(`
246-
".animate-duration-316 {
247-
animation-duration: 316ms;
248-
}
249-
.animate-duration-slow {
250-
animation-duration: 800ms;
251-
}
252-
"
253-
`)
246+
".animate-duration-316 {
247+
animation-duration: 316ms;
248+
}
249+
.animate-duration-slow {
250+
animation-duration: 800ms;
251+
}
252+
"
253+
`)
254254
})
255255

256256
test('plugin theme can have opacity modifiers', async () => {
@@ -3340,16 +3340,16 @@ describe('matchUtilities()', () => {
33403340
}
33413341

33423342
expect(optimizeCss(await run(['@w-1', 'hover:@w-1'])).trim()).toMatchInlineSnapshot(`
3343-
".\\@w-1 {
3343+
".\\@w-1 {
3344+
width: 1px;
3345+
}
3346+
3347+
@media (hover: hover) {
3348+
.hover\\:\\@w-1:hover {
33443349
width: 1px;
33453350
}
3346-
3347-
@media (hover: hover) {
3348-
.hover\\:\\@w-1:hover {
3349-
width: 1px;
3350-
}
3351-
}"
3352-
`)
3351+
}"
3352+
`)
33533353
})
33543354

33553355
test('custom functional utilities can return an array of rules', async () => {
@@ -4153,30 +4153,30 @@ describe('addComponents()', () => {
41534153

41544154
expect(optimizeCss(compiled.build(['btn', 'btn-blue', 'btn-red'])).trim())
41554155
.toMatchInlineSnapshot(`
4156-
".btn {
4157-
border-radius: .25rem;
4158-
padding: .5rem 1rem;
4159-
font-weight: 600;
4160-
}
4156+
".btn {
4157+
border-radius: .25rem;
4158+
padding: .5rem 1rem;
4159+
font-weight: 600;
4160+
}
41614161
4162-
.btn-blue {
4163-
color: #fff;
4164-
background-color: #3490dc;
4165-
}
4162+
.btn-blue {
4163+
color: #fff;
4164+
background-color: #3490dc;
4165+
}
41664166
4167-
.btn-blue:hover {
4168-
background-color: #2779bd;
4169-
}
4167+
.btn-blue:hover {
4168+
background-color: #2779bd;
4169+
}
41704170
4171-
.btn-red {
4172-
color: #fff;
4173-
background-color: #e3342f;
4174-
}
4171+
.btn-red {
4172+
color: #fff;
4173+
background-color: #e3342f;
4174+
}
41754175
4176-
.btn-red:hover {
4177-
background-color: #cc1f1a;
4178-
}"
4179-
`)
4176+
.btn-red:hover {
4177+
background-color: #cc1f1a;
4178+
}"
4179+
`)
41804180
})
41814181
})
41824182

@@ -4212,16 +4212,16 @@ describe('matchComponents()', () => {
42124212

42134213
expect(optimizeCss(compiled.build(['prose', 'sm:prose-sm', 'hover:prose-lg'])).trim())
42144214
.toMatchInlineSnapshot(`
4215-
".prose {
4216-
--container-size: normal;
4217-
}
4218-
4219-
@media (hover: hover) {
4220-
.hover\\:prose-lg:hover {
4221-
--container-size: lg;
4215+
".prose {
4216+
--container-size: normal;
42224217
}
4223-
}"
4224-
`)
4218+
4219+
@media (hover: hover) {
4220+
.hover\\:prose-lg:hover {
4221+
--container-size: lg;
4222+
}
4223+
}"
4224+
`)
42254225
})
42264226
})
42274227

packages/tailwindcss/src/css-functions.test.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -756,10 +756,10 @@ describe('theme(…)', () => {
756756
}
757757
`),
758758
).toMatchInlineSnapshot(`
759-
".fam {
760-
font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
761-
}"
762-
`)
759+
".fam {
760+
font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
761+
}"
762+
`)
763763
})
764764

765765
test('theme(fontFamily.sans) (config)', async () => {
@@ -776,10 +776,10 @@ describe('theme(…)', () => {
776776
)
777777

778778
expect(optimizeCss(compiled.build([])).trim()).toMatchInlineSnapshot(`
779-
".fam {
780-
font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
781-
}"
782-
`)
779+
".fam {
780+
font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
781+
}"
782+
`)
783783
})
784784
})
785785

0 commit comments

Comments
 (0)